virtual_desktop_manager\tray_plugins/
hotkeys.rs1#![cfg(feature = "global_hotkey")]
3
4use crate::{
5 dynamic_gui::DynamicUiHooks,
6 settings::UiSettings,
7 tray::{SystemTray, SystemTrayRef, TrayPlugin, TrayRoot},
8};
9use global_hotkey::{hotkey::HotKey, GlobalHotKeyEvent, GlobalHotKeyManager};
10use std::{
11 any::TypeId,
12 cell::RefCell,
13 collections::HashMap,
14 rc::Rc,
15 sync::{mpsc, Arc, Mutex},
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
19enum HotKeyAction {
20 OpenQuickSwitchMenu,
21 OpenContextMenuAtMousePos,
22}
23
24#[derive(Debug, Default)]
25struct CellState {
26 registered_hotkeys: Vec<HotKey>,
27 action_lookup: HashMap<u32, HotKeyAction>,
28}
29impl CellState {
30 pub fn clear(&mut self) {
31 self.registered_hotkeys.clear();
32 self.action_lookup.clear();
33 }
34 pub fn hotkeys(&self) -> &[HotKey] {
35 &self.registered_hotkeys
36 }
37 pub fn add_hotkey(&mut self, hotkey: HotKey, action: HotKeyAction) {
38 self.registered_hotkeys.push(hotkey);
39 self.action_lookup.insert(hotkey.id(), action);
40 }
41}
42
43#[derive(nwd::NwgPartial)]
44pub struct HotKeyPlugin {
45 tray: SystemTrayRef,
46
47 hotkey_manager: GlobalHotKeyManager,
48 current_hotkeys: RefCell<CellState>,
49 events: mpsc::Receiver<GlobalHotKeyEvent>,
50
51 latest_notice_sender: Arc<Mutex<Option<nwg::NoticeSender>>>,
52 #[nwg_control]
55 #[nwg_events( OnNotice: [Self::on_background_notice] )]
56 background_notice: nwg::Notice,
57}
58impl Default for HotKeyPlugin {
59 fn default() -> Self {
60 let latest_notice_sender = Arc::new(Mutex::new(None::<nwg::NoticeSender>));
61 let (tx, rx) = mpsc::channel();
62 _ = std::thread::Builder::new()
63 .name("GlobalHotKeyListenerThread".to_owned())
64 .spawn({
65 let latest_notice_sender = latest_notice_sender.clone();
66 move || {
67 let hotkey_rx = GlobalHotKeyEvent::receiver();
68 for ev in hotkey_rx.iter() {
69 if tx.send(ev).is_err() {
70 break;
71 }
72 if let Some(sender) = *latest_notice_sender.lock().unwrap() {
73 sender.notice();
74 }
75 }
76 }
77 });
78 Self {
79 tray: Default::default(),
80
81 hotkey_manager: global_hotkey::GlobalHotKeyManager::new()
82 .expect("Failed to create global keyboard shortcut manager"),
83 current_hotkeys: RefCell::default(),
84 events: rx,
85
86 latest_notice_sender,
87 background_notice: Default::default(),
88 }
89 }
90}
91impl DynamicUiHooks<SystemTray> for HotKeyPlugin {
92 fn before_partial_build(
93 &mut self,
94 tray: &Rc<SystemTray>,
95 _should_build: &mut bool,
96 ) -> Option<(nwg::ControlHandle, TypeId)> {
97 self.tray.set(tray);
98 Some((tray.root().window.handle, TypeId::of::<TrayRoot>()))
99 }
100 fn after_partial_build(&mut self, _dynamic_ui: &Rc<SystemTray>) {
101 *self.latest_notice_sender.lock().unwrap() = Some(self.background_notice.sender());
102 self.update_hotkeys();
103 }
104 fn before_rebuild(&mut self, _dynamic_ui: &Rc<SystemTray>) {
105 self.background_notice = Default::default();
106 }
107}
108impl TrayPlugin for HotKeyPlugin {
109 fn on_settings_changed(
110 &self,
111 _tray_ui: &Rc<SystemTray>,
112 prev: &Arc<UiSettings>,
113 new: &Arc<UiSettings>,
114 ) {
115 if !Arc::ptr_eq(&prev.quick_switch_hotkey, &new.quick_switch_hotkey)
116 && prev.quick_switch_hotkey != new.quick_switch_hotkey
117 {
118 self.update_hotkeys();
119 return;
120 }
121 if !Arc::ptr_eq(
122 &prev.open_menu_at_mouse_pos_hotkey,
123 &new.open_menu_at_mouse_pos_hotkey,
124 ) && prev.open_menu_at_mouse_pos_hotkey != new.open_menu_at_mouse_pos_hotkey
125 {
126 self.update_hotkeys();
127 }
128 }
129}
130impl HotKeyPlugin {
131 fn on_background_notice(&self) {
132 let Some(tray) = self.tray.get() else {
133 return;
134 };
135 for event in self.events.try_iter() {
136 tracing::debug!(?event, "Received global hotkey");
137 if event.state() == global_hotkey::HotKeyState::Pressed {
138 if let Ok(guard) = self.current_hotkeys.try_borrow() {
139 let action = guard.action_lookup.get(&event.id()).copied();
140 drop(guard);
141 if let Some(action) = action {
142 match action {
143 HotKeyAction::OpenQuickSwitchMenu => tray.notify_quick_switch_hotkey(),
144 HotKeyAction::OpenContextMenuAtMousePos => {
145 tray.notify_open_menu_at_mouse_position_hotkey()
146 }
147 }
148 } else {
149 tracing::warn!(?event, "No action registered for the pressed hotkey");
150 }
151 } else {
152 tracing::warn!(
153 ?event,
154 "Ignored hotkey event because hotkeys were currently being updated"
155 );
156 }
157 }
158 }
159 }
160 pub fn update_hotkeys(&self) {
161 #[cfg(feature = "global_hotkey")]
162 {
163 let settings = self.tray.get().unwrap().settings().get();
164 let Ok(mut guard) = self.current_hotkeys.try_borrow_mut() else {
165 tracing::warn!("Tried to update global hotkeys recursively");
166 return;
167 };
168 if let Err(e) = self.hotkey_manager.unregister_all(guard.hotkeys()) {
169 tracing::error!(error = e.to_string(), "Failed to unregister global hotkeys");
170 }
171 let mut hotkeys = std::mem::take(&mut *guard);
172 hotkeys.clear();
173
174 if !settings.quick_switch_hotkey.is_empty() {
175 match settings.quick_switch_hotkey.parse() {
176 Ok(hotkey) => hotkeys.add_hotkey(hotkey, HotKeyAction::OpenQuickSwitchMenu),
177 Err(e) => {
178 tracing::warn!(error = e.to_string(), "Invalid quick switch hotkey");
179 }
180 }
181 }
182 if !settings.open_menu_at_mouse_pos_hotkey.is_empty() {
183 match settings.open_menu_at_mouse_pos_hotkey.parse() {
184 Ok(hotkey) => {
185 hotkeys.add_hotkey(hotkey, HotKeyAction::OpenContextMenuAtMousePos)
186 }
187 Err(e) => {
188 tracing::warn!(
189 error = e.to_string(),
190 "Invalid hotkey for opening context menu at mouse location"
191 );
192 }
193 }
194 }
195
196 tracing::debug!(hotkeys =? hotkeys.hotkeys(), "Registering new hotkeys");
197
198 if hotkeys.hotkeys().is_empty() {
199 *guard = hotkeys;
200 return;
201 }
202 if let Err(e) = self.hotkey_manager.register_all(hotkeys.hotkeys()) {
203 tracing::error!(error = e.to_string(), "Failed to register global hotkeys");
204 } else {
205 *guard = hotkeys;
206 }
207 }
208 }
209}