summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-06 14:39:55 +0100
committertcmal <me@aria.rip>2024-08-13 17:56:36 +0100
commitd973959f7f34e02eeb01766b01aff680639a8c4c (patch)
tree155aab05ad70915d0ebdf509e828765c0bac2822
parent89b5f3f87c8022f2d32c422c3516fca2f1538d69 (diff)
Add support for mouse button binds
-rw-r--r--src/buttons.rs111
-rw-r--r--src/clients/mod.rs11
-rw-r--r--src/config.rs12
-rw-r--r--src/helpers.rs34
-rw-r--r--src/main.rs3
5 files changed, 161 insertions, 10 deletions
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<Item = &ButtonBind> {
+ 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)?;