Skip to content

Commit

Permalink
Implement Resolving Key Codes on Linux (#594)
Browse files Browse the repository at this point in the history
Since we brought back X11, we can now mostly just use the established
X11 connection to resolve the key codes. This however does not work with
the `evdev` implementation. The solution there is to lazily open a
connection to X11 when trying to resolve a key code and simply fail
resolving the key code when a connection can't be established, then as
before, falling back onto the US keyboard layout. An unfortunate side
effect of this all however is that resolving a key code is now stateful,
meaning that you always need a `Hook` to do so.
  • Loading branch information
CryZe authored Nov 7, 2022
1 parent 67cb40a commit d878eac
Show file tree
Hide file tree
Showing 9 changed files with 1,084 additions and 202 deletions.
6 changes: 3 additions & 3 deletions crates/livesplit-hotkey/src/key_code.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Hotkey, Modifiers};
use crate::{Hook, Hotkey, Modifiers};
use alloc::borrow::Cow;
use core::{fmt, str::FromStr};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -1767,10 +1767,10 @@ impl KeyCode {
}

/// Resolves the key according to the current keyboard layout.
pub fn resolve(self) -> Cow<'static, str> {
pub fn resolve(self, hook: &Hook) -> Cow<'static, str> {
let class = self.classify();
if class == KeyCodeClass::WritingSystem {
if let Some(resolved) = crate::platform::try_resolve(self) {
if let Some(resolved) = hook.try_resolve(self) {
let uppercase = if resolved != "ß" {
resolved.to_uppercase()
} else {
Expand Down
26 changes: 14 additions & 12 deletions crates/livesplit-hotkey/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,20 @@ mod tests {

#[test]
fn resolve() {
let hook = Hook::new().unwrap();

// Based on German keyboard layout.
println!("ß: {}", KeyCode::Minus.resolve());
println!("ü: {}", KeyCode::BracketLeft.resolve());
println!("#: {}", KeyCode::Backslash.resolve());
println!("+: {}", KeyCode::BracketRight.resolve());
println!("z: {}", KeyCode::KeyY.resolve());
println!("^: {}", KeyCode::Backquote.resolve());
println!("<: {}", KeyCode::IntlBackslash.resolve());
println!("Yen: {}", KeyCode::IntlYen.resolve());
println!("Enter: {}", KeyCode::Enter.resolve());
println!("Space: {}", KeyCode::Space.resolve());
println!("Tab: {}", KeyCode::Tab.resolve());
println!("Numpad0: {}", KeyCode::Numpad0.resolve());
println!("ß: {}", KeyCode::Minus.resolve(&hook));
println!("ü: {}", KeyCode::BracketLeft.resolve(&hook));
println!("#: {}", KeyCode::Backslash.resolve(&hook));
println!("+: {}", KeyCode::BracketRight.resolve(&hook));
println!("z: {}", KeyCode::KeyY.resolve(&hook));
println!("^: {}", KeyCode::Backquote.resolve(&hook));
println!("<: {}", KeyCode::IntlBackslash.resolve(&hook));
println!("Yen: {}", KeyCode::IntlYen.resolve(&hook));
println!("Enter: {}", KeyCode::Enter.resolve(&hook));
println!("Space: {}", KeyCode::Space.resolve(&hook));
println!("Tab: {}", KeyCode::Tab.resolve(&hook));
println!("Numpad0: {}", KeyCode::Numpad0.resolve(&hook));
}
}
36 changes: 34 additions & 2 deletions crates/livesplit-hotkey/src/linux/evdev_impl.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::{
collections::hash_map::HashMap, os::unix::prelude::AsRawFd, sync::mpsc::channel, thread,
collections::hash_map::HashMap, os::unix::prelude::AsRawFd, ptr, sync::mpsc::channel, thread,
};

use evdev::{Device, EventType, InputEventKind, Key};
use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker};
use x11_dl::xlib::{Xlib, _XDisplay};

use super::Message;
use super::{x11_impl, Message};
use crate::{Error, Hook, KeyCode, Modifiers, Result};

// Low numbered tokens are allocated to devices.
Expand Down Expand Up @@ -257,6 +258,8 @@ pub fn new() -> Result<Hook> {
let mut hotkeys: HashMap<(Key, Modifiers), Box<dyn FnMut() + Send>> = HashMap::new();
let mut modifiers = Modifiers::empty();

let (mut xlib, mut display) = (None, None);

'event_loop: loop {
if poll.poll(&mut events, None).is_err() {
result = Err(Error::EPoll);
Expand Down Expand Up @@ -330,6 +333,9 @@ pub fn new() -> Result<Hook> {
.and_then(|k| hotkeys.remove(&(k, key.modifiers)).map(drop))
.ok_or(Error::NotRegistered),
),
Message::Resolve(key_code, promise) => {
promise.set(resolve(&mut xlib, &mut display, key_code))
}
Message::End => {
break 'event_loop;
}
Expand All @@ -338,6 +344,11 @@ pub fn new() -> Result<Hook> {
}
}
}

if let (Some(xlib), Some(display)) = (xlib, display) {
unsafe { (xlib.XCloseDisplay)(display) };
}

result
});

Expand All @@ -347,3 +358,24 @@ pub fn new() -> Result<Hook> {
join_handle: Some(join_handle),
})
}

fn resolve(
xlib: &mut Option<Xlib>,
display: &mut Option<*mut _XDisplay>,
key_code: KeyCode,
) -> Option<char> {
if xlib.is_none() {
*xlib = Xlib::open().ok();
}
let xlib = xlib.as_ref()?;
if display.is_none() {
unsafe {
let result = (xlib.XOpenDisplay)(ptr::null());
if !result.is_null() {
*display = Some(result);
}
}
}
let xdisplay = (*display)?;
x11_impl::resolve(xlib, xdisplay, key_code)
}
13 changes: 10 additions & 3 deletions crates/livesplit-hotkey/src/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ enum Message {
Promise<Result<()>>,
),
Unregister(Hotkey, Promise<Result<()>>),
Resolve(KeyCode, Promise<Option<char>>),
End,
}

Expand Down Expand Up @@ -102,8 +103,14 @@ impl Hook {

future.value().ok_or(Error::ThreadStopped)?
}
}

pub(crate) fn try_resolve(_key_code: KeyCode) -> Option<String> {
None
pub(crate) fn try_resolve(&self, key_code: KeyCode) -> Option<String> {
let (future, promise) = future_promise();

self.sender.send(Message::Resolve(key_code, promise)).ok()?;

self.waker.wake().ok()?;

Some(char::to_string(&future.value()??))
}
}
Loading

0 comments on commit d878eac

Please sign in to comment.