1use crate::{
4 request_channel::{RequestChannel, RequestReceiver},
5 tasks::RuntimeWake,
6};
7use incular_platform::{
8 CapabilitySupport, GlobalShortcutChord, GlobalShortcutError, GlobalShortcutId,
9 PlatformCapabilities,
10};
11use std::{
12 collections::{HashMap, VecDeque},
13 future::Future,
14 pin::Pin,
15 sync::{Arc, RwLock, mpsc},
16 task::{Context, Poll},
17};
18use tokio::sync::oneshot;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
21pub struct GlobalShortcutRequestId(u64);
22
23impl GlobalShortcutRequestId {
24 #[must_use]
25 pub const fn new(value: u64) -> Self {
26 Self(value)
27 }
28
29 #[must_use]
30 pub const fn get(self) -> u64 {
31 self.0
32 }
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum NativeGlobalShortcutOperation {
37 Register {
38 id: GlobalShortcutId,
39 chord: GlobalShortcutChord,
40 },
41 Unregister {
42 id: GlobalShortcutId,
43 },
44}
45
46impl NativeGlobalShortcutOperation {
47 #[must_use]
48 pub const fn id(self) -> GlobalShortcutId {
49 match self {
50 Self::Register { id, .. } | Self::Unregister { id } => id,
51 }
52 }
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct NativeGlobalShortcutRequest {
57 pub request_id: GlobalShortcutRequestId,
58 pub operation: NativeGlobalShortcutOperation,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct NativeGlobalShortcutCompletion {
63 pub request_id: GlobalShortcutRequestId,
64 pub result: Result<(), GlobalShortcutError>,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum GlobalShortcutCompletionStatus {
69 Completed,
70 AbandonedRegistration(GlobalShortcutId),
73 UnknownRequest,
74}
75
76#[derive(Clone)]
77pub struct GlobalShortcutService {
78 bridge: Arc<GlobalShortcutBridge>,
79 capabilities: Arc<RwLock<PlatformCapabilities>>,
80}
81
82impl std::fmt::Debug for GlobalShortcutService {
83 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 formatter.write_str("GlobalShortcutService(..)")
85 }
86}
87
88impl GlobalShortcutService {
89 pub(crate) fn new(
90 bridge: Arc<GlobalShortcutBridge>,
91 capabilities: Arc<RwLock<PlatformCapabilities>>,
92 ) -> Self {
93 Self {
94 bridge,
95 capabilities,
96 }
97 }
98
99 #[must_use]
100 pub fn support(&self) -> CapabilitySupport {
101 self.capabilities
102 .read()
103 .expect("application capability snapshot lock")
104 .application_services
105 .global_shortcuts
106 }
107
108 pub fn register(
114 &self,
115 id: GlobalShortcutId,
116 chord: GlobalShortcutChord,
117 ) -> Result<GlobalShortcutRegistrationRequest, GlobalShortcutError> {
118 if self.support() == CapabilitySupport::Unsupported {
119 return Err(GlobalShortcutError::Unsupported);
120 }
121 let receiver = self.bridge.register(id, chord)?;
122 Ok(GlobalShortcutRegistrationRequest { receiver })
123 }
124}
125
126#[must_use = "global shortcut registration must be awaited or explicitly dropped"]
127pub struct GlobalShortcutRegistrationRequest {
128 receiver: RequestReceiver<Result<GlobalShortcutRegistration, GlobalShortcutError>>,
129}
130impl Unpin for GlobalShortcutRegistrationRequest {}
131impl Future for GlobalShortcutRegistrationRequest {
132 type Output = Result<GlobalShortcutRegistration, GlobalShortcutError>;
133 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
134 self.receiver
135 .poll_result(context, || Err(GlobalShortcutError::ApplicationStopped))
136 }
137}
138
139pub struct GlobalShortcutRegistration {
144 id: GlobalShortcutId,
145 chord: GlobalShortcutChord,
146 bridge: Arc<GlobalShortcutBridge>,
147 released: bool,
148}
149
150impl std::fmt::Debug for GlobalShortcutRegistration {
151 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 formatter
153 .debug_struct("GlobalShortcutRegistration")
154 .field("id", &self.id)
155 .field("chord", &self.chord)
156 .field("released", &self.released)
157 .finish()
158 }
159}
160
161impl GlobalShortcutRegistration {
162 #[must_use]
163 pub const fn id(&self) -> GlobalShortcutId {
164 self.id
165 }
166
167 #[must_use]
168 pub const fn chord(&self) -> GlobalShortcutChord {
169 self.chord
170 }
171
172 pub fn unregister(
173 mut self,
174 ) -> Result<GlobalShortcutUnregistrationRequest, GlobalShortcutError> {
175 let receiver = self.bridge.unregister(self.id)?;
176 self.released = true;
177 Ok(GlobalShortcutUnregistrationRequest { receiver })
178 }
179}
180
181impl Drop for GlobalShortcutRegistration {
182 fn drop(&mut self) {
183 if !self.released {
184 let _ = self.bridge.release(self.id);
185 self.released = true;
186 }
187 }
188}
189
190#[must_use = "global shortcut unregistration must be awaited or explicitly dropped"]
191pub struct GlobalShortcutUnregistrationRequest {
192 receiver: RequestReceiver<Result<(), GlobalShortcutError>>,
193}
194
195impl Unpin for GlobalShortcutUnregistrationRequest {}
196
197impl Future for GlobalShortcutUnregistrationRequest {
198 type Output = Result<(), GlobalShortcutError>;
199
200 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
201 self.receiver
202 .poll_result(context, || Err(GlobalShortcutError::ApplicationStopped))
203 }
204}
205
206pub(crate) struct QueuedGlobalShortcutRequest {
207 pub(crate) request_id: GlobalShortcutRequestId,
208 pub(crate) operation: NativeGlobalShortcutOperation,
209 pub(crate) reply: ShortcutReply,
210}
211
212pub(crate) struct PendingGlobalShortcutRequest {
213 pub(crate) operation: NativeGlobalShortcutOperation,
214 pub(crate) reply: ShortcutReply,
215}
216
217pub(crate) struct GlobalShortcutBridge {
218 channel: RequestChannel<QueuedGlobalShortcutRequest>,
219}
220impl GlobalShortcutBridge {
221 pub(crate) fn new(sender: mpsc::Sender<QueuedGlobalShortcutRequest>) -> Self {
222 Self {
223 channel: RequestChannel::new(sender),
224 }
225 }
226 fn register(
227 self: &Arc<Self>,
228 id: GlobalShortcutId,
229 chord: GlobalShortcutChord,
230 ) -> Result<
231 RequestReceiver<Result<GlobalShortcutRegistration, GlobalShortcutError>>,
232 GlobalShortcutError,
233 > {
234 self.channel
235 .request(|request_id| {
236 let (sender, receiver) = oneshot::channel();
237 (
238 QueuedGlobalShortcutRequest {
239 request_id: GlobalShortcutRequestId::new(request_id),
240 operation: NativeGlobalShortcutOperation::Register { id, chord },
241 reply: ShortcutReply::Registration {
242 sender,
243 bridge: self.clone(),
244 },
245 },
246 RequestReceiver::new(receiver, || {}),
247 )
248 })
249 .map_err(|_| GlobalShortcutError::ApplicationStopped)
250 }
251 fn unregister(
252 &self,
253 id: GlobalShortcutId,
254 ) -> Result<RequestReceiver<Result<(), GlobalShortcutError>>, GlobalShortcutError> {
255 self.channel
256 .request(|request_id| {
257 let (sender, receiver) = oneshot::channel();
258 (
259 QueuedGlobalShortcutRequest {
260 request_id: GlobalShortcutRequestId::new(request_id),
261 operation: NativeGlobalShortcutOperation::Unregister { id },
262 reply: ShortcutReply::Unregistration(sender),
263 },
264 RequestReceiver::new(receiver, || {}),
265 )
266 })
267 .map_err(|_| GlobalShortcutError::ApplicationStopped)
268 }
269 fn release(&self, id: GlobalShortcutId) -> Result<(), GlobalShortcutError> {
270 self.channel
271 .request(|request_id| {
272 (
273 QueuedGlobalShortcutRequest {
274 request_id: GlobalShortcutRequestId::new(request_id),
275 operation: NativeGlobalShortcutOperation::Unregister { id },
276 reply: ShortcutReply::Detached,
277 },
278 (),
279 )
280 })
281 .map_err(|_| GlobalShortcutError::ApplicationStopped)
282 }
283 pub(crate) fn set_wake(&self, wake: Arc<dyn RuntimeWake>) {
284 self.channel.set_wake(wake);
285 }
286 pub(crate) fn stop(&self) {
287 self.channel.stop();
288 }
289}
290
291pub(crate) fn drain_queued(
292 receiver: &mpsc::Receiver<QueuedGlobalShortcutRequest>,
293 pending: &mut crate::request_registry::RequestRegistry<
294 GlobalShortcutRequestId,
295 PendingGlobalShortcutRequest,
296 >,
297 native: &mut VecDeque<NativeGlobalShortcutRequest>,
298) {
299 while let Ok(queued) = receiver.try_recv() {
300 if matches!(
301 queued.operation,
302 NativeGlobalShortcutOperation::Register { .. }
303 ) && queued.reply.is_abandoned()
304 {
305 continue;
306 }
307 pending.insert(
308 queued.request_id,
309 PendingGlobalShortcutRequest {
310 operation: queued.operation,
311 reply: queued.reply,
312 },
313 );
314 native.push_back(NativeGlobalShortcutRequest {
315 request_id: queued.request_id,
316 operation: queued.operation,
317 });
318 }
319}
320
321pub(crate) enum ShortcutReply {
322 Registration {
323 sender: oneshot::Sender<Result<GlobalShortcutRegistration, GlobalShortcutError>>,
324 bridge: Arc<GlobalShortcutBridge>,
325 },
326 Unregistration(oneshot::Sender<Result<(), GlobalShortcutError>>),
327 Detached,
328}
329impl ShortcutReply {
330 pub(crate) fn is_abandoned(&self) -> bool {
331 match self {
332 Self::Registration { sender, .. } => sender.is_closed(),
333 Self::Unregistration(sender) => sender.is_closed(),
334 Self::Detached => false,
335 }
336 }
337 pub(crate) fn finish(
338 self,
339 operation: NativeGlobalShortcutOperation,
340 result: Result<(), GlobalShortcutError>,
341 ) -> GlobalShortcutCompletionStatus {
342 match self {
343 Self::Registration { sender, bridge } => {
344 let NativeGlobalShortcutOperation::Register { id, chord } = operation else {
345 unreachable!("registration reply requires a register operation");
346 };
347 let result = result.map(|()| GlobalShortcutRegistration {
348 id,
349 chord,
350 bridge,
351 released: false,
352 });
353 if let Err(Ok(mut registration)) = sender.send(result) {
354 registration.released = true;
357 return GlobalShortcutCompletionStatus::AbandonedRegistration(id);
358 }
359 }
360 Self::Unregistration(sender) => {
361 let _ = sender.send(result);
362 }
363 Self::Detached => {}
364 }
365 GlobalShortcutCompletionStatus::Completed
366 }
367}
368
369#[derive(Debug)]
374pub struct MemoryGlobalShortcutAdapter {
375 support: CapabilitySupport,
376 registrations: HashMap<GlobalShortcutId, GlobalShortcutChord>,
377}
378
379impl Default for MemoryGlobalShortcutAdapter {
380 fn default() -> Self {
381 Self::new(CapabilitySupport::Supported)
382 }
383}
384
385impl MemoryGlobalShortcutAdapter {
386 #[must_use]
387 pub fn new(support: CapabilitySupport) -> Self {
388 Self {
389 support,
390 registrations: HashMap::new(),
391 }
392 }
393
394 #[must_use]
395 pub const fn support(&self) -> CapabilitySupport {
396 self.support
397 }
398
399 #[must_use]
400 pub fn registered_chord(&self, id: GlobalShortcutId) -> Option<GlobalShortcutChord> {
401 self.registrations.get(&id).copied()
402 }
403
404 #[must_use]
405 pub fn registration_count(&self) -> usize {
406 self.registrations.len()
407 }
408
409 pub fn respond(
410 &mut self,
411 request: NativeGlobalShortcutRequest,
412 ) -> NativeGlobalShortcutCompletion {
413 let result = if self.support != CapabilitySupport::Supported {
414 Err(GlobalShortcutError::Unsupported)
415 } else {
416 match request.operation {
417 NativeGlobalShortcutOperation::Register { id, chord } => {
418 if self.registrations.contains_key(&id) {
419 Err(GlobalShortcutError::AlreadyRegistered(id))
420 } else if self
421 .registrations
422 .values()
423 .any(|registered| *registered == chord)
424 {
425 Err(GlobalShortcutError::Conflict(chord))
426 } else {
427 self.registrations.insert(id, chord);
428 Ok(())
429 }
430 }
431 NativeGlobalShortcutOperation::Unregister { id } => {
432 if self.registrations.remove(&id).is_some() {
433 Ok(())
434 } else {
435 Err(GlobalShortcutError::NotRegistered(id))
436 }
437 }
438 }
439 };
440 NativeGlobalShortcutCompletion {
441 request_id: request.request_id,
442 result,
443 }
444 }
445
446 #[must_use]
449 pub fn invoke(&self, id: GlobalShortcutId) -> Option<incular_platform::ApplicationActivation> {
450 self.registrations
451 .contains_key(&id)
452 .then_some(incular_platform::ApplicationActivation::GlobalShortcutInvoked(id))
453 }
454}