diff options
Diffstat (limited to 'src/buttons.rs')
-rw-r--r-- | src/buttons.rs | 125 |
1 files changed, 121 insertions, 4 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 { |