Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Activation based on cursor speed #29

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ fn default_timeout_ms() -> u16 {
250
}

fn default_flick_activation_speed() -> f64 {
3.0
}

fn default_color() -> u32 {
COLOR_RED
}
Expand Down Expand Up @@ -67,6 +71,8 @@ pub struct CornerConfig {
pub enter_command: Vec<String>,
#[serde(default = "default_command")]
pub exit_command: Vec<String>,
#[serde(default = "default_command")]
pub flick_command: Vec<String>,
#[serde(default = "default_locations")]
pub locations: Vec<Location>,
#[serde(default = "default_size")]
Expand All @@ -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,
}
Expand Down Expand Up @@ -121,9 +129,9 @@ pub fn get_configs(config_path: PathBuf) -> Result<Vec<CornerConfig>> {
toml::from_str::<Config>(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
)
}
Expand Down
157 changes: 110 additions & 47 deletions src/corner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::{
borrow::Borrow,
cmp,
process::Command,
sync::{
mpsc::{channel, Receiver, Sender},
Expand All @@ -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<CornerMotionEvent>,
action_was_done: bool,
}

impl Default for FlickInfo {
fn default() -> Self {
FlickInfo {
last_motion_event: None,
action_was_done: false,
}
}
}

#[allow(clippy::type_complexity)]
Expand All @@ -29,6 +51,7 @@ pub struct Corner {
Arc<Mutex<Sender<CornerEvent>>>,
Arc<Mutex<Receiver<CornerEvent>>>,
),
flick_info: Arc<Mutex<FlickInfo>>,
}

impl Corner {
Expand All @@ -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(())
}

Expand Down Expand Up @@ -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;
}
}
}
}
}
53 changes: 38 additions & 15 deletions src/wayland.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
config::{self, CornerConfig, Location},
corner::Corner,
corner::{Corner, CornerEvent, CornerMotionEvent},
};
use anyhow::{Context, Result};

Expand All @@ -21,6 +21,7 @@ use std::{
mpsc::{self, Receiver, Sender},
Arc, Mutex,
},
time::Instant,
};
use tracing::{debug, info};

Expand Down Expand Up @@ -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());
}
}
_ => (),
}
_ => (),
}
});

Expand Down