summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-13 17:56:39 +0100
committertcmal <me@aria.rip>2024-08-13 21:48:11 +0100
commite4d9484d0e1ebcf496d0606800560c09d2753df4 (patch)
tree23931a1cba89ca75e592c7351022bf7b22c487a8
parentd973959f7f34e02eeb01766b01aff680639a8c4c (diff)
Add moving and resizing with mouse
-rw-r--r--src/buttons.rs125
-rw-r--r--src/clients/client.rs2
-rw-r--r--src/clients/mod.rs2
-rw-r--r--src/config.rs19
-rw-r--r--src/conn_info/cursors.rs7
-rw-r--r--src/helpers.rs14
-rw-r--r--src/main.rs68
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() {