Skip to content
Closed
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
139 changes: 118 additions & 21 deletions src/main.rs

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to duplicate PR 11

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
use dirs::config_dir;
use input::event::{EventTrait, PointerEvent};
use input::event::keyboard::{KeyState, KeyboardEventTrait};
use input::event::pointer::PointerScrollEvent;
use input::event::PointerEvent;
use input::{Event, Libinput, LibinputInterface};
use input::event::pointer::{ButtonState, PointerScrollEvent};
use input::{Event, Device, Libinput, LibinputInterface};
use libc::{O_RDONLY, O_RDWR, O_WRONLY};
use parser::Keys;
use script_manager::ScriptManager;
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::os::unix::{fs::OpenOptionsExt, io::OwnedFd};
use std::path::Path;
use std::time::{Duration, Instant};
use std::u32;

const SCROLL_HOLD_MS: u64 = 500; // how long a scroll "press" lasts

mod parser;
mod script_manager;

Expand All @@ -35,6 +39,26 @@ impl LibinputInterface for WBindKeysInterface {
}
}

fn convert_button_to_key_state(button_state: ButtonState) -> KeyState {
match button_state {
ButtonState::Pressed => KeyState::Pressed,
ButtonState::Released => KeyState::Released,
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum ScrollDir {
Up,
Down,
Left,
Right,
}

struct ScrollState {
last_time: Instant,
active: bool,
}

fn main() {
let mut input = Libinput::new_with_udev(WBindKeysInterface);
input.udev_assign_seat("seat0").unwrap();
Expand All @@ -55,32 +79,48 @@ fn main() {
script_manager.load_script(&script).unwrap();

let mut active_keys = Vec::new();
let mut key:u32 = 0;
let mut state: KeyState = KeyState::Released;
let mut key_states: HashMap<u32, KeyState> = HashMap::new();
let mut scroll_states: HashMap<ScrollDir, ScrollState> = HashMap::new();

loop {
let mut key: u32 = 0;
let mut state: KeyState = KeyState::Released;

input.dispatch().unwrap();

// --- Handle libinput events ---
for event in &mut input {
let d: Device = event.device();

match event {
Event::Pointer(PointerEvent::Motion(_)) => {} // If event is mouse movement do nothing
Event::Pointer(PointerEvent::Button(mouse_button)) => {
key = mouse_button.button();
state = convert_button_to_key_state(mouse_button.button_state());
}
Event::Pointer(PointerEvent::ScrollWheel(scroll_event)) => {
if scroll_event.has_axis(input::event::pointer::Axis::Vertical) == true {
if scroll_event.scroll_value(input::event::pointer::Axis::Vertical) > 0.0 {
println!("Scroll Down!");
key = 0x999
}else {
println!("Scroll Up!");
key = 0x998
}
} else {
if scroll_event.scroll_value(input::event::pointer::Axis::Horizontal) > 0.0 {
print!("Scroll Right!");
key = 0x997
if let Some((scroll_dir, virtual_key)) = detect_scroll_direction(&scroll_event) {
let now = Instant::now();
let entry = scroll_states.entry(scroll_dir).or_insert(ScrollState {
last_time: now,
active: false,
});

// Only emit "Pressed" if not active or expired
if !entry.active
|| now.duration_since(entry.last_time)
> Duration::from_millis(SCROLL_HOLD_MS)
{
#[cfg(debug_assertions)]
println!("Scroll {:?} => Pressed ({:#03x})", scroll_dir, virtual_key);

entry.active = true;
entry.last_time = now;

key = virtual_key;
state = KeyState::Pressed;
} else {
println!("Scroll Left!");
key = 0x996
// ignore repeated scrolls in the same direction
}
}
}
Expand All @@ -103,15 +143,72 @@ fn main() {
}
_ => {} // Ignore all other events
}
if state == KeyState::Pressed {

// Only trigger on transition: Released → Pressed
let prev_state = key_states.get(&key).copied().unwrap_or(KeyState::Released);
key_states.insert(key, state);

if state == KeyState::Pressed && prev_state == KeyState::Released {
let total_combo = active_keys
.iter()
.chain(std::iter::once(&key))
.copied()
.collect::<Vec<u32>>();

script_manager.handle_action(total_combo, state);
script_manager.handle_action(total_combo, state, d.id_vendor(), d.id_product());
}
}

// --- Handle synthetic scroll releases ---
let now = Instant::now();
for (dir, state_entry) in scroll_states.iter_mut() {
if state_entry.active
&& now.duration_since(state_entry.last_time) > Duration::from_millis(SCROLL_HOLD_MS)
{
let release_key = scroll_dir_to_key(*dir);
let prev_state = key_states.get(&release_key).copied().unwrap_or(KeyState::Released);

if prev_state == KeyState::Pressed {
#[cfg(debug_assertions)]
println!("Scroll {:?} => Released ({:#03x})", dir, release_key);

key_states.insert(release_key, KeyState::Released);
state_entry.active = false;
}
}
}

// small sleep to avoid busy loop (libinput often blocks anyway)
std::thread::sleep(Duration::from_millis(5));
}
}

fn detect_scroll_direction<E>(scroll_event: &E) -> Option<(ScrollDir, u32)>
where
E: PointerScrollEvent,
{
if scroll_event.has_axis(input::event::pointer::Axis::Vertical) {
if scroll_event.scroll_value(input::event::pointer::Axis::Vertical) > 0.0 {
Some((ScrollDir::Down, 0x999))
} else {
Some((ScrollDir::Up, 0x998))
}
} else if scroll_event.has_axis(input::event::pointer::Axis::Horizontal) {
if scroll_event.scroll_value(input::event::pointer::Axis::Horizontal) > 0.0 {
Some((ScrollDir::Right, 0x997))
} else {
Some((ScrollDir::Left, 0x996))
}
} else {
None
}
}

fn scroll_dir_to_key(dir: ScrollDir) -> u32 {
match dir {
ScrollDir::Up => 0x998,
ScrollDir::Down => 0x999,
ScrollDir::Left => 0x996,
ScrollDir::Right => 0x997,
}
}
23 changes: 16 additions & 7 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ pub enum Keys {
Mouse3 = 0x112,
Mouse4 = 0x113,
Mouse5 = 0x114,
Mouse6 = 0x115,
Mouse7 = 0x116,
Mouse8 = 0x117,
Mouse9 = 0x118,
Mouse10 = 0x119,
ScrollLeft = 0x996,
ScrollRight = 0x997,
ScrollUp = 0x998,
Expand Down Expand Up @@ -186,11 +191,16 @@ pub fn parse_binding(binding: &str) -> Vec<u32> {
"Mouse2"=> keys.push(Keys::Mouse2 as u32),
"Mouse3"=> keys.push(Keys::Mouse3 as u32),
"Mouse4"=> keys.push(Keys::Mouse4 as u32),
"Mouse5"=> keys.push(Keys::Mouse5 as u32),
"ScrollLeft"=> keys.push(Keys::ScrollLeft as u32),
"ScrollRight"=> keys.push(Keys::ScrollRight as u32),
"ScrollUp"=> keys.push(Keys::ScrollUp as u32),
"ScrollDown"=> keys.push(Keys::ScrollDown as u32),
"Mouse5"=> keys.push(Keys::Mouse5 as u32),
"Mouse6"=> keys.push(Keys::Mouse6 as u32),
"Mouse7"=> keys.push(Keys::Mouse7 as u32),
"Mouse8"=> keys.push(Keys::Mouse8 as u32),
"Mouse9"=> keys.push(Keys::Mouse9 as u32),
"Mouse10"=> keys.push(Keys::Mouse10 as u32),
"ScrollLeft"=> keys.push(Keys::ScrollLeft as u32),
"ScrollRight"=> keys.push(Keys::ScrollRight as u32),
"ScrollUp"=> keys.push(Keys::ScrollUp as u32),
"ScrollDown"=> keys.push(Keys::ScrollDown as u32),
_ => {}
}
}
Expand Down Expand Up @@ -287,11 +297,10 @@ mod tests {
("F11", Keys::F11 as u32),
("F12", Keys::F12 as u32),
("F12", Keys::F12 as u32),

];

for (input, expected_output) in test_cases {
assert_eq!(parse_binding(input), vec![expected_output], "Failed for input: {}", input);
}
}
}
}
Loading