From b8116e69d7e8a3fc356b55771ef3b7a400b542ec Mon Sep 17 00:00:00 2001 From: Tuxinal <24763016+tuxinal@users.noreply.github.com> Date: Thu, 6 Feb 2025 11:50:18 +0330 Subject: [PATCH] add keyup and keydown events make sure keybind triggers don't repeat on libuiohook backends --- lib/venbind.d.ts | 2 +- src/js.rs | 25 +++++++++++++++------- src/lib.rs | 2 +- src/linux.rs | 55 +++++++++++++++++++++++++++++++++++++----------- src/structs.rs | 4 ++-- src/windows.rs | 29 ++++++++++++++++++++++--- 6 files changed, 90 insertions(+), 27 deletions(-) diff --git a/lib/venbind.d.ts b/lib/venbind.d.ts index 35e7074..d14dc25 100644 --- a/lib/venbind.d.ts +++ b/lib/venbind.d.ts @@ -1,5 +1,5 @@ export class Venbind { - startKeybinds(callback: (err: null | Error, id: number) => void): void; + startKeybinds(callback: (id: number, keyup: boolean) => void): void; registerKeybind(keybind: string, keybindId: number): void; unregisterKeybind(keybindId: number): void; preregisterKeybinds(actions: PreRegisterAction[]): void; diff --git a/src/js.rs b/src/js.rs index 793da5d..6097c0a 100644 --- a/src/js.rs +++ b/src/js.rs @@ -2,7 +2,9 @@ use std::{sync::mpsc::channel, thread}; use napi::{ bindgen_prelude::*, - threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}, + threadsafe_function::{ + ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode, + }, }; use napi_derive::napi; @@ -10,25 +12,30 @@ use crate::structs::{KeybindId, KeybindTrigger}; pub use crate::structs::PreRegisterAction; -#[napi(ts_args_type = "callback: (err: null | Error, id: number) => void")] +#[napi(ts_args_type = "callback: (id: number, keyup: boolean) => void")] pub fn start_keybinds(callback: JsFunction) -> Result<()> { let (tx, rx) = channel::(); thread::spawn(|| { crate::start_keybinds(tx); }); - - let thread_function: ThreadsafeFunction = callback - .create_threadsafe_function(0, |ctx| ctx.env.create_uint32(ctx.value).map(|v| vec![v]))?; +let thread_function: ThreadsafeFunction<(u32, bool), ErrorStrategy::Fatal> = callback + .create_threadsafe_function(0, |ctx: ThreadSafeCallContext<(u32, bool)>| { + ctx.env.create_uint32(ctx.value.0).and_then(|y| { + ctx.env + .get_boolean(ctx.value.1) + .and_then(|x| (y, x).into_vec(ctx.env.raw())) + }) + })?; thread::spawn(move || loop { match rx.recv() { Err(err) => { panic!("{err}"); } Ok(KeybindTrigger::Pressed(x)) => { - thread_function.call(x, ThreadsafeFunctionCallMode::Blocking); + thread_function.call((x, false), ThreadsafeFunctionCallMode::Blocking); } Ok(KeybindTrigger::Released(x)) => { - println!("released {}", x); + thread_function.call((x, true), ThreadsafeFunctionCallMode::Blocking); } } }); @@ -47,7 +54,9 @@ pub fn unregister_keybind(#[napi(ts_arg_type = "number")] id: KeybindId) { } #[napi] -pub fn preregister_keybinds(#[napi(ts_arg_type = "PreRegisterAction[]")] actions: Vec) { +pub fn preregister_keybinds( + #[napi(ts_arg_type = "PreRegisterAction[]")] actions: Vec, +) { #[cfg(target_os = "linux")] { crate::platform::xdg_preregister_keybinds(actions).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index e50283b..a264cd4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ mod tests { thread::spawn(|| { start_keybinds(tx); }); - thread::sleep(std::time::Duration::from_secs(2)); + // thread::sleep(std::time::Duration::from_secs(2)); #[cfg(target_os = "linux")] if crate::is_wayland() || crate::use_xdg_on_x11() { crate::xdg_preregister_keybinds(vec![ diff --git a/src/linux.rs b/src/linux.rs index 33ec746..78e6fa7 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -9,8 +9,8 @@ use std::{ sync::{mpsc::Sender, LazyLock, Mutex, OnceLock}, }; use uiohook_sys::{ - _event_type_EVENT_KEY_PRESSED, _uiohook_event, hook_run, hook_set_dispatch_proc, - UIOHOOK_SUCCESS, + _event_type_EVENT_KEY_PRESSED, _event_type_EVENT_KEY_RELEASED, _uiohook_event, hook_run, + hook_set_dispatch_proc, UIOHOOK_SUCCESS, }; use xcb::Extension; use xkbcommon::xkb::{self, State}; @@ -22,6 +22,8 @@ use crate::{ }; static KEYBINDS: LazyLock> = LazyLock::new(|| Mutex::new(Keybinds::default())); +static CURR_DOWN: LazyLock>> = + LazyLock::new(|| Mutex::new(None)); static TX: OnceLock> = OnceLock::new(); static XDG_STATE: LazyLock>> = LazyLock::new(|| Mutex::new(None)); @@ -58,11 +60,12 @@ pub(crate) fn unregister_keybind_internal(id: KeybindId) -> Result<()> { } async fn xdg_start_keybinds() -> Result<()> { + let mut state = XDG_STATE.lock().unwrap(); + let portal = GlobalShortcuts::new().await?; let session = portal.create_session().await?; - let mut state = XDG_STATE.lock().unwrap(); - let _ = state.replace(XDGState { portal, session }); + state.replace(XDGState { portal, session }); drop(state); xdg_input_thread().await; @@ -109,13 +112,19 @@ pub(crate) fn xdg_preregister_keybinds(actions: Vec) -> Resul .iter() .map(|x| NewShortcut::new(format!("{}", x.id), x.name.clone())) .collect(); - let lock = XDG_STATE.lock().unwrap(); - let state = lock.as_ref().unwrap(); - futures::executor::block_on( - state - .portal - .bind_shortcuts(&state.session, &shortcuts, None), - )?; + loop { + let lock = XDG_STATE.lock().unwrap(); + if let Some(state) = lock.as_ref() { + futures::executor::block_on(state.portal.bind_shortcuts( + &state.session, + &shortcuts, + None, + ))?; + break; + } else { + continue; + } + } Ok(()) } @@ -137,14 +146,36 @@ pub unsafe extern "C" fn uiohook_dispatch_proc(event_ref: *mut _uiohook_event) { ctrl, character: if !key.is_empty() { Some(key) } else { None }, }; + let mut down = CURR_DOWN.lock().unwrap(); + if let Some((down_keybind, id)) = &*down { + if *down_keybind == keybind { + return; // prevent repeating Pressed triggers + } + TX.get() + .unwrap() + .send(KeybindTrigger::Released(*id)) + .unwrap(); + down.take(); + } + let keybinds = KEYBINDS.lock(); - if let Some(id) = keybinds.unwrap().get_keybind_id(keybind) { + if let Some(id) = keybinds.unwrap().get_keybind_id(&keybind) { TX.get().unwrap().send(KeybindTrigger::Pressed(id)).unwrap(); + down.replace((keybind, id)); } } else { panic!("The state is gone???? how????"); } }); + } else if event.type_ == _event_type_EVENT_KEY_RELEASED { + let mut down = CURR_DOWN.lock().unwrap(); + if let Some((_, id)) = &*down { + TX.get() + .unwrap() + .send(KeybindTrigger::Released(*id)) + .unwrap(); + down.take(); + } } } diff --git a/src/structs.rs b/src/structs.rs index f53851a..cd5ce7b 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -79,7 +79,7 @@ impl Keybinds { pub fn unregister_keybind(&mut self, id: KeybindId) { self.keybinds.retain(|_, x| *x != id); } - pub fn get_keybind_id(&self, keybind: Keybind) -> Option { - self.keybinds.get(&keybind).copied() + pub fn get_keybind_id(&self, keybind: &Keybind) -> Option { + self.keybinds.get(keybind).copied() } } diff --git a/src/windows.rs b/src/windows.rs index 816bbc2..92abdbd 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -4,14 +4,16 @@ use std::sync::{mpsc::Sender, Mutex}; use std::sync::{LazyLock, OnceLock}; use uiohook_sys::{ - _event_type_EVENT_KEY_PRESSED, _uiohook_event, hook_run, hook_set_dispatch_proc, - UIOHOOK_SUCCESS, + _event_type_EVENT_KEY_PRESSED, _event_type_EVENT_KEY_RELEASED, _uiohook_event, hook_run, + hook_set_dispatch_proc, UIOHOOK_SUCCESS, }; use crate::errors::{Result, VenbindError}; use crate::structs::{Keybind, KeybindId, KeybindTrigger, Keybinds}; static KEYBINDS: LazyLock> = LazyLock::new(|| Mutex::new(Keybinds::default())); +static CURR_DOWN: LazyLock>> = + LazyLock::new(|| Mutex::new(None)); static TX: OnceLock> = OnceLock::new(); pub(crate) fn start_keybinds_internal(tx: Sender) -> Result<()> { @@ -55,10 +57,31 @@ pub unsafe extern "C" fn dispatch_proc(event_ref: *mut _uiohook_event) { None }, }; + let mut down = CURR_DOWN.lock().unwrap(); + if let Some((down_keybind, id)) = &*down { + if *down_keybind == keybind { + return; // prevent repeating Pressed triggers + } + TX.get() + .unwrap() + .send(KeybindTrigger::Released(*id)) + .unwrap(); + down.take(); + } let keybinds = KEYBINDS.lock(); - if let Some(id) = keybinds.unwrap().get_keybind_id(keybind) { + if let Some(id) = keybinds.unwrap().get_keybind_id(&keybind) { TX.get().unwrap().send(KeybindTrigger::Pressed(id)).unwrap(); + down.replace((keybind, id)); + } + } else if event.type_ == _event_type_EVENT_KEY_RELEASED { + let mut down = CURR_DOWN.lock().unwrap(); + if let Some((_, id)) = &*down { + TX.get() + .unwrap() + .send(KeybindTrigger::Released(*id)) + .unwrap(); + down.take(); } } }