From 8cf889a28eff344515eca9fbb71b92f8a8fc1ab1 Mon Sep 17 00:00:00 2001 From: Familex Date: Thu, 26 Dec 2024 01:46:40 +0300 Subject: [PATCH] flick_command MVP; config: flick_command, flick_activation_speed; activation based on last and current motion event --- src/config.rs | 12 +++- src/corner.rs | 157 ++++++++++++++++++++++++++++++++++--------------- src/wayland.rs | 53 ++++++++++++----- 3 files changed, 158 insertions(+), 64 deletions(-) diff --git a/src/config.rs b/src/config.rs index f55791d..f83c7e6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -27,6 +27,10 @@ fn default_timeout_ms() -> u16 { 250 } +fn default_flick_activation_speed() -> f64 { + 3.0 +} + fn default_color() -> u32 { COLOR_RED } @@ -67,6 +71,8 @@ pub struct CornerConfig { pub enter_command: Vec, #[serde(default = "default_command")] pub exit_command: Vec, + #[serde(default = "default_command")] + pub flick_command: Vec, #[serde(default = "default_locations")] pub locations: Vec, #[serde(default = "default_size")] @@ -75,6 +81,8 @@ pub struct CornerConfig { pub margin: i8, #[serde(default = "default_timeout_ms")] pub timeout_ms: u16, + #[serde(default = "default_flick_activation_speed")] + pub flick_activation_speed: f64, #[serde(default = "default_color", deserialize_with = "from_hex")] pub color: u32, } @@ -121,9 +129,9 @@ pub fn get_configs(config_path: PathBuf) -> Result> { toml::from_str::(config_content.as_str()).map(|item| { item.into_iter() .map(|(key, value)| { - if value.enter_command.is_empty() && value.exit_command.is_empty() { + if value.enter_command.is_empty() && value.exit_command.is_empty() && value.flick_command.is_empty() { bail!( - "You must provide either an `exit_command` or an `enter_command` for `{}`", + "You must provide one of `exit_command`, `enter_command`, `flick_command` for `{}`", key ) } diff --git a/src/corner.rs b/src/corner.rs index 817834a..6b5b957 100644 --- a/src/corner.rs +++ b/src/corner.rs @@ -1,6 +1,5 @@ use std::{ borrow::Borrow, - cmp, process::Command, sync::{ mpsc::{channel, Receiver, Sender}, @@ -15,10 +14,33 @@ use tracing::{debug, info}; use crate::config::CornerConfig; +#[derive(Debug, PartialEq)] +pub struct CornerMotionEvent { + pub time: Instant, + pub surface_x: f64, + pub surface_y: f64, +} + #[derive(Debug, PartialEq)] pub enum CornerEvent { Enter, Leave, + Motion(CornerMotionEvent), +} + +#[derive(Debug, PartialEq)] +pub struct FlickInfo { + last_motion_event: Option, + action_was_done: bool, +} + +impl Default for FlickInfo { + fn default() -> Self { + FlickInfo { + last_motion_event: None, + action_was_done: false, + } + } } #[allow(clippy::type_complexity)] @@ -29,6 +51,7 @@ pub struct Corner { Arc>>, Arc>>, ), + flick_info: Arc>, } impl Corner { @@ -37,63 +60,25 @@ impl Corner { Corner { config, channel: (Arc::new(Mutex::new(tx)), Arc::new(Mutex::new(rx))), + flick_info: Default::default(), } } pub fn wait(&self) -> Result<()> { - let timeout = Duration::from_millis(cmp::max(self.config.timeout_ms.into(), 5)); - let mut last_event = None; - let mut command_done_at = None; - loop { - let event_result = self - .channel - .1 - .lock() - .expect("cannot get corner receiver") - .recv_timeout(timeout); - match event_result { - Ok(event) => { - debug!("Received event: {:?}", event); - if command_done_at.map_or(true, |value| { - Instant::now() - .duration_since(value) - .ge(&Duration::from_millis(250)) - }) { - last_event = Some(event); - } else { - debug!("Ignored the event due to too fast after unlock."); - } - } - Err(_error) => { - if let Some(event) = last_event { - if event == CornerEvent::Enter { - self.execute_command(&self.config.enter_command)?; - } else if event == CornerEvent::Leave { - self.execute_command(&self.config.exit_command)?; - } - command_done_at = Some(Instant::now()); - } - last_event = None; - } - } + if self.config.timeout_ms != 0 { + // FIXME current implementation incompatible with flick_command + self.loop_with_timeout() + } else { + self.loop_without_timeout() } } - pub fn on_enter_mouse(&self) -> Result<()> { - self.channel - .0 - .lock() - .expect("Cannot get sender") - .send(CornerEvent::Enter)?; - Ok(()) - } - - pub fn on_leave_mouse(&self) -> Result<()> { + pub fn send_event(&self, event: CornerEvent) -> Result<()> { self.channel .0 .lock() .expect("Cannot get sender") - .send(CornerEvent::Leave)?; + .send(event)?; Ok(()) } @@ -123,4 +108,82 @@ impl Corner { Ok(()) } + + fn execute_event(&self, event: CornerEvent) -> Result<()> { + match event { + CornerEvent::Enter => self.execute_command(&self.config.enter_command), + CornerEvent::Leave => { + *self.flick_info.lock().unwrap() = Default::default(); + self.execute_command(&self.config.exit_command) + } + CornerEvent::Motion { 0: motion_event } => { + let mut flick_info = self.flick_info.lock().expect("Thread error"); + if !flick_info.action_was_done { + if let Some(last_motion_event) = flick_info.last_motion_event.as_ref() { + let delta = (motion_event.time - last_motion_event.time).as_millis() as f64; + let dx = motion_event.surface_x - last_motion_event.surface_x; + let dy = motion_event.surface_y - last_motion_event.surface_y; + let distance = (dx * dx + dy * dy).sqrt(); + let speed = distance / delta; + dbg!(&speed); + if speed >= self.config.flick_activation_speed { + self.execute_command(&self.config.flick_command)?; + (*flick_info).action_was_done = true; + } + } + (*flick_info).last_motion_event = Some(motion_event); + } + Ok(()) + } + } + } + + fn loop_without_timeout(&self) -> Result<()> { + loop { + if let Ok(event) = self + .channel + .1 + .lock() + .expect("cannot get corner receiver") + .recv() + { + self.execute_event(event)?; + } + } + } + + fn loop_with_timeout(&self) -> Result<()> { + let timeout = Duration::from_millis(self.config.timeout_ms.into()); + let mut last_event = None; + let mut command_done_at = None; + loop { + let event_result = self + .channel + .1 + .lock() + .expect("cannot get corner receiver") + .recv_timeout(timeout); + match event_result { + Ok(event) => { + debug!("Received event: {:?}", event); + if command_done_at.map_or(true, |value| { + Instant::now() + .duration_since(value) + .ge(&Duration::from_millis(250)) + }) { + last_event = Some(event); + } else { + debug!("Ignored the event due to too fast after unlock."); + } + } + Err(_error) => { + if let Some(event) = last_event { + self.execute_event(event)?; + command_done_at = Some(Instant::now()); + } + last_event = None; + } + } + } + } } diff --git a/src/wayland.rs b/src/wayland.rs index 79aff58..1e4f1fa 100644 --- a/src/wayland.rs +++ b/src/wayland.rs @@ -1,6 +1,6 @@ use crate::{ config::{self, CornerConfig, Location}, - corner::Corner, + corner::{Corner, CornerEvent, CornerMotionEvent}, }; use anyhow::{Context, Result}; @@ -21,6 +21,7 @@ use std::{ mpsc::{self, Receiver, Sender}, Arc, Mutex, }, + time::Instant, }; use tracing::{debug, info}; @@ -121,21 +122,43 @@ impl Wayland { let pointer_event_receiver = Arc::new(Mutex::new(rx)); thread::scope(|scope| -> Result<()> { - scope.spawn(|_| loop { - let event = pointer_event_receiver - .lock() - .expect("Could not lock event receiver") - .recv(); - match event { - Ok(wl_pointer::Event::Enter { surface, .. }) => { - self.get_corner(&surface) - .and_then(|corner| corner.on_enter_mouse().ok()); - } - Ok(wl_pointer::Event::Leave { surface, .. }) => { - self.get_corner(&surface) - .and_then(|corner| corner.on_leave_mouse().ok()); + scope.spawn(|_| { + let mut hovered_surface = None; + loop { + let event = pointer_event_receiver + .lock() + .expect("Could not lock event receiver") + .recv(); + match event { + Ok(wl_pointer::Event::Enter { surface, .. }) => { + hovered_surface = Some(surface.clone()); + self.get_corner(&surface) + .and_then(|corner| corner.send_event(CornerEvent::Enter).ok()); + } + Ok(wl_pointer::Event::Leave { surface, .. }) => { + hovered_surface = None; + self.get_corner(&surface) + .and_then(|corner| corner.send_event(CornerEvent::Leave).ok()); + } + Ok(wl_pointer::Event::Motion { + time: _time, + surface_x, + surface_y, + }) => { + if let Some(ref hovered_surface) = hovered_surface { + let event = CornerEvent::Motion { + 0: CornerMotionEvent { + time: Instant::now(), + surface_x, + surface_y, + }, + }; + self.get_corner(&hovered_surface) + .and_then(|corner| corner.send_event(event).ok()); + } + } + _ => (), } - _ => (), } });