Skip to content

Commit

Permalink
Merge pull request #114 from mahkoh/jorth/double-click
Browse files Browse the repository at this point in the history
tree: support toggling floating with double clicks
  • Loading branch information
mahkoh authored Mar 3, 2024
2 parents a588b90 + d425768 commit 9115919
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 14 deletions.
8 changes: 8 additions & 0 deletions jay-config/src/_private/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
Expand Down
6 changes: 6 additions & 0 deletions jay-config/src/_private/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,12 @@ pub enum ClientMessage<'a> {
connector: Connector,
transform: Transform,
},
SetDoubleClickIntervalUsec {
usec: u64,
},
SetDoubleClickDistance {
dist: i32,
},
}

#[derive(Serialize, Deserialize, Debug)]
Expand Down
28 changes: 28 additions & 0 deletions jay-config/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use {
Axis, Direction, ModifiedKeySym, Workspace,
},
serde::{Deserialize, Serialize},
std::time::Duration,
};

/// An input device.
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -329,3 +332,28 @@ pub fn on_new_seat<F: Fn(Seat) + 'static>(f: F) {
pub fn on_new_input_device<F: Fn(InputDevice) + 'static>(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)
}
2 changes: 2 additions & 0 deletions src/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions src/config/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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(())
}
Expand Down
22 changes: 12 additions & 10 deletions src/ifs/wl_seat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -623,6 +623,10 @@ impl WlSeatGlobal {
Some(tl) => tl,
_ => return,
};
self.set_tl_floating(tl, floating);
}

pub fn set_tl_floating(self: &Rc<Self>, tl: Rc<dyn ToplevelNode>, floating: bool) {
let data = tl.tl_data();
if data.is_fullscreen.get() {
return;
Expand All @@ -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);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ pub struct State {
pub drm_feedback_ids: DrmFeedbackIds,
pub direct_scanout_enabled: Cell<bool>,
pub output_transforms: RefCell<AHashMap<Rc<OutputId>, Transform>>,
pub double_click_interval_usec: Cell<u64>,
pub double_click_distance: Cell<i32>,
}

// impl Drop for State {
Expand Down
14 changes: 13 additions & 1 deletion src/tree/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use {
},
utils::{
clonecell::CloneCell,
double_click_state::DoubleClickState,
errorfmt::ErrorFmt,
linkedlist::{LinkedList, LinkedNode, NodeRef},
numcell::NumCell,
Expand Down Expand Up @@ -146,6 +147,7 @@ struct SeatState {
x: i32,
y: i32,
op: Option<SeatOp>,
double_click_state: DoubleClickState,
}

impl ContainerChild {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1176,7 +1179,7 @@ impl Node for ContainerNode {
fn node_on_button(
self: Rc<Self>,
seat: &Rc<WlSeatGlobal>,
_time_usec: u64,
time_usec: u64,
button: u32,
state: KeyState,
_serial: u32,
Expand Down Expand Up @@ -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();
Expand Down
19 changes: 16 additions & 3 deletions src/tree/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -462,7 +464,7 @@ impl Node for FloatNode {
fn node_on_button(
self: Rc<Self>,
seat: &Rc<WlSeatGlobal>,
_time_usec: u64,
time_usec: u64,
button: u32,
state: KeyState,
_serial: u32,
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
35 changes: 35 additions & 0 deletions src/utils/double_click_state.rs
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit 9115919

Please sign in to comment.