Skip to content

Commit

Permalink
Add distance check for double-tap
Browse files Browse the repository at this point in the history
Previously only the time between taps was considered when trying to
detect double-taps. As a result the gesture handle could be tapped on
both ends and the last tap would act as a double-tap to cycle in that
direction.

Instead we now check if the double-tap is within a certain distance of
the original tap, behaving more consistently with what a double-tap is
commonly expected to be

Additionally a small bug was fixed where it was possible that the tap
after a double-tap would be recognized as a double-tap, since the
double-tap itself was considered the start of a new tap sequence.

There's also been a minor refactor of the `set_sleep` methods used to
control display power, since they are completely unrelated to sleep.
  • Loading branch information
chrisduerr committed Nov 2, 2024
1 parent 7684585 commit 3d04f9e
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 22 deletions.
16 changes: 8 additions & 8 deletions src/catacomb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ pub struct Catacomb {
pub touch_state: TouchState,
pub frame_pacer: FramePacer,
pub seat_name: String,
pub display_on: bool,
pub windows: Windows,
pub seat: Seat<Self>,
pub terminated: bool,
pub sleeping: bool,
pub backend: Udev,

// Smithay state.
Expand Down Expand Up @@ -317,13 +317,13 @@ impl Catacomb {
backend,
seat,
accelerometer_token: accel_token,
display_on: true,
idle_inhibitors: Default::default(),
key_bindings: Default::default(),
ime_override: Default::default(),
frame_pacer: Default::default(),
last_focus: Default::default(),
terminated: Default::default(),
sleeping: Default::default(),
stalled: Default::default(),
locker: Default::default(),
}
Expand All @@ -341,7 +341,7 @@ impl Catacomb {
#[cfg_attr(feature = "profiling", profiling::function)]
pub fn create_frame(&mut self) {
// Skip rendering while the screen is off.
if self.sleeping {
if !self.display_on {
self.stalled = true;
return;
}
Expand Down Expand Up @@ -442,11 +442,11 @@ impl Catacomb {
}

/// Set output power mode.
pub fn set_sleep(&mut self, sleep: bool) {
self.sleeping = sleep;
pub fn set_display_status(&mut self, on: bool) {
self.display_on = on;

// Pause accelerometer checks during sleep.
if sleep {
// Pause accelerometer checks while display is off.
if on {
trace_error!(self.event_loop.disable(&self.accelerometer_token));
} else if !self.windows.orientation_locked() {
trace_error!(self.event_loop.enable(&self.accelerometer_token));
Expand All @@ -456,7 +456,7 @@ impl Catacomb {
self.idle_notifier_state.notify_activity(&self.seat);
}

self.backend.set_sleep(sleep);
self.backend.set_display_status(on);
}

/// Lock the output's orientation.
Expand Down
30 changes: 22 additions & 8 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub const HOLD_DURATION: Duration = Duration::from_secs(1);
/// Maximum time between taps to be considered a double-tap.
const MAX_DOUBLE_TAP_DURATION: Duration = Duration::from_millis(300);

/// Square of the maximum distance between taps to be considered a double-tap.
const MAX_DOUBLE_TAP_DISTANCE: f64 = 400.;

/// Square of the maximum distance before touch input is considered a drag.
const MAX_TAP_DISTANCE: f64 = 400.;

Expand All @@ -47,6 +50,7 @@ pub struct TouchState {
pub user_gestures: Vec<GestureBinding>,
pub position: Point<f64, Logical>,

last_tap: Option<(Instant, Point<f64, Logical>)>,
pending_single_tap: Option<RegistrationToken>,
event_loop: LoopHandle<'static, Catacomb>,
velocity_timer: Option<RegistrationToken>,
Expand All @@ -57,21 +61,20 @@ pub struct TouchState {
events: Vec<TouchEvent>,
slot: Option<TouchSlot>,
start: TouchStart,
last_tap: Instant,
is_drag: bool,
}

impl TouchState {
pub fn new(event_loop: LoopHandle<'static, Catacomb>) -> Self {
Self {
event_loop,
last_tap: Instant::now(),
pending_single_tap: Default::default(),
velocity_timer: Default::default(),
input_surface: Default::default(),
user_gestures: Default::default(),
active_app_id: Default::default(),
tap_surface: Default::default(),
last_tap: Default::default(),
position: Default::default(),
velocity: Default::default(),
is_drag: Default::default(),
Expand Down Expand Up @@ -162,11 +165,22 @@ impl TouchState {
return Some(TouchAction::Drag);
}

if self.last_tap.elapsed() <= MAX_DOUBLE_TAP_DURATION {
(!touching).then_some(TouchAction::DoubleTap)
} else {
(!touching).then_some(TouchAction::Tap)
// Only report taps after release.
if touching {
return None;
}

// Report double-taps.
if let Some(last_tap) = self.last_tap.take() {
let last_tap_delta = last_tap.1 - self.position;
if last_tap.0.elapsed() <= MAX_DOUBLE_TAP_DURATION
&& last_tap_delta.x.powi(2) + last_tap_delta.y.powi(2) <= MAX_DOUBLE_TAP_DISTANCE
{
return Some(TouchAction::DoubleTap);
}
}

Some(TouchAction::Tap)
}

/// Find gestures matching an origin point.
Expand Down Expand Up @@ -327,7 +341,7 @@ impl Catacomb {
/// Process new input events.
pub fn handle_input<I: InputBackend>(&mut self, event: InputEvent<I>) {
// Ignore non-keyboard input events while the screen is off.
if self.sleeping && !matches!(event, InputEvent::Keyboard { .. }) {
if !self.display_on && !matches!(event, InputEvent::Keyboard { .. }) {
return;
}

Expand Down Expand Up @@ -480,7 +494,7 @@ impl Catacomb {
let pending_single_tap =
self.event_loop.insert_source(timer, Self::on_single_tap).unwrap();
self.touch_state.pending_single_tap = Some(pending_single_tap);
self.touch_state.last_tap = Instant::now();
self.touch_state.last_tap = Some((Instant::now(), self.touch_state.position));
},
Some(TouchAction::DoubleTap) => {
// Cancel single-tap.
Expand Down
6 changes: 4 additions & 2 deletions src/ipc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,11 @@ fn handle_message(buffer: &mut String, mut stream: UnixStream, catacomb: &mut Ca
binding.app_id.base() != app_id || binding.key != key || binding.mods != mods
});
},
IpcMessage::Dpms { state: Some(state) } => catacomb.set_sleep(state == DpmsState::Off),
IpcMessage::Dpms { state: Some(state) } => {
catacomb.set_display_status(state == DpmsState::On)
},
IpcMessage::Dpms { state: None } => {
let state = if catacomb.sleeping { DpmsState::Off } else { DpmsState::On };
let state = if catacomb.display_on { DpmsState::On } else { DpmsState::Off };
send_reply(&mut stream, &IpcMessage::DpmsReply { state });
},
// Ignore IPC replies.
Expand Down
8 changes: 4 additions & 4 deletions src/udev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,17 +222,17 @@ impl Udev {
trace_error!(self.session.change_vt(vt));
}

/// Set power saving state.
pub fn set_sleep(&mut self, sleep: bool) {
/// Set output power saving state.
pub fn set_display_status(&mut self, on: bool) {
let output_device = match &mut self.output_device {
Some(output_device) => output_device,
None => return,
};

output_device.set_enabled(!sleep);
output_device.set_enabled(on);

// Request immediate redraw, so vblanks start coming in again.
if !sleep {
if on {
self.schedule_redraw(Duration::ZERO);
}
}
Expand Down

0 comments on commit 3d04f9e

Please sign in to comment.