瀏覽代碼

feat(core): globalShortcut API (#1232)

Lucas Fernandes Nogueira 4 年之前
父節點
當前提交
855effadd9

+ 7 - 0
.changes/shortcut-api.md

@@ -0,0 +1,7 @@
+---
+"api": minor
+"tauri-api": minor
+"tauri": minor
+---
+
+Adds a global shortcut API.

+ 2 - 1
api/package.json

@@ -14,7 +14,8 @@
     "./notification": "./dist/notification.js",
     "./tauri": "./dist/tauri.js",
     "./window": "./dist/window.js",
-    "./shell": "./dist/shell.js"
+    "./shell": "./dist/shell.js",
+    "./globalShortcut": "./dist/globalShortcut.js"
   },
   "funding": {
     "type": "opencollective",

+ 2 - 1
api/rollup.config.js

@@ -20,7 +20,8 @@ export default [
       tauri: './src/tauri.ts',
       window: './src/window.ts',
       cli: './src/cli.ts',
-      notification: './src/notification.ts'
+      notification: './src/notification.ts',
+      globalShortcut: './src/globalShortcut.ts'
     },
     treeshake: true,
     perf: true,

+ 3 - 1
api/src/bundle.ts

@@ -9,6 +9,7 @@ import * as shell from './shell'
 import * as tauri from './tauri'
 import * as window from './window'
 import * as notification from './notification'
+import * as globalShortcut from './globalShortcut'
 
 export {
   cli,
@@ -20,5 +21,6 @@ export {
   shell,
   tauri,
   window,
-  notification
+  notification,
+  globalShortcut
 }

+ 39 - 0
api/src/globalShortcut.ts

@@ -0,0 +1,39 @@
+import { promisified, transformCallback } from './tauri'
+
+/**
+ * register a global shortcut
+ * @param shortcut shortcut definition, modifiers and key separated by "+" e.g. Alt+Q
+ * @param handler shortcut handler callback
+ */
+async function registerShortcut(
+  shortcut: string,
+  handler: () => void
+): Promise<void> {
+  return await promisified({
+    module: 'GlobalShortcut',
+    message: {
+      cmd: 'register',
+      shortcut,
+      handler: transformCallback(handler)
+    }
+  })
+}
+
+/**
+ * unregister a global shortcut
+ * @param shortcut shortcut definition, modifiers and key separated by "+" e.g. Alt+Q
+ */
+async function unregisterShortcut(shortcut: string): Promise<void> {
+  return await promisified({
+    module: 'GlobalShortcut',
+    message: {
+      cmd: 'unregister',
+      shortcut
+    }
+  })
+}
+
+export {
+  registerShortcut,
+  unregisterShortcut
+}

+ 1 - 8
api/src/http.ts

@@ -155,11 +155,4 @@ async function deleteRequest<T>(
   })
 }
 
-export {
-  request,
-  get,
-  post,
-  put,
-  patch,
-  deleteRequest as httpDelete,
-}
+export { request, get, post, put, patch, deleteRequest as httpDelete }

+ 1 - 1
api/src/window.ts

@@ -187,7 +187,7 @@ function resize(width: number, height: number): void {
     message: {
       cmd: 'resize',
       width,
-      height,
+      height
     }
   })
 }

+ 2 - 0
tauri-api/Cargo.toml

@@ -35,6 +35,7 @@ tauri-utils = { version = "0.5", path = "../tauri-utils" }
 clap = { version = "=3.0.0-beta.2", optional = true }
 notify-rust = { version = "4.2.2", optional = true }
 once_cell = "1.5.2"
+tauri-hotkey = { git = "https://github.com/tauri-apps/tauri-hotkey-rs", branch = "dev", optional = true }
 
 [dev-dependencies]
 quickcheck = "1.0.3"
@@ -43,3 +44,4 @@ quickcheck_macros = "1.0.0"
 [features]
 cli = [ "clap" ]
 notification = [ "notify-rust" ]
+global-shortcut = [ "tauri-hotkey" ]

+ 4 - 0
tauri-api/src/error.rs

@@ -53,6 +53,10 @@ pub enum Error {
   #[cfg(feature = "cli")]
   #[error("failed to parse CLI arguments: {0}")]
   ParseCliArguments(#[from] clap::Error),
+  /// Shortcut error.
+  #[cfg(feature = "global-shortcut")]
+  #[error("shortcut error: {0}")]
+  Shortcut(#[from] tauri_hotkey::Error),
 }
 
 impl From<attohttpc::StatusCode> for Error {

+ 4 - 0
tauri-api/src/lib.rs

@@ -30,6 +30,10 @@ pub mod cli;
 #[macro_use]
 extern crate clap;
 
+/// Global shortcuts interface.
+#[cfg(feature = "global-shortcut")]
+pub mod shortcuts;
+
 /// The desktop notifications API module.
 #[cfg(feature = "notification")]
 pub mod notification;

+ 30 - 0
tauri-api/src/shortcuts.rs

@@ -0,0 +1,30 @@
+use tauri_hotkey::{parse_hotkey, HotkeyManager};
+
+/// The shortcut manager builder.
+#[derive(Default)]
+pub struct ShortcutManager(HotkeyManager);
+
+impl ShortcutManager {
+  /// Initializes a new instance of the shortcut manager.
+  pub fn new() -> Self {
+    Default::default()
+  }
+
+  /// Registers a new shortcut handler.
+  pub fn register_shortcut<H: FnMut() + Send + 'static>(
+    &mut self,
+    shortcut: String,
+    handler: H,
+  ) -> crate::Result<()> {
+    let hotkey = parse_hotkey(&shortcut.to_uppercase().replace(" ", ""))?;
+    self.0.register(hotkey, handler)?;
+    Ok(())
+  }
+
+  /// Unregister a previously registered shortcut handler.
+  pub fn unregister_shortcut(&mut self, shortcut: String) -> crate::Result<()> {
+    let hotkey = parse_hotkey(&shortcut.to_uppercase().replace(" ", ""))?;
+    self.0.unregister(&hotkey)?;
+    Ok(())
+  }
+}

+ 4 - 1
tauri/Cargo.toml

@@ -49,7 +49,7 @@ serde = { version = "1.0", features = [ "derive" ] }
 [features]
 cli = [ "tauri-api/cli" ]
 embedded-server = [ "tiny_http" ]
-all-api = [ "tauri-api/notification" ]
+all-api = [ "tauri-api/notification", "tauri-api/global-shortcut" ]
 updater = [ ]
 
 # FS
@@ -83,6 +83,9 @@ http-request = [ ]
 # notification
 notification = [ "tauri-api/notification" ]
 
+# global shortcut
+global-shortcut = [ "tauri-api/global-shortcut" ]
+
 [[example]]
 name = "communication"
 path = "examples/communication/src-tauri/src/main.rs"

+ 3 - 0
tauri/build.rs

@@ -44,5 +44,8 @@ fn main() {
 
     // notification
     notification: { any(all_api, feature = "notification") },
+
+    // global shortcut
+    global_shortcut: { any(all_api, feature = "global_shortcut" )},
   }
 }

+ 81 - 0
tauri/examples/api/src-tauri/Cargo.lock

@@ -27,6 +27,15 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
 
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "andrew"
 version = "0.3.1"
@@ -2105,6 +2114,24 @@ dependencies = [
  "redox_syscall 0.2.4",
 ]
 
+[[package]]
+name = "regex"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+
 [[package]]
 name = "remove_dir_all"
 version = "0.5.3"
@@ -2378,6 +2405,12 @@ version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b"
 
+[[package]]
+name = "strum"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
+
 [[package]]
 name = "strum_macros"
 version = "0.8.0"
@@ -2400,6 +2433,18 @@ dependencies = [
  "syn 1.0.60",
 ]
 
+[[package]]
+name = "strum_macros"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote 1.0.8",
+ "syn 1.0.60",
+]
+
 [[package]]
 name = "syn"
 version = "0.11.11"
@@ -2515,6 +2560,7 @@ dependencies = [
  "serde_repr",
  "tar",
  "tauri-dialog",
+ "tauri-hotkey",
  "tauri-utils",
  "tempfile",
  "thiserror",
@@ -2540,6 +2586,32 @@ dependencies = [
  "pkg-config",
 ]
 
+[[package]]
+name = "tauri-hotkey"
+version = "0.0.0"
+source = "git+https://github.com/tauri-apps/tauri-hotkey-rs?branch=dev#48cda6dc66fbfc6f54c1aca3c14df9490f898729"
+dependencies = [
+ "log",
+ "once_cell",
+ "regex",
+ "serde",
+ "strum 0.20.0",
+ "strum_macros 0.20.1",
+ "tauri-hotkey-sys",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-hotkey-sys"
+version = "0.0.0"
+source = "git+https://github.com/tauri-apps/tauri-hotkey-rs?branch=dev#48cda6dc66fbfc6f54c1aca3c14df9490f898729"
+dependencies = [
+ "cc",
+ "thiserror",
+ "winapi 0.3.9",
+ "x11-dl",
+]
+
 [[package]]
 name = "tauri-macros"
 version = "0.1.0"
@@ -2618,6 +2690,15 @@ dependencies = [
  "syn 1.0.60",
 ]
 
+[[package]]
+name = "thread_local"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+dependencies = [
+ "once_cell",
+]
+
 [[package]]
 name = "tiff"
 version = "0.6.1"

+ 4 - 0
tauri/examples/api/src/App.svelte

@@ -9,6 +9,7 @@
   import Http from './components/Http.svelte'
   import Notifications from './components/Notifications.svelte'
   import Window from './components/Window.svelte'
+  import Shortcuts from './components/Shortcuts.svelte'
 
   const views = [{
     label: 'Messages',
@@ -31,6 +32,9 @@
   }, {
     label: 'Window',
     component: Window
+  }, {
+    label: 'Shortcuts',
+    component: Shortcuts
   }]
 
   let selected = views[0].label;

+ 41 - 0
tauri/examples/api/src/components/Shortcuts.svelte

@@ -0,0 +1,41 @@
+<script>
+  import { writable } from 'svelte/store'
+  import { registerShortcut, unregisterShortcut } from '@tauri-apps/api/globalShortcut'
+
+  export let onMessage
+  const shortcuts = writable([])
+  let shortcut = 'CTRL+X'
+
+  function register() {
+    const shortcut_ = shortcut
+    registerShortcut(shortcut_, () => {
+      onMessage(`Shortcut ${shortcut_} triggered`)
+    }).then(() => {
+      shortcuts.update(shortcuts_ => [...shortcuts_, shortcut_])
+      onMessage(`Shortcut ${shortcut_} registered successfully`)
+    }).catch(onMessage)
+  }
+
+  function unregister(shortcut) {
+    const shortcut_ = shortcut
+    unregisterShortcut(shortcut_).then(() => {
+      shortcuts.update(shortcuts_ => shortcuts_.filter(s => s !== shortcut_))
+      onMessage(`Shortcut ${shortcut_} unregistered`)
+    }).catch(onMessage)
+  }
+</script>
+
+<div style="margin-top: 24px">
+  <div>
+    <input placeholder="Type a shortcut with '+' as separator..." bind:value={shortcut}>
+    <button type="button" on:click={register}>Register</button>
+  </div>
+  <div>
+    {#each $shortcuts as savedShortcut}
+    <div>
+      {savedShortcut}
+      <button type="button" on:click={()=> unregister(savedShortcut)}>Unregister</button>
+    </div>
+    {/each}
+  </div>
+</div>

文件差異過大導致無法顯示
+ 0 - 0
tauri/examples/communication/dist/__tauri.js


+ 81 - 0
tauri/examples/communication/src-tauri/Cargo.lock

@@ -27,6 +27,15 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
 
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "andrew"
 version = "0.3.1"
@@ -2105,6 +2114,24 @@ dependencies = [
  "redox_syscall 0.2.4",
 ]
 
+[[package]]
+name = "regex"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+
 [[package]]
 name = "remove_dir_all"
 version = "0.5.3"
@@ -2378,6 +2405,12 @@ version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b"
 
+[[package]]
+name = "strum"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
+
 [[package]]
 name = "strum_macros"
 version = "0.8.0"
@@ -2400,6 +2433,18 @@ dependencies = [
  "syn 1.0.60",
 ]
 
+[[package]]
+name = "strum_macros"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote 1.0.8",
+ "syn 1.0.60",
+]
+
 [[package]]
 name = "syn"
 version = "0.11.11"
@@ -2515,6 +2560,7 @@ dependencies = [
  "serde_repr",
  "tar",
  "tauri-dialog",
+ "tauri-hotkey",
  "tauri-utils",
  "tempfile",
  "thiserror",
@@ -2540,6 +2586,32 @@ dependencies = [
  "pkg-config",
 ]
 
+[[package]]
+name = "tauri-hotkey"
+version = "0.0.0"
+source = "git+https://github.com/tauri-apps/tauri-hotkey-rs?branch=dev#48cda6dc66fbfc6f54c1aca3c14df9490f898729"
+dependencies = [
+ "log",
+ "once_cell",
+ "regex",
+ "serde",
+ "strum 0.20.0",
+ "strum_macros 0.20.1",
+ "tauri-hotkey-sys",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-hotkey-sys"
+version = "0.0.0"
+source = "git+https://github.com/tauri-apps/tauri-hotkey-rs?branch=dev#48cda6dc66fbfc6f54c1aca3c14df9490f898729"
+dependencies = [
+ "cc",
+ "thiserror",
+ "winapi 0.3.9",
+ "x11-dl",
+]
+
 [[package]]
 name = "tauri-macros"
 version = "0.1.0"
@@ -2618,6 +2690,15 @@ dependencies = [
  "syn 1.0.60",
 ]
 
+[[package]]
+name = "thread_local"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+dependencies = [
+ "once_cell",
+]
+
 [[package]]
 name = "tiff"
 version = "0.6.1"

文件差異過大導致無法顯示
+ 0 - 0
tauri/examples/multiwindow/dist/__tauri.js


+ 81 - 0
tauri/examples/multiwindow/src-tauri/Cargo.lock

@@ -27,6 +27,15 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
 
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "andrew"
 version = "0.3.1"
@@ -2038,6 +2047,24 @@ dependencies = [
  "redox_syscall 0.2.4",
 ]
 
+[[package]]
+name = "regex"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+
 [[package]]
 name = "remove_dir_all"
 version = "0.5.3"
@@ -2305,6 +2332,12 @@ version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b"
 
+[[package]]
+name = "strum"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
+
 [[package]]
 name = "strum_macros"
 version = "0.8.0"
@@ -2327,6 +2360,18 @@ dependencies = [
  "syn 1.0.60",
 ]
 
+[[package]]
+name = "strum_macros"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote 1.0.8",
+ "syn 1.0.60",
+]
+
 [[package]]
 name = "syn"
 version = "0.11.11"
@@ -2441,6 +2486,7 @@ dependencies = [
  "serde_repr",
  "tar",
  "tauri-dialog",
+ "tauri-hotkey",
  "tauri-utils",
  "tempfile",
  "thiserror",
@@ -2466,6 +2512,32 @@ dependencies = [
  "pkg-config",
 ]
 
+[[package]]
+name = "tauri-hotkey"
+version = "0.0.0"
+source = "git+https://github.com/tauri-apps/tauri-hotkey-rs?branch=dev#48cda6dc66fbfc6f54c1aca3c14df9490f898729"
+dependencies = [
+ "log",
+ "once_cell",
+ "regex",
+ "serde",
+ "strum 0.20.0",
+ "strum_macros 0.20.1",
+ "tauri-hotkey-sys",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-hotkey-sys"
+version = "0.0.0"
+source = "git+https://github.com/tauri-apps/tauri-hotkey-rs?branch=dev#48cda6dc66fbfc6f54c1aca3c14df9490f898729"
+dependencies = [
+ "cc",
+ "thiserror",
+ "winapi 0.3.9",
+ "x11-dl",
+]
+
 [[package]]
 name = "tauri-macros"
 version = "0.1.0"
@@ -2526,6 +2598,15 @@ dependencies = [
  "syn 1.0.60",
 ]
 
+[[package]]
+name = "thread_local"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+dependencies = [
+ "once_cell",
+]
+
 [[package]]
 name = "tiff"
 version = "0.6.1"

+ 4 - 0
tauri/src/endpoints.rs

@@ -4,6 +4,8 @@ mod dialog;
 mod event;
 #[allow(unused_imports)]
 mod file_system;
+#[cfg(global_shortcut)]
+mod global_shortcut;
 #[cfg(http_request)]
 mod http;
 mod internal;
@@ -37,6 +39,7 @@ enum Module {
   Cli(cli::Cmd),
   Notification(notification::Cmd),
   Http(http::Cmd),
+  GlobalShortcut(global_shortcut::Cmd),
 }
 
 impl Module {
@@ -55,6 +58,7 @@ impl Module {
       Self::Cli(cmd) => cmd.run(webview_manager, context).await,
       Self::Notification(cmd) => cmd.run(webview_manager, context).await?,
       Self::Http(cmd) => cmd.run(webview_manager).await,
+      Self::GlobalShortcut(cmd) => cmd.run(webview_manager).await?,
     }
     Ok(())
   }

+ 85 - 0
tauri/src/endpoints/global_shortcut.rs

@@ -0,0 +1,85 @@
+use crate::{api::shortcuts::ShortcutManager, async_runtime::Mutex};
+use once_cell::sync::Lazy;
+use serde::Deserialize;
+
+use std::sync::Arc;
+
+type ShortcutManagerHandle = Arc<Mutex<ShortcutManager>>;
+
+pub fn manager_handle() -> &'static ShortcutManagerHandle {
+  static MANAGER: Lazy<ShortcutManagerHandle> = Lazy::new(Default::default);
+  &MANAGER
+}
+
+/// The API descriptor.
+#[derive(Deserialize)]
+#[serde(tag = "cmd", rename_all = "camelCase")]
+pub enum Cmd {
+  /// Register a global shortcut.
+  Register {
+    shortcut: String,
+    handler: String,
+    callback: String,
+    error: String,
+  },
+  /// Unregister a global shortcut.
+  Unregister {
+    shortcut: String,
+    callback: String,
+    error: String,
+  },
+}
+
+impl Cmd {
+  pub async fn run<D: crate::ApplicationDispatcherExt + 'static>(
+    self,
+    webview_manager: &crate::WebviewManager<D>,
+  ) -> crate::Result<()> {
+    #[cfg(not(global_shortcut))]
+    super::allowlist_error(webview_manager, error, "globalShortcut");
+    #[cfg(global_shortcut)]
+    match self {
+      Self::Register {
+        shortcut,
+        handler,
+        callback,
+        error,
+      } => {
+        let dispatcher = webview_manager.current_webview()?.clone();
+        crate::execute_promise(
+          webview_manager,
+          async move {
+            let mut manager = manager_handle().lock().await;
+            manager.register_shortcut(shortcut, move || {
+              let callback_string =
+                crate::api::rpc::format_callback(handler.to_string(), serde_json::Value::Null);
+              dispatcher.eval(callback_string.as_str());
+            })?;
+            Ok(())
+          },
+          callback,
+          error,
+        )
+        .await;
+      }
+      Self::Unregister {
+        shortcut,
+        callback,
+        error,
+      } => {
+        crate::execute_promise(
+          webview_manager,
+          async move {
+            let mut manager = manager_handle().lock().await;
+            manager.unregister_shortcut(shortcut)?;
+            Ok(())
+          },
+          callback,
+          error,
+        )
+        .await;
+      }
+    }
+    Ok(())
+  }
+}

部分文件因文件數量過多而無法顯示