diff options
-rw-r--r-- | src/buttons.rs | 125 | ||||
-rw-r--r-- | src/clients/client.rs | 2 | ||||
-rw-r--r-- | src/clients/mod.rs | 2 | ||||
-rw-r--r-- | src/config.rs | 19 | ||||
-rw-r--r-- | src/conn_info/cursors.rs | 7 | ||||
-rw-r--r-- | src/helpers.rs | 14 | ||||
-rw-r--r-- | src/main.rs | 68 |
7 files changed, 184 insertions, 53 deletions
diff --git a/src/buttons.rs b/src/buttons.rs index a7d8a34..da6c9c2 100644 --- a/src/buttons.rs +++ b/src/buttons.rs @@ -1,10 +1,10 @@ //! Mouse button related code -use crate::{config::BUTTON_BINDS, conn_info::Connection, debug, WM}; +use crate::{config::BUTTON_BINDS, conn_info::Connection, debug, Error, Result, WM}; use xcb::{ x::{ - ButtonIndex, ButtonPressEvent, Cursor, EventMask, GrabButton, GrabMode, ModMask, - UngrabButton, Window, + self, ButtonIndex, ButtonPressEvent, Cursor, EventMask, GrabButton, GrabMode, GrabPointer, + ModMask, MotionNotifyEvent, QueryPointer, UngrabButton, UngrabPointer, Window, }, Xid, }; @@ -27,13 +27,130 @@ impl WM<'_> { .difference(self.conn.keyboard_state.numlock_mask() | ModMask::LOCK), ); } + + /// Move the currently focused window until the mouse button is released. + /// This starts its own event loop, so won't exit till the user is done moving the window. + pub fn mouse_move(&mut self) -> Result<()> { + let Some(w) = self.clients.focused_mut().map(|c| c.window()) else { + return Ok(()); + }; + + let reply = self + .conn + .wait_for_reply(self.conn.send_request(&QueryPointer { window: w }))?; + let (original_x, original_y) = (reply.win_x(), reply.win_y()); + + self.track_mouse_movement(w, |this, e| { + if let Some(c) = this.clients.focused_mut() { + c.set_geom( + &this.conn, + e.root_x() - original_x, + e.root_y() - original_y, + c.width(), + c.height(), + c.border_width(), + ); + c.configure_notify(&this.conn); + } + + Ok(()) + }) + } + + /// Resize the currently focused window with the mouse. + /// This starts its own event loop, so won't exit till the user is done resizing the window. + pub fn mouse_resize(&mut self) -> Result<()> { + let Some((w, original_width, original_height)) = self + .clients + .focused_mut() + .map(|c| (c.window(), c.width(), c.height())) + else { + return Ok(()); + }; + + let reply = self + .conn + .wait_for_reply(self.conn.send_request(&QueryPointer { window: w }))?; + let (original_x, original_y) = (reply.win_x(), reply.win_y()); + + self.track_mouse_movement(w, |this, e| { + if let Some(c) = this.clients.focused_mut() { + c.set_geom( + &this.conn, + c.x(), + c.y(), + original_width.saturating_add_signed(e.root_x() - original_x), + original_height.saturating_add_signed(e.root_y() - original_y), + c.border_width(), + ); + c.configure_notify(&this.conn); + } + + Ok(()) + }) + } + + /// Start tracking pointer movement, calling `f` with each generated motion event. + /// This starts its own event loop, so won't exit till the user is done resizing the window. + pub fn track_mouse_movement( + &mut self, + w: Window, + mut f: impl FnMut(&mut Self, MotionNotifyEvent) -> Result<()>, + ) -> Result<()> { + self.conn + .wait_for_reply(self.conn.send_request(&GrabPointer { + owner_events: false, + grab_window: w, + event_mask: EventMask::BUTTON_PRESS + | EventMask::BUTTON_RELEASE + | EventMask::POINTER_MOTION, + pointer_mode: GrabMode::Async, + keyboard_mode: GrabMode::Async, + confine_to: Window::none(), + cursor: self.conn.cursors.moving(), + time: x::CURRENT_TIME, + }))?; + + let mut last_time = 0; + let mut res = Ok(()); + while res.is_ok() { + match self.conn.wait_for_event() { + Ok(xcb::Event::X(x::Event::MotionNotify(e))) => { + if e.time() - last_time <= (1000 / 60) { + continue; + } + + last_time = e.time(); + res = f(self, e); + res = res.and(self.conn.flush()); + } + Ok(xcb::Event::X(x::Event::ButtonRelease(_))) => { + break; + } + Ok(e) => self.dispatch_event(e)?, + Err(Error::Xcb(xcb::Error::Protocol(e))) => { + eprintln!("protocol error in event loop: {e:#?}\ncontinuing anyway"); + } + Err(e) => { + eprintln!("unrecoverable error: {e:#?}\nexiting event loop"); + res = Err(e); + } + } + } + + self.conn.send_request(&UngrabPointer { + time: x::CURRENT_TIME, + }); + + res + } } pub fn grab(conn: &Connection<'_>, window: Window, focused: bool) { conn.send_request(&UngrabButton { button: ButtonIndex::Any, - grab_window: window, modifiers: ModMask::ANY, + grab_window: window, }); if !focused { diff --git a/src/clients/client.rs b/src/clients/client.rs index c8fca6c..03afc16 100644 --- a/src/clients/client.rs +++ b/src/clients/client.rs @@ -62,7 +62,7 @@ impl Client { } } - /// Send a configure configure notify event with the current geometry. + /// Send a configure notify event with the current geometry. pub fn configure_notify(&self, conn: &Connection<'_>) { conn.send_request(&SendEvent { destination: SendEventDest::Window(self.window), diff --git a/src/clients/mod.rs b/src/clients/mod.rs index a59240d..29219b6 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -243,8 +243,6 @@ impl ClientState { | EventMask::STRUCTURE_NOTIFY, ); - buttons::grab(conn, c.window(), false); - // Add to net_client_list conn.send_request(&ChangeProperty { mode: xcb::x::PropMode::Append, diff --git a/src/config.rs b/src/config.rs index aebe0f1..54549b7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,8 @@ use crate::{ clients::TagFocus, conn_info::Colour, helpers::{ - focus_next, focus_prev, set_tag, set_tag_focus, spawn, toggle_floating, toggle_fullscreen, + focus_next, focus_prev, mouse_move, mouse_resize, set_tag, set_tag_focus, spawn, + toggle_floating, toggle_fullscreen, }, keys::Keybinds, }; @@ -69,27 +70,15 @@ pub const KEYBINDS: Keybinds = Keybinds(&[ // { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, // { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, // { MODKEY, XK_space, setlayout, {0} }, - // { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, - // { MODKEY, XK_0, view, {.ui = ~0 } }, - // { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, // { MODKEY, XK_comma, focusmon, {.i = -1 } }, // { MODKEY, XK_period, focusmon, {.i = +1 } }, // { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, // { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, - // TAGKEYS( XK_1, 0) - // TAGKEYS( XK_2, 1) - // TAGKEYS( XK_3, 2) - // TAGKEYS( XK_4, 3) - // TAGKEYS( XK_5, 4) - // TAGKEYS( XK_6, 5) - // TAGKEYS( XK_7, 6) - // TAGKEYS( XK_8, 7) - // TAGKEYS( XK_9, 8) // { MODKEY|ShiftMask, XK_q, quit, {0} }, ]); /// The (mouse) button binds to use pub const BUTTON_BINDS: ButtonBinds = ButtonBinds(&[ - bind_btn!(MAIN_MODIFIER , N1 -> &toggle_floating), - // TODO + bind_btn!(MAIN_MODIFIER , N1 -> &mouse_move), + bind_btn!(MAIN_MODIFIER , N2 -> &mouse_resize), ]); diff --git a/src/conn_info/cursors.rs b/src/conn_info/cursors.rs index bb547e4..c564f56 100644 --- a/src/conn_info/cursors.rs +++ b/src/conn_info/cursors.rs @@ -6,6 +6,7 @@ use xcb::{ // https://tronche.com/gui/x/xlib/appendix/b/ const XC_LEFT_PTR: u16 = 68; +const XC_FLEUR: u16 = 52; /// Caches X11 cursor objects #[derive(Debug)] @@ -14,6 +15,7 @@ pub struct Cursors { font: Font, normal: Cursor, + moving: Cursor, } impl Cursors { @@ -28,6 +30,7 @@ impl Cursors { Ok(Self { normal: Self::load_cursor(conn, font, XC_LEFT_PTR)?, + moving: Self::load_cursor(conn, font, XC_FLEUR)?, font, }) } @@ -57,6 +60,10 @@ impl Cursors { self.normal } + pub const fn moving(&self) -> Cursor { + self.moving + } + /// Free the associated resources to avoid leaking them. /// This object must not be used again after this method is called, as all resources will be invalid. pub unsafe fn free(&self, conn: &RawConnection) { diff --git a/src/helpers.rs b/src/helpers.rs index cc76e43..175eea9 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -82,3 +82,17 @@ pub fn set_tag(wm: &mut WM<'_>, tag: Tag) { wm.clients.set_client_tag(&wm.conn, pos, tag); } } + +/// Move the currently focused window with the mouse +pub fn mouse_move(wm: &mut WM<'_>) { + if let Err(e) = wm.mouse_move() { + println!("error when moving with mouse: {:?}", e); + } +} + +/// Resize the currently focused window with the mouse +pub fn mouse_resize(wm: &mut WM<'_>) { + if let Err(e) = wm.mouse_resize() { + println!("error when resizing with mouse: {:?}", e); + } +} diff --git a/src/main.rs b/src/main.rs index 946c0ca..5b15b9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,37 +83,8 @@ impl<'a> WM<'a> { pub fn event_loop(&mut self) -> Result<()> { loop { match self.conn.wait_for_event() { - Ok(x) => { - debug!("received event: {x:?}"); - - match x { - // See keys.rs - Event::X(x::Event::KeyPress(e)) => self.handle_key_press(&e), - Event::X(x::Event::MappingNotify(e)) => self.handle_mapping_notify(&e)?, - - // See buttons.rs - Event::X(x::Event::ButtonPress(e)) => self.handle_button_press(&e), - - // See clients/mod.rs - Event::X(x::Event::ConfigureRequest(e)) => { - self.handle_configure_request(&e)?; - } - Event::X(x::Event::ConfigureNotify(e)) => { - self.handle_configure_notify(&e)?; - } - Event::X(x::Event::DestroyNotify(e)) => self.handle_destroy_notify(&e), - Event::X(x::Event::MapRequest(e)) => self.handle_map_request(&e)?, - Event::X(x::Event::UnmapNotify(e)) => self.handle_unmap_notify(&e), - - // See focus.rs - Event::X(x::Event::EnterNotify(e)) => self.handle_enter_notify(&e), - Event::X(x::Event::FocusIn(e)) => self.handle_focus_in(&e), - - // See below - Event::X(x::Event::PropertyNotify(e)) => self.handle_property_notify(&e), - Event::X(x::Event::ClientMessage(e)) => self.handle_client_message(&e), - _ => {} - } + Ok(e) => { + self.dispatch_event(e)?; } Err(Error::Xcb(xcb::Error::Protocol(e))) => { eprintln!("protocol error in event loop: {e:#?}\ncontinuing anyway"); @@ -127,6 +98,41 @@ impl<'a> WM<'a> { } } + pub fn dispatch_event(&mut self, e: xcb::Event) -> Result<()> { + debug!("received event: {e:?}"); + + match e { + // See keys.rs + Event::X(x::Event::KeyPress(e)) => self.handle_key_press(&e), + Event::X(x::Event::MappingNotify(e)) => self.handle_mapping_notify(&e)?, + + // See buttons.rs + Event::X(x::Event::ButtonPress(e)) => self.handle_button_press(&e), + + // See clients/mod.rs + Event::X(x::Event::ConfigureRequest(e)) => { + self.handle_configure_request(&e)?; + } + Event::X(x::Event::ConfigureNotify(e)) => { + self.handle_configure_notify(&e)?; + } + Event::X(x::Event::DestroyNotify(e)) => self.handle_destroy_notify(&e), + Event::X(x::Event::MapRequest(e)) => self.handle_map_request(&e)?, + Event::X(x::Event::UnmapNotify(e)) => self.handle_unmap_notify(&e), + + // See focus.rs + Event::X(x::Event::EnterNotify(e)) => self.handle_enter_notify(&e), + Event::X(x::Event::FocusIn(e)) => self.handle_focus_in(&e), + + // See below + Event::X(x::Event::PropertyNotify(e)) => self.handle_property_notify(&e), + Event::X(x::Event::ClientMessage(e)) => self.handle_client_message(&e), + _ => {} + } + + Ok(()) + } + /// Update client properties when they change in X11. fn handle_property_notify(&mut self, e: &PropertyNotifyEvent) { if x::ATOM_WM_HINTS == e.atom() { |