From d973959f7f34e02eeb01766b01aff680639a8c4c Mon Sep 17 00:00:00 2001 From: tcmal Date: Tue, 6 Aug 2024 14:39:55 +0100 Subject: Add support for mouse button binds --- src/buttons.rs | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/clients/mod.rs | 11 ++++-- src/config.rs | 12 ++++-- src/helpers.rs | 34 ++++++++++++++-- src/main.rs | 3 ++ 5 files changed, 161 insertions(+), 10 deletions(-) create mode 100644 src/buttons.rs diff --git a/src/buttons.rs b/src/buttons.rs new file mode 100644 index 0000000..a7d8a34 --- /dev/null +++ b/src/buttons.rs @@ -0,0 +1,111 @@ +//! Mouse button related code + +use crate::{config::BUTTON_BINDS, conn_info::Connection, debug, WM}; +use xcb::{ + x::{ + ButtonIndex, ButtonPressEvent, Cursor, EventMask, GrabButton, GrabMode, ModMask, + UngrabButton, 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), + ); + } +} + +pub fn grab(conn: &Connection<'_>, window: Window, focused: bool) { + conn.send_request(&UngrabButton { + button: ButtonIndex::Any, + grab_window: window, + modifiers: ModMask::ANY, + }); + + 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::BUTTONBINDS`]. +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() + } +} diff --git a/src/clients/mod.rs b/src/clients/mod.rs index 1abab20..a59240d 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -3,6 +3,7 @@ use std::cmp::{min, Ordering}; use crate::{ + buttons, config::BORDER_WIDTH, conn_info::Connection, debug, @@ -87,7 +88,7 @@ impl WM<'_> { /// Removing destroyed windows from the client list and rearrange. pub(crate) fn handle_destroy_notify(&mut self, e: &DestroyNotifyEvent) { - self.clients.unmanage(&self.conn, e.window()); + self.clients.unmanage_destroyed(&self.conn, e.window()); } /// Map a window, starting to manage it if needed. @@ -242,7 +243,7 @@ impl ClientState { | EventMask::STRUCTURE_NOTIFY, ); - // TODO: grabbuttons + buttons::grab(conn, c.window(), false); // Add to net_client_list conn.send_request(&ChangeProperty { @@ -258,8 +259,8 @@ impl ClientState { self.rearrange_monitor(conn, mon); } - /// Stop managing the given window - pub fn unmanage(&mut self, conn: &Connection<'_>, window: Window) { + /// Stop managing the given destroyed window + pub fn unmanage_destroyed(&mut self, conn: &Connection<'_>, window: Window) { let Some((mon, i)) = self.find_client_pos(window) else { return; }; @@ -275,6 +276,7 @@ impl ClientState { if let Some(new) = self.set_focused(mon, i) { new.set_border(conn, conn.colours.border_focused()); new.sync_hints(conn, true); + buttons::grab(conn, new.window(), true); if !new.never_focus() { conn.send_request(&SetInputFocus { revert_to: InputFocus::PointerRoot, @@ -307,6 +309,7 @@ impl ClientState { pub fn unfocus(&mut self, conn: &Connection<'_>) { if let Some(old) = self.focused_mut() { old.set_border(conn, conn.colours.border_normal()); + buttons::grab(conn, old.window(), false); } } diff --git a/src/config.rs b/src/config.rs index 3182ba7..aebe0f1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,16 +2,16 @@ #![allow(clippy::unreadable_literal)] // Colours are more readable this way imo use xcb::x::ModMask; -use xkeysym::Keysym; use crate::{ - bind, + bind, bind_btn, + buttons::ButtonBinds, clients::TagFocus, conn_info::Colour, helpers::{ focus_next, focus_prev, set_tag, set_tag_focus, spawn, toggle_floating, toggle_fullscreen, }, - keys::{Keybind, Keybinds}, + keys::Keybinds, }; /// Width of the border around windows @@ -87,3 +87,9 @@ pub const KEYBINDS: Keybinds = Keybinds(&[ // 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 +]); diff --git a/src/helpers.rs b/src/helpers.rs index 24c8cf2..cc76e43 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,6 +1,9 @@ //! Helpers for writing configuration -use crate::WM; +use crate::{ + clients::{Tag, TagFocus}, + WM, +}; use std::{ffi::OsStr, process::Command}; /// Syntax sugar for creating [`Keybind`]s. This is helpful for writing [`crate::config::KEYBINDS`] @@ -12,9 +15,21 @@ use std::{ffi::OsStr, process::Command}; #[macro_export] macro_rules! bind { ($mod:expr , $key:ident -> $action:expr) => { - Keybind { + $crate::keys::Keybind { modifiers: $mod, - key: Keysym::$key, + key: ::xkeysym::Keysym::$key, + action: $action, + } + }; +} + +/// Syntax sugar for creating [`ButtonBind`]s. See also: [`self::bind`]. +#[macro_export] +macro_rules! bind_btn { + ($mod:expr , $key:ident -> $action:expr) => { + $crate::buttons::ButtonBind { + modifiers: $mod, + button: ::xcb::x::ButtonIndex::$key, action: $action, } }; @@ -54,3 +69,16 @@ pub fn toggle_fullscreen(wm: &mut WM<'_>) { wm.clients.toggle_fullscreen(&wm.conn, pos); } } + +/// Set the focused tag for the currently selected monitor +pub fn set_tag_focus(wm: &mut WM<'_>, tag_focus: TagFocus) { + wm.clients + .set_mon_tag_focus(&wm.conn, wm.clients.focused_mon(), tag_focus); +} + +/// Set the tag for the currently selected client +pub fn set_tag(wm: &mut WM<'_>, tag: Tag) { + if let Some(pos) = wm.clients.focused_pos() { + wm.clients.set_client_tag(&wm.conn, pos, tag); + } +} diff --git a/src/main.rs b/src/main.rs index 02f83c8..946c0ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -91,6 +91,9 @@ impl<'a> WM<'a> { 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)?; -- cgit v1.2.3