Skip to content

Commit

Permalink
Add Support for Key Modifiers to the Hotkeys (#582)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
CryZe committed Oct 11, 2022
1 parent 401b70f commit 209c0a9
Show file tree
Hide file tree
Showing 16 changed files with 880 additions and 311 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ jobs:

- name: Install cross
if: matrix.cross == '' && matrix.no_std == ''
run: cargo install cross
run: cargo install cross --debug

- name: Build Static Library
run: sh .github/workflows/build_static.sh
Expand Down
11 changes: 6 additions & 5 deletions crates/livesplit-hotkey/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repository = "https://github.com/LiveSplit/livesplit-core/tree/master/crates/liv
license = "Apache-2.0/MIT"
description = "livesplit-hotkey provides cross-platform global hotkey hooks."
keywords = ["speedrun", "timer", "livesplit", "hotkey", "keyboard"]
edition = "2018"
edition = "2021"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.2", features = [
Expand All @@ -16,14 +16,14 @@ winapi = { version = "0.3.2", features = [
"winuser"
], optional = true }

[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.7"

[target.'cfg(target_os = "linux")'.dependencies]
evdev = { version = "=0.11.4", optional = true }
mio = { version = "0.8.0", default-features = false, features = ["os-ext", "os-poll"], optional = true }
promising-future = { version = "0.2.4", optional = true }

[target.'cfg(target_os = "macos")'.dependencies]
bitflags = { version = "1.2.1", optional = true }

[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
wasm-bindgen = { version = "0.2.54", optional = true }
web-sys = { version = "0.3.28", default-features = false, features = ["Gamepad", "GamepadButton", "EventTarget", "KeyboardEvent", "Navigator", "Window"], optional = true }
Expand All @@ -32,8 +32,9 @@ web-sys = { version = "0.3.28", default-features = false, features = ["Gamepad",
cfg-if = "1.0.0"
serde = { version = "1.0.98", default-features = false, features = ["derive", "alloc"] }
snafu = { version = "0.7.0", default-features = false }
bitflags = { version = "1.2.1" }

[features]
default = ["std"]
std = ["snafu/std", "serde/std", "evdev", "mio", "promising-future", "winapi", "bitflags"]
std = ["snafu/std", "serde/std", "evdev", "mio", "promising-future", "winapi"]
wasm-web = ["wasm-bindgen", "web-sys"]
99 changes: 99 additions & 0 deletions crates/livesplit-hotkey/src/hotkey.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use core::{fmt, str::FromStr};

use serde::{Deserialize, Serialize};

use crate::{KeyCode, Modifiers};

/// A hotkey is a combination of a key code and a set of modifiers.
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub struct Hotkey {
/// The key code of the hotkey.
pub key_code: KeyCode,
/// The modifiers of the hotkey.
pub modifiers: Modifiers,
}

impl fmt::Debug for Hotkey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

impl fmt::Display for Hotkey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.modifiers.is_empty() {
f.write_str(self.key_code.name())
} else {
write!(f, "{} + {}", self.modifiers, self.key_code.name())
}
}
}

impl FromStr for Hotkey {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((modifiers, key_code)) = s.rsplit_once('+') {
let modifiers = modifiers.trim_end().parse()?;
let key_code = key_code.trim_start().parse()?;
Ok(Self {
key_code,
modifiers,
})
} else {
let key_code = s.parse()?;
Ok(Self {
key_code,
modifiers: Modifiers::empty(),
})
}
}
}

impl Serialize for Hotkey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if self.modifiers.is_empty() {
self.key_code.serialize(serializer)
} else {
serializer.collect_str(self)
}
}
}

impl<'de> Deserialize<'de> for Hotkey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(HotkeyVisitor)
}
}

struct HotkeyVisitor;

impl<'de> serde::de::Visitor<'de> for HotkeyVisitor {
type Value = Hotkey;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a valid hotkey")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Hotkey::from_str(v).map_err(|()| serde::de::Error::custom("invalid hotkey"))
}
}

impl From<KeyCode> for Hotkey {
fn from(key_code: KeyCode) -> Self {
Self {
key_code,
modifiers: Modifiers::empty(),
}
}
}
Loading

0 comments on commit 209c0a9

Please sign in to comment.