Skip to content

Commit d97f998

Browse files
committed
Add Support for Key Modifiers to the Hotkeys (#582)
This adds support for key modifiers to the `livesplit-hotkey` crate. Instead of specifying a `KeyCode`, you now specify a `Hotkey` which consists of a `KeyCode` and a set of `Modifiers`. All the implementations support modifiers. However while `wasm-web` and `macOS` natively provide the state of the modifiers to us, the other platforms manually track their state.
1 parent 401b70f commit d97f998

File tree

16 files changed

+871
-302
lines changed

16 files changed

+871
-302
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ jobs:
631631

632632
- name: Install cross
633633
if: matrix.cross == '' && matrix.no_std == ''
634-
run: cargo install cross
634+
run: cargo install cross --debug
635635

636636
- name: Build Static Library
637637
run: sh .github/workflows/build_static.sh

crates/livesplit-hotkey/Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ repository = "https://github.com/LiveSplit/livesplit-core/tree/master/crates/liv
77
license = "Apache-2.0/MIT"
88
description = "livesplit-hotkey provides cross-platform global hotkey hooks."
99
keywords = ["speedrun", "timer", "livesplit", "hotkey", "keyboard"]
10-
edition = "2018"
10+
edition = "2021"
1111

1212
[target.'cfg(windows)'.dependencies]
1313
winapi = { version = "0.3.2", features = [
@@ -16,14 +16,14 @@ winapi = { version = "0.3.2", features = [
1616
"winuser"
1717
], optional = true }
1818

19+
[target.'cfg(target_os = "macos")'.dependencies]
20+
objc = "0.2.7"
21+
1922
[target.'cfg(target_os = "linux")'.dependencies]
2023
evdev = { version = "=0.11.4", optional = true }
2124
mio = { version = "0.8.0", default-features = false, features = ["os-ext", "os-poll"], optional = true }
2225
promising-future = { version = "0.2.4", optional = true }
2326

24-
[target.'cfg(target_os = "macos")'.dependencies]
25-
bitflags = { version = "1.2.1", optional = true }
26-
2727
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
2828
wasm-bindgen = { version = "0.2.54", optional = true }
2929
web-sys = { version = "0.3.28", default-features = false, features = ["Gamepad", "GamepadButton", "EventTarget", "KeyboardEvent", "Navigator", "Window"], optional = true }
@@ -32,8 +32,9 @@ web-sys = { version = "0.3.28", default-features = false, features = ["Gamepad",
3232
cfg-if = "1.0.0"
3333
serde = { version = "1.0.98", default-features = false, features = ["derive", "alloc"] }
3434
snafu = { version = "0.7.0", default-features = false }
35+
bitflags = { version = "1.2.1" }
3536

3637
[features]
3738
default = ["std"]
38-
std = ["snafu/std", "serde/std", "evdev", "mio", "promising-future", "winapi", "bitflags"]
39+
std = ["snafu/std", "serde/std", "evdev", "mio", "promising-future", "winapi"]
3940
wasm-web = ["wasm-bindgen", "web-sys"]

crates/livesplit-hotkey/src/hotkey.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use core::{fmt, str::FromStr};
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
use crate::{KeyCode, Modifiers};
6+
7+
/// A hotkey is a combination of a key code and a set of modifiers.
8+
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
9+
pub struct Hotkey {
10+
/// The key code of the hotkey.
11+
pub key_code: KeyCode,
12+
/// The modifiers of the hotkey.
13+
pub modifiers: Modifiers,
14+
}
15+
16+
impl fmt::Debug for Hotkey {
17+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18+
fmt::Display::fmt(self, f)
19+
}
20+
}
21+
22+
impl fmt::Display for Hotkey {
23+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24+
if self.modifiers.is_empty() {
25+
f.write_str(self.key_code.name())
26+
} else {
27+
write!(f, "{} + {}", self.modifiers, self.key_code.name())
28+
}
29+
}
30+
}
31+
32+
impl FromStr for Hotkey {
33+
type Err = ();
34+
35+
fn from_str(s: &str) -> Result<Self, Self::Err> {
36+
if let Some((modifiers, key_code)) = s.rsplit_once('+') {
37+
let modifiers = modifiers.trim_end().parse()?;
38+
let key_code = key_code.trim_start().parse()?;
39+
Ok(Self {
40+
key_code,
41+
modifiers,
42+
})
43+
} else {
44+
let key_code = s.parse()?;
45+
Ok(Self {
46+
key_code,
47+
modifiers: Modifiers::empty(),
48+
})
49+
}
50+
}
51+
}
52+
53+
impl Serialize for Hotkey {
54+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
55+
where
56+
S: serde::Serializer,
57+
{
58+
if self.modifiers.is_empty() {
59+
self.key_code.serialize(serializer)
60+
} else {
61+
serializer.collect_str(self)
62+
}
63+
}
64+
}
65+
66+
impl<'de> Deserialize<'de> for Hotkey {
67+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
68+
where
69+
D: serde::Deserializer<'de>,
70+
{
71+
deserializer.deserialize_str(HotkeyVisitor)
72+
}
73+
}
74+
75+
struct HotkeyVisitor;
76+
77+
impl<'de> serde::de::Visitor<'de> for HotkeyVisitor {
78+
type Value = Hotkey;
79+
80+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81+
formatter.write_str("a valid hotkey")
82+
}
83+
84+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
85+
where
86+
E: serde::de::Error,
87+
{
88+
Hotkey::from_str(v).map_err(|()| serde::de::Error::custom("invalid hotkey"))
89+
}
90+
}
91+
92+
impl From<KeyCode> for Hotkey {
93+
fn from(key_code: KeyCode) -> Self {
94+
Self {
95+
key_code,
96+
modifiers: Modifiers::empty(),
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)