From d4257687608f07fd2ccdc08a2758212b62746b00 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 3 Mar 2024 14:18:46 +0100 Subject: [PATCH] tree: support toggling floating with double clicks --- jay-config/src/_private/client.rs | 8 +++++++ jay-config/src/_private/ipc.rs | 6 ++++++ jay-config/src/input.rs | 28 +++++++++++++++++++++++++ src/compositor.rs | 2 ++ src/config/handler.rs | 14 +++++++++++++ src/ifs/wl_seat.rs | 22 ++++++++++--------- src/state.rs | 2 ++ src/tree/container.rs | 14 ++++++++++++- src/tree/float.rs | 19 ++++++++++++++--- src/utils.rs | 1 + src/utils/double_click_state.rs | 35 +++++++++++++++++++++++++++++++ 11 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 src/utils/double_click_state.rs diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index bf3415b3..bf120466 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -465,6 +465,14 @@ impl Client { *self.on_new_input_device.borrow_mut() = Some(Rc::new(f)); } + pub fn set_double_click_interval(&self, usec: u64) { + self.send(&ClientMessage::SetDoubleClickIntervalUsec { usec }); + } + + pub fn set_double_click_distance(&self, dist: i32) { + self.send(&ClientMessage::SetDoubleClickDistance { dist }); + } + pub fn connector_set_position(&self, connector: Connector, x: i32, y: i32) { self.send(&ClientMessage::ConnectorSetPosition { connector, x, y }); } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 49c69e81..779288cb 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -346,6 +346,12 @@ pub enum ClientMessage<'a> { connector: Connector, transform: Transform, }, + SetDoubleClickIntervalUsec { + usec: u64, + }, + SetDoubleClickDistance { + dist: i32, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 5ee9cc3c..96edc040 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -10,6 +10,7 @@ use { Axis, Direction, ModifiedKeySym, Workspace, }, serde::{Deserialize, Serialize}, + std::time::Duration, }; /// An input device. @@ -257,6 +258,8 @@ impl Seat { } /// Toggles whether the currently focused window is floating. + /// + /// You can do the same by double-clicking on the header. pub fn toggle_floating(self) { get!().toggle_floating(self); } @@ -329,3 +332,28 @@ pub fn on_new_seat(f: F) { pub fn on_new_input_device(f: F) { get!().on_new_input_device(f) } + +/// Sets the maximum time between two clicks to be registered as a double click by the +/// compositor. +/// +/// This only affects interactions with the compositor UI and has no effect on +/// applications. +/// +/// The default is 400 ms. +pub fn set_double_click_time(duration: Duration) { + let usec = duration.as_micros().min(u64::MAX as u128); + get!().set_double_click_interval(usec as u64) +} + +/// Sets the maximum distance between two clicks to be registered as a double click by the +/// compositor. +/// +/// This only affects interactions with the compositor UI and has no effect on +/// applications. +/// +/// Setting a negative distance disables double clicks. +/// +/// The default is 5. +pub fn set_double_click_distance(distance: i32) { + get!().set_double_click_distance(distance) +} diff --git a/src/compositor.rs b/src/compositor.rs index ee462330..d65b81c4 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -205,6 +205,8 @@ fn start_compositor2( drm_feedback_ids: Default::default(), direct_scanout_enabled: Cell::new(true), output_transforms: Default::default(), + double_click_interval_usec: Cell::new(400 * 1000), + double_click_distance: Cell::new(5), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index 42a078a7..168e5786 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -615,6 +615,14 @@ impl ConfigProxyHandler { self.state.default_workspace_capture.set(capture); } + fn handle_set_double_click_interval_usec(&self, usec: u64) { + self.state.double_click_interval_usec.set(usec); + } + + fn handle_set_double_click_distance(&self, dist: i32) { + self.state.double_click_distance.set(dist); + } + fn handle_get_seat_workspace(&self, seat: Seat) -> Result<(), CphError> { let seat = self.get_seat(seat)?; let output = seat.get_output(); @@ -1355,6 +1363,12 @@ impl ConfigProxyHandler { } => self .handle_connector_set_transform(connector, transform) .wrn("connector_set_transform")?, + ClientMessage::SetDoubleClickIntervalUsec { usec } => { + self.handle_set_double_click_interval_usec(usec) + } + ClientMessage::SetDoubleClickDistance { dist } => { + self.handle_set_double_click_distance(dist) + } } Ok(()) } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 071ee511..be0d1c87 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -47,7 +47,7 @@ use { time::now_usec, tree::{ generic_node_visitor, ContainerNode, ContainerSplit, Direction, FloatNode, FoundNode, - Node, OutputNode, WorkspaceNode, + Node, OutputNode, ToplevelNode, WorkspaceNode, }, utils::{ asyncevent::AsyncEvent, @@ -623,6 +623,10 @@ impl WlSeatGlobal { Some(tl) => tl, _ => return, }; + self.set_tl_floating(tl, floating); + } + + pub fn set_tl_floating(self: &Rc, tl: Rc, floating: bool) { let data = tl.tl_data(); if data.is_fullscreen.get() { return; @@ -634,15 +638,13 @@ impl WlSeatGlobal { Some(p) => p, _ => return, }; - if let Some(cn) = parent.node_into_containing_node() { - if !floating { - cn.cnode_remove_child2(tl.tl_as_node(), true); - self.state.map_tiled(tl); - } else if let Some(ws) = data.workspace.get() { - cn.cnode_remove_child2(tl.tl_as_node(), true); - let (width, height) = data.float_size(&ws); - self.state.map_floating(tl, width, height, &ws, None); - } + if !floating { + parent.cnode_remove_child2(tl.tl_as_node(), true); + self.state.map_tiled(tl); + } else if let Some(ws) = data.workspace.get() { + parent.cnode_remove_child2(tl.tl_as_node(), true); + let (width, height) = data.float_size(&ws); + self.state.map_floating(tl, width, height, &ws, None); } } diff --git a/src/state.rs b/src/state.rs index 6816a991..ee6ae0a2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -154,6 +154,8 @@ pub struct State { pub drm_feedback_ids: DrmFeedbackIds, pub direct_scanout_enabled: Cell, pub output_transforms: RefCell, Transform>>, + pub double_click_interval_usec: Cell, + pub double_click_distance: Cell, } // impl Drop for State { diff --git a/src/tree/container.rs b/src/tree/container.rs index 466a94c4..65fbeb5e 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -18,6 +18,7 @@ use { }, utils::{ clonecell::CloneCell, + double_click_state::DoubleClickState, errorfmt::ErrorFmt, linkedlist::{LinkedList, LinkedNode, NodeRef}, numcell::NumCell, @@ -146,6 +147,7 @@ struct SeatState { x: i32, y: i32, op: Option, + double_click_state: DoubleClickState, } impl ContainerChild { @@ -521,6 +523,7 @@ impl ContainerNode { x, y, op: None, + double_click_state: Default::default(), }); let mut changed = false; changed |= mem::replace(&mut seat_state.x, x) != x; @@ -1176,7 +1179,7 @@ impl Node for ContainerNode { fn node_on_button( self: Rc, seat: &Rc, - _time_usec: u64, + time_usec: u64, button: u32, state: KeyState, _serial: u32, @@ -1232,6 +1235,15 @@ impl Node for ContainerNode { } return; }; + if seat_data + .double_click_state + .click(&self.state, time_usec, seat_data.x, seat_data.y) + && kind == SeatOpKind::Move + { + drop(seat_datas); + seat.set_tl_floating(child.node.clone(), true); + return; + } seat_data.op = Some(SeatOp { child, kind }) } else if state == KeyState::Released { let op = seat_data.op.take().unwrap(); diff --git a/src/tree/float.rs b/src/tree/float.rs index b0423d36..094fcd74 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -14,8 +14,8 @@ use { StackedNode, ToplevelNode, WorkspaceNode, }, utils::{ - clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - linkedlist::LinkedNode, + clonecell::CloneCell, copyhashmap::CopyHashMap, double_click_state::DoubleClickState, + errorfmt::ErrorFmt, linkedlist::LinkedNode, }, }, ahash::AHashMap, @@ -57,6 +57,7 @@ struct SeatState { op_active: bool, dist_hor: i32, dist_ver: i32, + double_click_state: DoubleClickState, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -228,6 +229,7 @@ impl FloatNode { op_active: false, dist_hor: 0, dist_ver: 0, + double_click_state: Default::default(), }); seat_state.x = x; seat_state.y = y; @@ -462,7 +464,7 @@ impl Node for FloatNode { fn node_on_button( self: Rc, seat: &Rc, - _time_usec: u64, + time_usec: u64, button: u32, state: KeyState, _serial: u32, @@ -479,6 +481,17 @@ impl Node for FloatNode { if state != KeyState::Pressed { return; } + if seat_data + .double_click_state + .click(&self.state, time_usec, seat_data.x, seat_data.y) + && seat_data.op_type == OpType::Move + { + if let Some(tl) = self.child.get() { + drop(seat_datas); + seat.set_tl_floating(tl, false); + return; + } + } seat_data.op_active = true; let pos = self.position.get(); match seat_data.op_type { diff --git a/src/utils.rs b/src/utils.rs index 7f48eebd..68f72d1d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,6 +10,7 @@ pub mod cell_ext; pub mod clonecell; pub mod copyhashmap; pub mod debug_fn; +pub mod double_click_state; pub mod errorfmt; pub mod fdcloser; pub mod hex; diff --git a/src/utils/double_click_state.rs b/src/utils/double_click_state.rs new file mode 100644 index 00000000..ffb827f1 --- /dev/null +++ b/src/utils/double_click_state.rs @@ -0,0 +1,35 @@ +use crate::state::State; + +#[derive(Default)] +pub struct DoubleClickState { + last_click: Option<(u64, i32, i32)>, +} + +impl DoubleClickState { + pub fn click(&mut self, state: &State, time_usec: u64, x: i32, y: i32) -> bool { + let res = self.click_(state, time_usec, x, y); + if !res { + self.last_click = Some((time_usec, x, y)); + } + res + } + + fn click_(&mut self, state: &State, time_usec: u64, x: i32, y: i32) -> bool { + let Some((last_usec, last_x, last_y)) = self.last_click.take() else { + return false; + }; + if time_usec.wrapping_sub(last_usec) > state.double_click_interval_usec.get() { + return false; + } + let max_dist = state.double_click_distance.get(); + if max_dist < 0 { + return false; + } + let dist_x = last_x - x; + let dist_y = last_y - y; + if dist_x * dist_x + dist_y * dist_y > max_dist * max_dist { + return false; + } + true + } +}