Skip to content

Commit

Permalink
feat: add movemouse-smooth-diagonals (#573)
Browse files Browse the repository at this point in the history
Co-authored-by: rszyma <rszyma.dev@gmail.com>
  • Loading branch information
jtroo and rszyma authored Oct 7, 2023
1 parent 5abe16b commit ef23c68
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 37 deletions.
7 changes: 7 additions & 0 deletions cfg_samples/kanata.kbd
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,13 @@ If you need help, please feel welcome to ask in the GitHub discussions.
;;
;; movemouse-inherit-accel-state yes

;; This config entry alters the behavior of movemouseaccel actions.
;; This makes diagonal movements simultaneous to mitigate choppiness in
;; drawing apps, if you're using kanata mouse movements to draw for
;; whatever reason.
;;
;; movemouse-smooth-diagonals yes

)

;; deflocalkeys-* enables you to define and use key names that match your locale
Expand Down
22 changes: 21 additions & 1 deletion docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,25 @@ movement actions will be inherited if others are already being pressed.
)
----

[[movemouse-smooth-diagonals]]
== movemouse-smooth-diagonals
<<table-of-contents,Back to ToC>>

By default, mouse movements move one direction at a time
and vertical/horizontal movements are on independent timers.

This can result in non-smooth diagonals when drawing a line in some app.
This option adds a small imperceptible amount of latency to
synchronize the mouse movements.

.Example:
[source]
----
(defcfg
movemouse-smooth-diagonals yes
)
----

[[linux-only-linux-dev]]
=== Linux only: linux-dev
<<table-of-contents,Back to ToC>>
Expand Down Expand Up @@ -657,6 +676,8 @@ a non-applicable operating system.
sequence-backtrack-modcancel no
log-layer-changes no
delegate-to-first-layer yes
movemouse-inherit-accel-state yes
movemouse-smooth-diagonals yes
linux-dev /dev/input/dev1:/dev/input/dev2
linux-dev-names-include "Name 1:Name 2"
linux-dev-names-exclude "Name 3:Name 4"
Expand All @@ -666,7 +687,6 @@ a non-applicable operating system.
linux-x11-repeat-delay-rate 400,50
windows-altgr add-lctl-release
windows-interception-mouse-hwid "70, 0, 60, 0"
movemouse-inherit-accel-state yes
)
----

Expand Down
1 change: 1 addition & 0 deletions parser/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ fn parse_defcfg(expr: &[SExpr]) -> Result<HashMap<String, String>> {
"log-layer-changes",
"delegate-to-first-layer",
"linux-continue-if-no-devs-found",
"movemouse-smooth-diagonals",
"movemouse-inherit-accel-state",
];
let mut cfg = HashMap::default();
Expand Down
120 changes: 95 additions & 25 deletions src/kanata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,34 @@ pub struct Kanata {
/// If a mousemove action is active and another mousemove action is activated,
/// reuse the acceleration state.
movemouse_inherit_accel_state: bool,
/// Removes jaggedneess of vertical and horizontal mouse movements when used
/// simultaneously at the cost of increased mousemove actions latency.
movemouse_smooth_diagonals: bool,
/// If movemouse_smooth_diagonals is enabled, the previous mouse actions
/// gets stored in this buffer and if the next movemouse action is opposite axis
/// than the one stored in the buffer, both events are outputted at the same time.
movemouse_buffer: Option<(Axis, CalculatedMouseMove)>,
}

#[derive(PartialEq, Clone, Copy)]
pub enum Axis {
Vertical,
Horizontal,
}

impl From<MoveDirection> for Axis {
fn from(val: MoveDirection) -> Axis {
match val {
MoveDirection::Up | MoveDirection::Down => Axis::Vertical,
MoveDirection::Left | MoveDirection::Right => Axis::Horizontal,
}
}
}

#[derive(Clone, Copy)]
pub struct CalculatedMouseMove {
pub direction: MoveDirection,
pub distance: u16,
}

pub struct ScrollState {
Expand Down Expand Up @@ -349,6 +377,11 @@ impl Kanata {
dynamic_macros: Default::default(),
log_layer_changes,
caps_word: None,
movemouse_smooth_diagonals: cfg
.items
.get("movemouse-smooth-diagonals")
.map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str()))
.unwrap_or_default(),
movemouse_inherit_accel_state: cfg
.items
.get("movemouse-inherit-accel-state")
Expand All @@ -358,6 +391,7 @@ impl Kanata {
defcfg_items: cfg.items,
waiting_for_idle: HashSet::default(),
ticks_since_idle: 0,
movemouse_buffer: None,
})
}

Expand Down Expand Up @@ -392,6 +426,11 @@ impl Kanata {
self.sequences = cfg.sequences;
self.overrides = cfg.overrides;
self.log_layer_changes = log_layer_changes;
self.movemouse_smooth_diagonals = cfg
.items
.get("movemouse-smooth-diagonals")
.map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str()))
.unwrap_or_default();
self.movemouse_inherit_accel_state = cfg
.items
.get("movemouse-inherit-accel-state")
Expand Down Expand Up @@ -539,7 +578,32 @@ impl Kanata {
let scaled_distance =
apply_mouse_distance_modifiers(mmsv.distance, &self.move_mouse_speed_modifiers);
log::debug!("handle_move_mouse: scaled vdistance: {}", scaled_distance);
self.kbd_out.move_mouse(mmsv.direction, scaled_distance)?;

let current_move = CalculatedMouseMove {
direction: mmsv.direction,
distance: scaled_distance,
};

if self.movemouse_smooth_diagonals {
let axis: Axis = current_move.direction.into();
match &self.movemouse_buffer {
Some((previous_axis, previous_move)) => {
if axis == *previous_axis {
self.kbd_out.move_mouse(*previous_move)?;
self.movemouse_buffer = Some((axis, current_move));
} else {
self.kbd_out
.move_mouse_many(&[*previous_move, current_move])?;
self.movemouse_buffer = None;
}
}
None => {
self.movemouse_buffer = Some((axis, current_move));
}
}
} else {
self.kbd_out.move_mouse(current_move)?;
}
} else {
mmsv.ticks_until_move -= 1;
}
Expand All @@ -561,7 +625,32 @@ impl Kanata {
let scaled_distance =
apply_mouse_distance_modifiers(mmsh.distance, &self.move_mouse_speed_modifiers);
log::debug!("handle_move_mouse: scaled hdistance: {}", scaled_distance);
self.kbd_out.move_mouse(mmsh.direction, scaled_distance)?;

let current_move = CalculatedMouseMove {
direction: mmsh.direction,
distance: scaled_distance,
};

if self.movemouse_smooth_diagonals {
let axis: Axis = current_move.direction.into();
match &self.movemouse_buffer {
Some((previous_axis, previous_move)) => {
if axis == *previous_axis {
self.kbd_out.move_mouse(*previous_move)?;
self.movemouse_buffer = Some((axis, current_move));
} else {
self.kbd_out
.move_mouse_many(&[*previous_move, current_move])?;
self.movemouse_buffer = None;
}
}
None => {
self.movemouse_buffer = Some((axis, current_move));
}
}
} else {
self.kbd_out.move_mouse(current_move)?;
}
} else {
mmsh.ticks_until_move -= 1;
}
Expand Down Expand Up @@ -1217,7 +1306,8 @@ impl Kanata {
}
pbtn
}
CustomAction::MoveMouse { direction, .. } => {
CustomAction::MoveMouse { direction, .. }
| CustomAction::MoveMouseAccel { direction, .. } => {
match direction {
MoveDirection::Up | MoveDirection::Down => {
if let Some(move_mouse_state_vertical) =
Expand All @@ -1238,28 +1328,8 @@ impl Kanata {
}
}
}
pbtn
}
CustomAction::MoveMouseAccel { direction, .. } => {
match direction {
MoveDirection::Up | MoveDirection::Down => {
if let Some(move_mouse_state_vertical) =
&self.move_mouse_state_vertical
{
if move_mouse_state_vertical.direction == *direction {
self.move_mouse_state_vertical = None;
}
}
}
MoveDirection::Left | MoveDirection::Right => {
if let Some(move_mouse_state_horizontal) =
&self.move_mouse_state_horizontal
{
if move_mouse_state_horizontal.direction == *direction {
self.move_mouse_state_horizontal = None;
}
}
}
if self.movemouse_smooth_diagonals {
self.movemouse_buffer = None
}
pbtn
}
Expand Down
28 changes: 21 additions & 7 deletions src/oskbd/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use std::path::PathBuf;
use std::thread;

use super::*;
use crate::oskbd::KeyEvent;
use crate::{kanata::CalculatedMouseMove, oskbd::KeyEvent};
use kanata_parser::custom_action::*;
use kanata_parser::keys::*;

Expand Down Expand Up @@ -540,16 +540,30 @@ impl KbdOut {
}
}

pub fn move_mouse(&mut self, direction: MoveDirection, distance: u16) -> Result<(), io::Error> {
let (axis, distance) = match direction {
MoveDirection::Up => (RelativeAxisType::REL_Y, -i32::from(distance)),
MoveDirection::Down => (RelativeAxisType::REL_Y, i32::from(distance)),
MoveDirection::Left => (RelativeAxisType::REL_X, -i32::from(distance)),
MoveDirection::Right => (RelativeAxisType::REL_X, i32::from(distance)),
pub fn move_mouse(&mut self, mv: CalculatedMouseMove) -> Result<(), io::Error> {
let (axis, distance) = match mv.direction {
MoveDirection::Up => (RelativeAxisType::REL_Y, -i32::from(mv.distance)),
MoveDirection::Down => (RelativeAxisType::REL_Y, i32::from(mv.distance)),
MoveDirection::Left => (RelativeAxisType::REL_X, -i32::from(mv.distance)),
MoveDirection::Right => (RelativeAxisType::REL_X, i32::from(mv.distance)),
};
self.write(InputEvent::new(EventType::RELATIVE, axis.0, distance))
}

pub fn move_mouse_many(&mut self, moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {
let mut events = vec![];
for mv in moves {
let (axis, distance) = match mv.direction {
MoveDirection::Up => (RelativeAxisType::REL_Y, -i32::from(mv.distance)),
MoveDirection::Down => (RelativeAxisType::REL_Y, i32::from(mv.distance)),
MoveDirection::Left => (RelativeAxisType::REL_X, -i32::from(mv.distance)),
MoveDirection::Right => (RelativeAxisType::REL_X, i32::from(mv.distance)),
};
events.push(InputEvent::new(EventType::RELATIVE, axis.0, distance));
}
self.write_many(&events)
}

pub fn set_mouse(&mut self, _x: u16, _y: u16) -> Result<(), io::Error> {
log::warn!("setmouse does not work in Linux yet. Maybe try out warpd:\n\thttps://github.com/rvaiya/warpd");
Ok(())
Expand Down
33 changes: 31 additions & 2 deletions src/oskbd/windows/interception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::io;
use kanata_interception::{Interception, KeyState, MouseFlags, MouseState, Stroke};

use super::OsCodeWrapper;
use crate::kanata::CalculatedMouseMove;
use crate::oskbd::KeyValue;
use kanata_parser::custom_action::*;
use kanata_parser::keys::*;
Expand Down Expand Up @@ -94,6 +95,29 @@ impl InputEvent {
})
}

fn from_mouse_move_many(moves: &[CalculatedMouseMove]) -> Self {
let mut x_acc = 0;
let mut y_acc = 0;
for mov in moves {
let acc_change = match mov.direction {
MoveDirection::Up => (0, -i32::from(mov.distance)),
MoveDirection::Down => (0, i32::from(mov.distance)),
MoveDirection::Left => (-i32::from(mov.distance), 0),
MoveDirection::Right => (i32::from(mov.distance), 0),
};
x_acc += acc_change.0;
y_acc += acc_change.1;
}
Self(Stroke::Mouse {
state: MouseState::MOVE,
flags: MouseFlags::empty(),
rolling: 0,
x: x_acc,
y: y_acc,
information: 0,
})
}

fn from_mouse_set(x: u16, y: u16) -> Self {
Self(Stroke::Mouse {
state: MouseState::MOVE,
Expand Down Expand Up @@ -183,8 +207,13 @@ impl KbdOut {
Ok(())
}

pub fn move_mouse(&mut self, direction: MoveDirection, distance: u16) -> Result<(), io::Error> {
write_interception(InputEvent::from_mouse_move(direction, distance));
pub fn move_mouse(&mut self, mv: CalculatedMouseMove) -> Result<(), io::Error> {
write_interception(InputEvent::from_mouse_move(mv.direction, mv.distance));
Ok(())
}

pub fn move_mouse_many(&mut self, moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {
write_interception(InputEvent::from_mouse_move_many(moves));
Ok(())
}

Expand Down
26 changes: 24 additions & 2 deletions src/oskbd/windows/llhook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use winapi::shared::minwindef::*;
use winapi::shared::windef::*;
use winapi::um::winuser::*;

use crate::kanata::CalculatedMouseMove;
use crate::oskbd::{KeyEvent, KeyValue};
use kanata_parser::custom_action::*;
use kanata_parser::keys::*;
Expand Down Expand Up @@ -216,8 +217,13 @@ impl KbdOut {
Ok(())
}

pub fn move_mouse(&mut self, direction: MoveDirection, distance: u16) -> Result<(), io::Error> {
move_mouse(direction, distance);
pub fn move_mouse(&mut self, mv: CalculatedMouseMove) -> Result<(), io::Error> {
move_mouse(mv.direction, mv.distance);
Ok(())
}

pub fn move_mouse_many(&mut self, moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {
move_mouse_many(moves);
Ok(())
}

Expand Down Expand Up @@ -303,6 +309,22 @@ fn move_mouse(direction: MoveDirection, distance: u16) {
}
}

fn move_mouse_many(moves: &[CalculatedMouseMove]) {
let mut x_acc = 0;
let mut y_acc = 0;
for mov in moves {
let acc_change = match mov.direction {
MoveDirection::Up => (0, -i32::from(mov.distance)),
MoveDirection::Down => (0, i32::from(mov.distance)),
MoveDirection::Left => (-i32::from(mov.distance), 0),
MoveDirection::Right => (i32::from(mov.distance), 0),
};
x_acc += acc_change.0;
y_acc += acc_change.1;
}
move_mouse_xy(x_acc, y_acc);
}

fn move_mouse_xy(x: i32, y: i32) {
mouse_event(MOUSEEVENTF_MOVE, 0, x, y);
}
Expand Down

0 comments on commit ef23c68

Please sign in to comment.