//! Mouse button related code use crate::{config::BUTTON_BINDS, conn_info::Connection, debug, Error, Result, WM}; use xcb::{ x::{ self, ButtonIndex, ButtonPressEvent, Cursor, EventMask, GrabButton, GrabMode, GrabPointer, ModMask, MotionNotifyEvent, QueryPointer, UngrabButton, UngrabPointer, Window, }, Xid, }; impl WM<'_> { /// Dispatch the given button press event according to [`self::BUTTON_BINDS`] pub fn handle_button_press(&mut self, e: &ButtonPressEvent) { let button = match e.detail() { 1 => ButtonIndex::N1, 2 => ButtonIndex::N2, 3 => ButtonIndex::N3, 4 => ButtonIndex::N4, 5 => ButtonIndex::N5, _ => return, }; BUTTON_BINDS.dispatch( self, button, ModMask::from_bits_truncate(e.state().bits()) .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, modifiers: ModMask::ANY, grab_window: window, }); if !focused { conn.send_request(&GrabButton { button: ButtonIndex::Any, modifiers: ModMask::ANY, grab_window: window, owner_events: false, event_mask: EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE, pointer_mode: GrabMode::Sync, keyboard_mode: GrabMode::Sync, confine_to: Window::none(), cursor: Cursor::none(), }); } for btn in BUTTON_BINDS.binds() { for modifiers in [ ModMask::empty(), ModMask::LOCK, conn.keyboard_state.numlock_mask(), conn.keyboard_state.numlock_mask() | ModMask::LOCK, ] { conn.send_request(&GrabButton { button: btn.button, modifiers: btn.modifiers | modifiers, grab_window: window, owner_events: false, event_mask: EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE, pointer_mode: GrabMode::Async, keyboard_mode: GrabMode::Sync, confine_to: Window::none(), cursor: Cursor::none(), }); } } } /// A button bound to some action pub struct ButtonBind { pub modifiers: ModMask, pub button: ButtonIndex, pub action: &'static dyn Fn(&mut WM<'_>), } /// A set of button binds. Currently, there is only one instance of this defined statically: [`crate::config::BUTTON_BINDS`]. pub struct ButtonBinds(pub &'static [ButtonBind]); impl ButtonBinds { /// Get an iterator over all bound buttons pub fn binds(&self) -> impl Iterator { self.0.iter() } /// Attempt to run the action for the matching buttonbind, if it exists. pub fn dispatch(&self, wm: &mut WM<'_>, button: ButtonIndex, modifiers: ModMask) { debug!("received {modifiers:?} {button:?}"); if let Some(bind) = self .binds() .find(|b| b.button == button && modifiers == b.modifiers) { debug!("found action"); (bind.action)(wm); } } } impl std::fmt::Debug for ButtonBind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ButtonBind") .field("modifiers", &self.modifiers) .field("button", &self.button) .finish_non_exhaustive() } }