Skip to content

Commit

Permalink
add keyup and keydown events
Browse files Browse the repository at this point in the history
make sure keybind triggers don't repeat on libuiohook backends
  • Loading branch information
tuxinal committed Feb 6, 2025
1 parent 48ac5d2 commit b8116e6
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 27 deletions.
2 changes: 1 addition & 1 deletion lib/venbind.d.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
25 changes: 17 additions & 8 deletions src/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,40 @@ 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;

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::<KeybindTrigger>();
thread::spawn(|| {
crate::start_keybinds(tx);
});

let thread_function: ThreadsafeFunction<u32, ErrorStrategy::Fatal> = 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);
}
}
});
Expand All @@ -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<PreRegisterAction>) {
pub fn preregister_keybinds(
#[napi(ts_arg_type = "PreRegisterAction[]")] actions: Vec<PreRegisterAction>,
) {
#[cfg(target_os = "linux")]
{
crate::platform::xdg_preregister_keybinds(actions).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![
Expand Down
55 changes: 43 additions & 12 deletions src/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -22,6 +22,8 @@ use crate::{
};

static KEYBINDS: LazyLock<Mutex<Keybinds>> = LazyLock::new(|| Mutex::new(Keybinds::default()));
static CURR_DOWN: LazyLock<Mutex<Option<(Keybind, KeybindId)>>> =
LazyLock::new(|| Mutex::new(None));
static TX: OnceLock<Sender<KeybindTrigger>> = OnceLock::new();

static XDG_STATE: LazyLock<Mutex<Option<XDGState>>> = LazyLock::new(|| Mutex::new(None));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -109,13 +112,19 @@ pub(crate) fn xdg_preregister_keybinds(actions: Vec<PreRegisterAction>) -> 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(())
}

Expand All @@ -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();
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<KeybindId> {
self.keybinds.get(&keybind).copied()
pub fn get_keybind_id(&self, keybind: &Keybind) -> Option<KeybindId> {
self.keybinds.get(keybind).copied()
}
}
29 changes: 26 additions & 3 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Mutex<Keybinds>> = LazyLock::new(|| Mutex::new(Keybinds::default()));
static CURR_DOWN: LazyLock<Mutex<Option<(Keybind, KeybindId)>>> =
LazyLock::new(|| Mutex::new(None));
static TX: OnceLock<Sender<KeybindTrigger>> = OnceLock::new();

pub(crate) fn start_keybinds_internal(tx: Sender<KeybindTrigger>) -> Result<()> {
Expand Down Expand Up @@ -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();
}
}
}
Expand Down

0 comments on commit b8116e6

Please sign in to comment.