refactor(global-shortcut): enhance `un/register` to accept an array, remove `un/registerAll` (#1117)

* refactor(shell): enhance `un/register` to accept an array, remove `un/registerAll`

closes #1101

* Update lib.rs

* remove permissions, cleanup docs

* bring back unregister_all

* fmt

* fix build

* bundle

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
pull/1527/head
Amr Bashir 1 year ago committed by GitHub
parent a66549329c
commit 381a466db3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,10 @@
---
"global-shortcut": "patch"
---
Refactored the Rust APIs:
- Renamed `GlobalShortcut::on_all_shortcuts` to `GlobalShortcut::on_shortcuts`
- Renamed `GlobalShortcut::register_all` to `GlobalShortcut::register_multiple`
- Changed `GlobalShortcut::unregister_all` behavior to remove all registerd shortcuts.
- Added `GlobalShortcut::unregister_multiple` to register a list of shortcuts (old behavior of `unregister_all`).

@ -0,0 +1,8 @@
---
"global-shortcut-js": "patch"
---
Refactored the JS APIs:
- Enhanced `register` and `unregister` to take either a single shortcut or an array.
- Removed `registerAll` instead use `register` with an array.

@ -4850,13 +4850,6 @@
"global-shortcut:allow-register" "global-shortcut:allow-register"
] ]
}, },
{
"description": "global-shortcut:allow-register-all -> Enables the register_all command without any pre-configured scope.",
"type": "string",
"enum": [
"global-shortcut:allow-register-all"
]
},
{ {
"description": "global-shortcut:allow-unregister -> Enables the unregister command without any pre-configured scope.", "description": "global-shortcut:allow-unregister -> Enables the unregister command without any pre-configured scope.",
"type": "string", "type": "string",
@ -4885,13 +4878,6 @@
"global-shortcut:deny-register" "global-shortcut:deny-register"
] ]
}, },
{
"description": "global-shortcut:deny-register-all -> Denies the register_all command without any pre-configured scope.",
"type": "string",
"enum": [
"global-shortcut:deny-register-all"
]
},
{ {
"description": "global-shortcut:deny-unregister -> Denies the unregister command without any pre-configured scope.", "description": "global-shortcut:deny-unregister -> Denies the unregister command without any pre-configured scope.",
"type": "string", "type": "string",

@ -1,9 +1,8 @@
<script> <script>
import { writable } from "svelte/store"; import { writable, get } from "svelte/store";
import { import {
register as registerShortcut, register as registerShortcut,
unregister as unregisterShortcut, unregister as unregisterShortcut,
unregisterAll as unregisterAllShortcuts,
} from "@tauri-apps/plugin-global-shortcut"; } from "@tauri-apps/plugin-global-shortcut";
export let onMessage; export let onMessage;
@ -35,7 +34,7 @@
} }
function unregisterAll() { function unregisterAll() {
unregisterAllShortcuts() unregisterShortcut(get(shortcuts))
.then(() => { .then(() => {
shortcuts.update(() => []); shortcuts.update(() => []);
onMessage(`Unregistered all shortcuts`); onMessage(`Unregistered all shortcuts`);

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_GLOBALSHORTCUT__=function(t){"use strict";function e(t,e,r,s){if("a"===r&&!s)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?s:"a"===r?s.call(t):s?s.value:e.get(t)}function r(t,e,r,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,r),r}var s,n,i;"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,s.set(this,(()=>{})),n.set(this,0),i.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:o})=>{if(o===e(this,n,"f")){r(this,n,o+1),e(this,s,"f").call(this,t);const a=Object.keys(e(this,i,"f"));if(a.length>0){let t=o+1;for(const r of a.sort()){if(parseInt(r)!==t)break;{const n=e(this,i,"f")[r];delete e(this,i,"f")[r],e(this,s,"f").call(this,n),t+=1}}r(this,n,t)}}else e(this,i,"f")[o.toString()]=t}))}set onmessage(t){r(this,s,t)}get onmessage(){return e(this,s,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}return s=new WeakMap,n=new WeakMap,i=new WeakMap,t.isRegistered=async function(t){return await a("plugin:global-shortcut|is_registered",{shortcut:t})},t.register=async function(t,e){const r=new o;r.onmessage=e,await a("plugin:global-shortcut|register",{shortcut:t,handler:r})},t.registerAll=async function(t,e){const r=new o;r.onmessage=e,await a("plugin:global-shortcut|register_all",{shortcuts:t,handler:r})},t.unregister=async function(t){await a("plugin:global-shortcut|unregister",{shortcut:t})},t.unregisterAll=async function(){await a("plugin:global-shortcut|unregister_all")},t}({});Object.defineProperty(window.__TAURI__,"globalShortcut",{value:__TAURI_PLUGIN_GLOBALSHORTCUT__})} if("__TAURI__"in window){var __TAURI_PLUGIN_GLOBALSHORTCUT__=function(t){"use strict";function e(t,e,r,s){if("a"===r&&!s)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?s:"a"===r?s.call(t):s?s.value:e.get(t)}function r(t,e,r,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,r),r}var s,n,i;"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,s.set(this,(()=>{})),n.set(this,0),i.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:o})=>{if(o===e(this,n,"f")){r(this,n,o+1),e(this,s,"f").call(this,t);const a=Object.keys(e(this,i,"f"));if(a.length>0){let t=o+1;for(const r of a.sort()){if(parseInt(r)!==t)break;{const n=e(this,i,"f")[r];delete e(this,i,"f")[r],e(this,s,"f").call(this,n),t+=1}}r(this,n,t)}}else e(this,i,"f")[o.toString()]=t}))}set onmessage(t){r(this,s,t)}get onmessage(){return e(this,s,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}return s=new WeakMap,n=new WeakMap,i=new WeakMap,t.isRegistered=async function(t){return await a("plugin:global-shortcut|is_registered",{shortcut:t})},t.register=async function(t,e){const r=new o;return r.onmessage=e,await a("plugin:global-shortcut|register",{shortcuts:Array.isArray(t)?t:[t],handler:r})},t.unregister=async function(t){return await a("plugin:global-shortcut|unregister",{shortcuts:Array.isArray(t)?t:[t]})},t.unregisterAll=async function(){return await a("plugin:global-shortcut|unregister_all",{})},t}({});Object.defineProperty(window.__TAURI__,"globalShortcut",{value:__TAURI_PLUGIN_GLOBALSHORTCUT__})}

@ -2,13 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
const COMMANDS: &[&str] = &[ const COMMANDS: &[&str] = &["register", "unregister", "unregister_all", "is_registered"];
"register",
"register_all",
"unregister",
"unregister_all",
"is_registered",
];
fn main() { fn main() {
tauri_plugin::Builder::new(COMMANDS) tauri_plugin::Builder::new(COMMANDS)

@ -19,15 +19,28 @@ export interface ShortcutEvent {
export type ShortcutHandler = (event: ShortcutEvent) => void; export type ShortcutHandler = (event: ShortcutEvent) => void;
/** /**
* Register a global shortcut. * Register a global shortcut or a list of shortcuts.
*
* The handler is called when any of the registered shortcuts are pressed by the user.
*
* If the shortcut is already taken by another application, the handler will not be triggered.
* Make sure the shortcut is as unique as possible while still taking user experience into consideration.
*
* @example * @example
* ```typescript * ```typescript
* import { register } from '@tauri-apps/plugin-global-shortcut'; * import { register } from '@tauri-apps/plugin-global-shortcut';
*
* // register a single hotkey
* await register('CommandOrControl+Shift+C', (event) => { * await register('CommandOrControl+Shift+C', (event) => {
* if (event.state === "Pressed") { * if (event.state === "Pressed") {
* console.log('Shortcut triggered'); * console.log('Shortcut triggered');
* } * }
* }); * });
*
* // or register multiple hotkeys at once
* await register(['CommandOrControl+Shift+C', 'Alt+A'], (event) => {
* console.log(`Shortcut ${event.shortcut} triggered`);
* });
* ``` * ```
* *
* @param shortcut Shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q * @param shortcut Shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q
@ -36,97 +49,75 @@ export type ShortcutHandler = (event: ShortcutEvent) => void;
* @since 2.0.0 * @since 2.0.0
*/ */
async function register( async function register(
shortcut: string, shortcuts: string | string[],
handler: ShortcutHandler, handler: ShortcutHandler,
): Promise<void> { ): Promise<void> {
const h = new Channel<ShortcutEvent>(); const h = new Channel<ShortcutEvent>();
h.onmessage = handler; h.onmessage = handler;
await invoke("plugin:global-shortcut|register", { return await invoke("plugin:global-shortcut|register", {
shortcut, shortcuts: Array.isArray(shortcuts) ? shortcuts : [shortcuts],
handler: h, handler: h,
}); });
} }
/** /**
* Register a collection of global shortcuts. * Unregister a global shortcut or a list of shortcuts.
*
* @example * @example
* ```typescript * ```typescript
* import { registerAll } from '@tauri-apps/plugin-global-shortcut'; * import { unregister } from '@tauri-apps/plugin-global-shortcut';
* await registerAll(['CommandOrControl+Shift+C', 'Ctrl+Alt+F12'], (event) => { *
* console.log(`Shortcut ${event.shortcut} ${event.state}`); * // unregister a single hotkey
* }); * await unregister('CmdOrControl+Space');
*
* // or unregister multiple hotkeys at the same time
* await unregister(['CmdOrControl+Space', 'Alt+A']);
* ``` * ```
* *
* @param shortcuts Array of shortcut definitions, modifiers and key separated by "+" e.g. CmdOrControl+Q * @param shortcut shortcut definition (modifiers and key separated by "+" e.g. CmdOrControl+Q), also accepts a list of shortcuts
* @param handler Shortcut handler callback - takes the triggered shortcut as argument
* *
* @since 2.0.0 * @since 2.0.0
*/ */
async function registerAll( async function unregister(shortcuts: string | string[]): Promise<void> {
shortcuts: string[], return await invoke("plugin:global-shortcut|unregister", {
handler: ShortcutHandler, shortcuts: Array.isArray(shortcuts) ? shortcuts : [shortcuts],
): Promise<void> {
const h = new Channel<ShortcutEvent>();
h.onmessage = handler;
await invoke("plugin:global-shortcut|register_all", {
shortcuts,
handler: h,
}); });
} }
/** /**
* Determines whether the given shortcut is registered by this application or not. * Unregister all global shortcuts.
*
* If the shortcut is registered by another application, it will still return `false`.
* *
* @example * @example
* ```typescript * ```typescript
* import { isRegistered } from '@tauri-apps/plugin-global-shortcut'; * import { unregisterAll } from '@tauri-apps/plugin-global-shortcut';
* const isRegistered = await isRegistered('CommandOrControl+P'); * await unregisterAll();
* ``` * ```
*
* @param shortcut shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q
*
* @since 2.0.0 * @since 2.0.0
*/ */
async function isRegistered(shortcut: string): Promise<boolean> { async function unregisterAll(): Promise<void> {
return await invoke("plugin:global-shortcut|is_registered", { return await invoke("plugin:global-shortcut|unregister_all", {});
shortcut,
});
} }
/** /**
* Unregister a global shortcut. * Determines whether the given shortcut is registered by this application or not.
*
* If the shortcut is registered by another application, it will still return `false`.
*
* @example * @example
* ```typescript * ```typescript
* import { unregister } from '@tauri-apps/plugin-global-shortcut'; * import { isRegistered } from '@tauri-apps/plugin-global-shortcut';
* await unregister('CmdOrControl+Space'); * const isRegistered = await isRegistered('CommandOrControl+P');
* ``` * ```
* *
* @param shortcut shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q * @param shortcut shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q
* *
* @since 2.0.0 * @since 2.0.0
*/ */
async function unregister(shortcut: string): Promise<void> { async function isRegistered(shortcut: string): Promise<boolean> {
await invoke("plugin:global-shortcut|unregister", { return await invoke("plugin:global-shortcut|is_registered", {
shortcut, shortcut,
}); });
} }
/** export { register, unregister, unregisterAll, isRegistered };
* Unregisters all shortcuts registered by the application.
* @example
* ```typescript
* import { unregisterAll } from '@tauri-apps/plugin-global-shortcut';
* await unregisterAll();
* ```
*
* @since 2.0.0
*/
async function unregisterAll(): Promise<void> {
await invoke("plugin:global-shortcut|unregister_all");
}
export { register, registerAll, isRegistered, unregister, unregisterAll };

@ -29,7 +29,7 @@ use serde::Serialize;
use tauri::{ use tauri::{
ipc::Channel, ipc::Channel,
plugin::{Builder as PluginBuilder, TauriPlugin}, plugin::{Builder as PluginBuilder, TauriPlugin},
AppHandle, Manager, Runtime, State, Window, AppHandle, Manager, Runtime, State,
}; };
mod error; mod error;
@ -84,7 +84,7 @@ impl<R: Runtime> GlobalShortcut<R> {
Ok(()) Ok(())
} }
fn register_all_internal<S, F>(&self, shortcuts: S, handler: Option<F>) -> Result<()> fn register_multiple_internal<S, F>(&self, shortcuts: S, handler: Option<F>) -> Result<()>
where where
S: IntoIterator<Item = Shortcut>, S: IntoIterator<Item = Shortcut>,
F: Fn(&AppHandle<R>, &Shortcut, ShortcutEvent) + Send + Sync + 'static, F: Fn(&AppHandle<R>, &Shortcut, ShortcutEvent) + Send + Sync + 'static,
@ -107,7 +107,9 @@ impl<R: Runtime> GlobalShortcut<R> {
Ok(()) Ok(())
} }
}
impl<R: Runtime> GlobalShortcut<R> {
/// Register a shortcut. /// Register a shortcut.
pub fn register<S>(&self, shortcut: S) -> Result<()> pub fn register<S>(&self, shortcut: S) -> Result<()>
where where
@ -131,7 +133,7 @@ impl<R: Runtime> GlobalShortcut<R> {
} }
/// Register multiple shortcuts. /// Register multiple shortcuts.
pub fn register_all<S, T>(&self, shortcuts: S) -> Result<()> pub fn register_multiple<S, T>(&self, shortcuts: S) -> Result<()>
where where
S: IntoIterator<Item = T>, S: IntoIterator<Item = T>,
T: TryInto<ShortcutWrapper>, T: TryInto<ShortcutWrapper>,
@ -141,11 +143,11 @@ impl<R: Runtime> GlobalShortcut<R> {
for shortcut in shortcuts { for shortcut in shortcuts {
s.push(try_into_shortcut(shortcut)?); s.push(try_into_shortcut(shortcut)?);
} }
self.register_all_internal(s, None::<fn(&AppHandle<R>, &Shortcut, ShortcutEvent)>) self.register_multiple_internal(s, None::<fn(&AppHandle<R>, &Shortcut, ShortcutEvent)>)
} }
/// Register multiple shortcuts with a handler. /// Register multiple shortcuts with a handler.
pub fn on_all_shortcuts<S, T, F>(&self, shortcuts: S, handler: F) -> Result<()> pub fn on_shortcuts<S, T, F>(&self, shortcuts: S, handler: F) -> Result<()>
where where
S: IntoIterator<Item = T>, S: IntoIterator<Item = T>,
T: TryInto<ShortcutWrapper>, T: TryInto<ShortcutWrapper>,
@ -156,9 +158,10 @@ impl<R: Runtime> GlobalShortcut<R> {
for shortcut in shortcuts { for shortcut in shortcuts {
s.push(try_into_shortcut(shortcut)?); s.push(try_into_shortcut(shortcut)?);
} }
self.register_all_internal(s, Some(handler)) self.register_multiple_internal(s, Some(handler))
} }
/// Unregister a shortcut
pub fn unregister<S: TryInto<ShortcutWrapper>>(&self, shortcut: S) -> Result<()> pub fn unregister<S: TryInto<ShortcutWrapper>>(&self, shortcut: S) -> Result<()>
where where
S::Error: std::error::Error, S::Error: std::error::Error,
@ -169,7 +172,8 @@ impl<R: Runtime> GlobalShortcut<R> {
Ok(()) Ok(())
} }
pub fn unregister_all<T: TryInto<ShortcutWrapper>, S: IntoIterator<Item = T>>( /// Unregister multiple shortcuts.
pub fn unregister_multiple<T: TryInto<ShortcutWrapper>, S: IntoIterator<Item = T>>(
&self, &self,
shortcuts: S, shortcuts: S,
) -> Result<()> ) -> Result<()>
@ -191,6 +195,16 @@ impl<R: Runtime> GlobalShortcut<R> {
Ok(()) Ok(())
} }
/// Unregister all registered shortcuts.
pub fn unregister_all(&self) -> Result<()> {
let mut shortcuts = self.shortcuts.lock().unwrap();
let hotkeys = std::mem::take(&mut *shortcuts);
let hotkeys = hotkeys.values().map(|s| s.shortcut).collect::<Vec<_>>();
self.manager
.unregister_all(hotkeys.as_slice())
.map_err(Into::into)
}
/// Determines whether the given shortcut is registered by this application or not. /// Determines whether the given shortcut is registered by this application or not.
/// ///
/// If the shortcut is registered by another application, it will still return `false`. /// If the shortcut is registered by another application, it will still return `false`.
@ -239,29 +253,7 @@ struct ShortcutJsEvent {
#[tauri::command] #[tauri::command]
fn register<R: Runtime>( fn register<R: Runtime>(
_window: Window<R>, _app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>,
shortcut: String,
handler: Channel,
) -> Result<()> {
global_shortcut.register_internal(
parse_shortcut(shortcut)?,
Some(
move |_app: &AppHandle<R>, shortcut: &Shortcut, e: ShortcutEvent| {
let js_event = ShortcutJsEvent {
id: e.id,
state: e.state,
shortcut: shortcut.into_string(),
};
let _ = handler.send(js_event);
},
),
)
}
#[tauri::command]
fn register_all<R: Runtime>(
_window: Window<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: State<'_, GlobalShortcut<R>>,
shortcuts: Vec<String>, shortcuts: Vec<String>,
handler: Channel, handler: Channel,
@ -275,7 +267,7 @@ fn register_all<R: Runtime>(
hotkeys.push(hotkey); hotkeys.push(hotkey);
} }
global_shortcut.register_all_internal( global_shortcut.register_multiple_internal(
hotkeys, hotkeys,
Some( Some(
move |_app: &AppHandle<R>, shortcut: &Shortcut, e: ShortcutEvent| { move |_app: &AppHandle<R>, shortcut: &Shortcut, e: ShortcutEvent| {
@ -294,22 +286,21 @@ fn register_all<R: Runtime>(
fn unregister<R: Runtime>( fn unregister<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: State<'_, GlobalShortcut<R>>,
shortcut: String, shortcuts: Vec<String>,
) -> Result<()> { ) -> Result<()> {
global_shortcut.unregister(parse_shortcut(shortcut)?) let mut hotkeys = Vec::new();
for shortcut in shortcuts {
hotkeys.push(parse_shortcut(&shortcut)?);
}
global_shortcut.unregister_multiple(hotkeys)
} }
#[tauri::command] #[tauri::command]
fn unregister_all<R: Runtime>( fn unregister_all<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: State<'_, GlobalShortcut<R>>,
shortcuts: Vec<String>,
) -> Result<()> { ) -> Result<()> {
let mut hotkeys = Vec::new(); global_shortcut.unregister_all()
for shortcut in shortcuts {
hotkeys.push(parse_shortcut(&shortcut)?);
}
global_shortcut.unregister_all(hotkeys)
} }
#[tauri::command] #[tauri::command]
@ -379,10 +370,9 @@ impl<R: Runtime> Builder<R> {
PluginBuilder::new("global-shortcut") PluginBuilder::new("global-shortcut")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
register, register,
register_all,
unregister, unregister,
unregister_all, unregister_all,
is_registered is_registered,
]) ])
.setup(move |app, _api| { .setup(move |app, _api| {
let manager = GlobalHotKeyManager::new()?; let manager = GlobalHotKeyManager::new()?;

Loading…
Cancel
Save