Skip to content

Commit

Permalink
Fix hotkey selector panic (#110)
Browse files Browse the repository at this point in the history
* Implement to_string() for keyboard::Keys and key

* Allow unsetting global hotkeys

Allows global hotkeys to be set to "not set" by setting to any incompatible value, for example Esc.

* Improve keyboard.rs

Replace key_to_string()->String with key_to_str()->&str.

* Refactor hotkey::Manager
  • Loading branch information
Barafu authored Jul 21, 2024
1 parent e3a531e commit e7d9716
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/component/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use super::setting::Chat;
use crate::widget::ComboBoxItem;

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, Hash, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum Function {
Rewrite,
Expand Down
130 changes: 130 additions & 0 deletions src/component/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,23 @@ impl FromStr for Keys {
}
}

impl ToString for Keys {
fn to_string(&self) -> String {
let mut s: Vec<String> = Vec::new();

for k in &self.0 {
match k {
Key::Control => s.push("CTRL".to_string()),
Key::Shift => s.push("SHIFT".to_string()),
Key::Alt => s.push("ALT".to_string()),
Key::Meta => s.push("META".to_string()),
_ => s.push(key_to_str(k).unwrap_or_else(|_| "not set").to_uppercase()),
}
}
s.join("+")
}
}

// We can't use [`enigo::Key::Unicode`], it will cause panic.
// Don't know why, maybe that can only be used in main thread.
fn key_of(key: char) -> Result<Key> {
Expand Down Expand Up @@ -180,3 +197,116 @@ fn key_of(key: char) -> Result<Key> {

Ok(k)
}

pub fn key_to_str(key: &Key) -> Result<&'static str> {
match key {
#[cfg(all(unix, not(target_os = "macos")))]
Key::Unicode(c) => Ok(c),

#[cfg(target_os = "windows")]
Key::Other(v) => Ok(match v {
0x30 => "0",
0x31 => "1",
0x32 => "2",
0x33 => "3",
0x34 => "4",
0x35 => "5",
0x36 => "6",
0x37 => "7",
0x38 => "8",
0x39 => "9",
0x41 => "A",
0x42 => "B",
0x43 => "C",
0x44 => "D",
0x45 => "E",
0x46 => "F",
0x47 => "G",
0x48 => "H",
0x49 => "I",
0x4A => "J",
0x4B => "K",
0x4C => "L",
0x4D => "M",
0x4E => "N",
0x4F => "O",
0x50 => "P",
0x51 => "Q",
0x52 => "R",
0x53 => "S",
0x54 => "T",
0x55 => "U",
0x56 => "V",
0x57 => "W",
0x58 => "X",
0x59 => "Y",
0x5A => "Z",
0xBB => "=",
0xBD => "-",
0xBA => ";",
0xBC => ",",
0xBE => ".",
0xBF => "/",
0xC0 => "`",
0xDB => "[",
0xDD => "]",
0xDC => "\\",
0xDE => "'",
_ => return Err(Error::UnsupportedKey(format!("{:x}", v))),
}),

#[cfg(target_os = "macos")]
Key::Other(v) => Ok(match v {
0 => "A",
1 => "S",
2 => "D",
3 => "F",
4 => "H",
5 => "G",
6 => "Z",
7 => "X",
8 => "C",
9 => "V",
11 => "B",
12 => "Q",
13 => "W",
14 => "E",
15 => "R",
16 => "Y",
17 => "T",
18 => "1",
19 => "2",
20 => "3",
21 => "4",
22 => "6",
23 => "5",
24 => "=",
25 => "9",
26 => "7",
27 => "-",
28 => "8",
29 => "0",
30 => "]",
31 => "O",
32 => "U",
33 => "[",
34 => "I",
35 => "P",
37 => "L",
38 => "J",
39 => "'",
40 => "K",
41 => ";",
42 => "\\",
43 => ",",
44 => "/",
45 => "N",
46 => "M",
47 => ".",
50 => "`",
_ => return Err(Error::UnsupportedKey(format!("{:x}", v))),
}),

_ => Err(Error::UnsupportedKey(format!("{:?}", key))),
}
}
139 changes: 99 additions & 40 deletions src/service/hotkey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{

pub struct Hotkey {
// The manager need to be kept alive during the whole program life.
_manager: GlobalHotKeyManager,
ghk_manager: GlobalHotKeyManager,
manager: Arc<RwLock<Manager>>,
abort: ArtBool,
}
Expand All @@ -37,7 +37,7 @@ impl Hotkey {
) -> Result<Self> {
let ctx = ctx.to_owned();
let _manager = GlobalHotKeyManager::new().map_err(GlobalHotKeyError::Main)?;
let manager = Arc::new(RwLock::new(Manager::new(&_manager, hotkeys)?));
let manager = Arc::new(RwLock::new(Manager::new(&_manager, hotkeys)));
let manager_ = manager.clone();
let notification_sound = state.general.notification_sound.clone();
let activated_function = state.chat.activated_function.clone();
Expand Down Expand Up @@ -107,17 +107,20 @@ impl Hotkey {
}
});

Ok(Self { _manager, manager, abort })
Ok(Self { ghk_manager: _manager, manager, abort })
}

pub fn renew(&self, hotkeys: &Hotkeys) {
pub fn renew(&self, hotkeys: &mut Hotkeys) {
tracing::info!("renewing hotkey manager");

let mut manager = self.manager.write();

self._manager.unregister_all(&manager.hotkeys).expect("unregister must succeed");
manager.unregister_hotkeys(&self.ghk_manager);

*manager = Manager::new(&self._manager, hotkeys).expect("renew must succeed");
*manager = Manager::new(&self.ghk_manager, hotkeys);

// Write hotkey texts back into the settings.
manager.read_hotkeys_into_settings(hotkeys);
}

pub fn abort(&self) {
Expand All @@ -130,47 +133,103 @@ impl Debug for Hotkey {
}
}

#[derive(Debug)]
struct HotKeyPair {
hotkey: HotKey,
keys: Keys,
}
struct Manager {
hotkeys: [HotKey; 4],
hotkeys_keys: [Keys; 4],
/// Hotkeys. The order follows the order in settings
hotkeys_list: [Option<HotKeyPair>; 4],
}
impl Manager {
fn new(_manager: &GlobalHotKeyManager, hotkeys: &Hotkeys) -> Result<Self> {
let hotkeys_raw = [
&hotkeys.rewrite,
&hotkeys.rewrite_directly,
&hotkeys.translate,
&hotkeys.translate_directly,
/// Creates new manager. Registers hotkeys with global manager.
/// If any hotkey did not gersister, stores None instead of it.
fn new(ghk_manager: &GlobalHotKeyManager, settings_hotkeys: &Hotkeys) -> Self {
let hotkey_str_list = [
&settings_hotkeys.rewrite,
&settings_hotkeys.rewrite_directly,
&settings_hotkeys.translate,
&settings_hotkeys.translate_directly,
];
let hotkeys: [HotKey; 4] = hotkeys_raw
.iter()
.map(|h| h.parse())
.collect::<Result<Vec<_>, _>>()
.map_err(GlobalHotKeyError::Parse)?
.try_into()
.expect("array must fit");

_manager.register_all(&hotkeys).map_err(GlobalHotKeyError::Main)?;

let hotkeys_keys = hotkeys_raw
.iter()
.map(|h| h.parse())
.collect::<Result<Vec<_>, _>>()?
.try_into()
.expect("array must fit");

Ok(Self { hotkeys, hotkeys_keys })

let mut hotkeys_list: Vec<Option<HotKeyPair>> = Vec::with_capacity(4);

for hotkey_str in hotkey_str_list.into_iter() {
//Parse error possible when str value is "Not set"
let hotkey: HotKey = match hotkey_str.parse() {
Ok(v) => v,
Err(_) => {
hotkeys_list.push(None);
continue;
},
};
// Same goes for Keys
let keys: Keys = match hotkey_str.parse() {
Ok(v) => v,
Err(_) => {
hotkeys_list.push(None);
continue;
},
};
// If manager.register fails, ignore the key, and it becomes "Not set".
let e = ghk_manager.register(hotkey);
if e.is_err() {
hotkeys_list.push(None);
continue;
}

// If key has been registered, add it to new_hotkeys
hotkeys_list.push(Some(HotKeyPair { hotkey, keys }));
}

Self { hotkeys_list: hotkeys_list.try_into().expect("hotkeys_list must have 4 elements") }
}

fn match_func(&self, id: u32) -> (Function, Keys) {
match id {
i if i == self.hotkeys[0].id => (Function::Rewrite, self.hotkeys_keys[0].clone()),
i if i == self.hotkeys[1].id =>
(Function::RewriteDirectly, self.hotkeys_keys[1].clone()),
i if i == self.hotkeys[2].id => (Function::Translate, self.hotkeys_keys[2].clone()),
i if i == self.hotkeys[3].id =>
(Function::TranslateDirectly, self.hotkeys_keys[3].clone()),
_ => unreachable!(),
// Must follow the same order of functions as in settings
const FUNCTION_LIST: [Function; 4] = [
Function::Rewrite,
Function::RewriteDirectly,
Function::Translate,
Function::TranslateDirectly,
];

for (i, pair) in self.hotkeys_list.iter().enumerate() {
if let Some(pair) = pair {
if pair.hotkey.id == id {
return (FUNCTION_LIST[i], pair.keys.clone());
}
}
}
unreachable!();
}

// Copies text representation of actually registered hotkeys back to settings.
// Replaces text for unset hotkeys with "Not set"
fn read_hotkeys_into_settings(&self, settings_hotkeys: &mut Hotkeys) {
let hotkey_str_list = [
&mut settings_hotkeys.rewrite,
&mut settings_hotkeys.rewrite_directly,
&mut settings_hotkeys.translate,
&mut settings_hotkeys.translate_directly,
];

for (i, k) in hotkey_str_list.into_iter().enumerate() {
*k = match &self.hotkeys_list[i] {
Some(p) => p.keys.to_string(),
None => "Not set".to_string(),
};
}
}

/// Unregisters all hotkeys with given global hotkey manager.
fn unregister_hotkeys(&mut self, ghk_manager: &GlobalHotKeyManager) {
for list_entry in self.hotkeys_list.iter_mut() {
if let Some(pair) = list_entry {
ghk_manager.unregister(pair.hotkey).expect("unregister must succeed");
}
*list_entry = None;
}
}
}
2 changes: 1 addition & 1 deletion src/ui/panel/setting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ impl Setting {

changed
}) {
ctx.services.hotkey.renew(&ctx.components.setting.hotkeys);
ctx.services.hotkey.renew(&mut ctx.components.setting.hotkeys);
}
});
});
Expand Down

0 comments on commit e7d9716

Please sign in to comment.