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>
This commit is contained in:
Amr Bashir
2024-07-08 19:20:00 +03:00
committed by GitHub
parent a66549329c
commit 381a466db3
9 changed files with 101 additions and 123 deletions

View File

@@ -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`).

View File

@@ -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.

View File

@@ -4850,13 +4850,6 @@
"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.",
"type": "string",
@@ -4885,13 +4878,6 @@
"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.",
"type": "string",

View File

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

View File

@@ -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__})}

View File

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

View File

@@ -19,15 +19,28 @@ export interface ShortcutEvent {
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
* ```typescript
* import { register } from '@tauri-apps/plugin-global-shortcut';
*
* // register a single hotkey
* await register('CommandOrControl+Shift+C', (event) => {
* if (event.state === "Pressed") {
* 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
@@ -36,46 +49,56 @@ export type ShortcutHandler = (event: ShortcutEvent) => void;
* @since 2.0.0
*/
async function register(
shortcut: string,
shortcuts: string | string[],
handler: ShortcutHandler,
): Promise<void> {
const h = new Channel<ShortcutEvent>();
h.onmessage = handler;
await invoke("plugin:global-shortcut|register", {
shortcut,
return await invoke("plugin:global-shortcut|register", {
shortcuts: Array.isArray(shortcuts) ? shortcuts : [shortcuts],
handler: h,
});
}
/**
* Register a collection of global shortcuts.
* Unregister a global shortcut or a list of shortcuts.
*
* @example
* ```typescript
* import { registerAll } from '@tauri-apps/plugin-global-shortcut';
* await registerAll(['CommandOrControl+Shift+C', 'Ctrl+Alt+F12'], (event) => {
* console.log(`Shortcut ${event.shortcut} ${event.state}`);
* });
* import { unregister } from '@tauri-apps/plugin-global-shortcut';
*
* // 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 handler Shortcut handler callback - takes the triggered shortcut as argument
* @param shortcut shortcut definition (modifiers and key separated by "+" e.g. CmdOrControl+Q), also accepts a list of shortcuts
*
* @since 2.0.0
*/
async function registerAll(
shortcuts: string[],
handler: ShortcutHandler,
): Promise<void> {
const h = new Channel<ShortcutEvent>();
h.onmessage = handler;
await invoke("plugin:global-shortcut|register_all", {
shortcuts,
handler: h,
async function unregister(shortcuts: string | string[]): Promise<void> {
return await invoke("plugin:global-shortcut|unregister", {
shortcuts: Array.isArray(shortcuts) ? shortcuts : [shortcuts],
});
}
/**
* Unregister all global shortcuts.
*
* @example
* ```typescript
* import { unregisterAll } from '@tauri-apps/plugin-global-shortcut';
* await unregisterAll();
* ```
* @since 2.0.0
*/
async function unregisterAll(): Promise<void> {
return await invoke("plugin:global-shortcut|unregister_all", {});
}
/**
* Determines whether the given shortcut is registered by this application or not.
*
@@ -97,36 +120,4 @@ async function isRegistered(shortcut: string): Promise<boolean> {
});
}
/**
* Unregister a global shortcut.
* @example
* ```typescript
* import { unregister } from '@tauri-apps/plugin-global-shortcut';
* await unregister('CmdOrControl+Space');
* ```
*
* @param shortcut shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q
*
* @since 2.0.0
*/
async function unregister(shortcut: string): Promise<void> {
await invoke("plugin:global-shortcut|unregister", {
shortcut,
});
}
/**
* 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 };
export { register, unregister, unregisterAll, isRegistered };

View File

@@ -2,7 +2,7 @@
[default]
description = """
No features are enabled by default, as we believe
the shortcuts can be inherently dangerous and it is
the shortcuts can be inherently dangerous and it is
application specific if specific shortcuts should be
registered or unregistered.
"""

View File

@@ -29,7 +29,7 @@ use serde::Serialize;
use tauri::{
ipc::Channel,
plugin::{Builder as PluginBuilder, TauriPlugin},
AppHandle, Manager, Runtime, State, Window,
AppHandle, Manager, Runtime, State,
};
mod error;
@@ -84,7 +84,7 @@ impl<R: Runtime> GlobalShortcut<R> {
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
S: IntoIterator<Item = Shortcut>,
F: Fn(&AppHandle<R>, &Shortcut, ShortcutEvent) + Send + Sync + 'static,
@@ -107,7 +107,9 @@ impl<R: Runtime> GlobalShortcut<R> {
Ok(())
}
}
impl<R: Runtime> GlobalShortcut<R> {
/// Register a shortcut.
pub fn register<S>(&self, shortcut: S) -> Result<()>
where
@@ -131,7 +133,7 @@ impl<R: Runtime> GlobalShortcut<R> {
}
/// Register multiple shortcuts.
pub fn register_all<S, T>(&self, shortcuts: S) -> Result<()>
pub fn register_multiple<S, T>(&self, shortcuts: S) -> Result<()>
where
S: IntoIterator<Item = T>,
T: TryInto<ShortcutWrapper>,
@@ -141,11 +143,11 @@ impl<R: Runtime> GlobalShortcut<R> {
for shortcut in shortcuts {
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.
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
S: IntoIterator<Item = T>,
T: TryInto<ShortcutWrapper>,
@@ -156,9 +158,10 @@ impl<R: Runtime> GlobalShortcut<R> {
for shortcut in shortcuts {
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<()>
where
S::Error: std::error::Error,
@@ -169,7 +172,8 @@ impl<R: Runtime> GlobalShortcut<R> {
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,
shortcuts: S,
) -> Result<()>
@@ -191,6 +195,16 @@ impl<R: Runtime> GlobalShortcut<R> {
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.
///
/// If the shortcut is registered by another application, it will still return `false`.
@@ -239,29 +253,7 @@ struct ShortcutJsEvent {
#[tauri::command]
fn register<R: Runtime>(
_window: Window<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>,
_app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>,
shortcuts: Vec<String>,
handler: Channel,
@@ -275,7 +267,7 @@ fn register_all<R: Runtime>(
hotkeys.push(hotkey);
}
global_shortcut.register_all_internal(
global_shortcut.register_multiple_internal(
hotkeys,
Some(
move |_app: &AppHandle<R>, shortcut: &Shortcut, e: ShortcutEvent| {
@@ -292,15 +284,6 @@ fn register_all<R: Runtime>(
#[tauri::command]
fn unregister<R: Runtime>(
_app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>,
shortcut: String,
) -> Result<()> {
global_shortcut.unregister(parse_shortcut(shortcut)?)
}
#[tauri::command]
fn unregister_all<R: Runtime>(
_app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>,
shortcuts: Vec<String>,
@@ -309,7 +292,15 @@ fn unregister_all<R: Runtime>(
for shortcut in shortcuts {
hotkeys.push(parse_shortcut(&shortcut)?);
}
global_shortcut.unregister_all(hotkeys)
global_shortcut.unregister_multiple(hotkeys)
}
#[tauri::command]
fn unregister_all<R: Runtime>(
_app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>,
) -> Result<()> {
global_shortcut.unregister_all()
}
#[tauri::command]
@@ -379,10 +370,9 @@ impl<R: Runtime> Builder<R> {
PluginBuilder::new("global-shortcut")
.invoke_handler(tauri::generate_handler![
register,
register_all,
unregister,
unregister_all,
is_registered
is_registered,
])
.setup(move |app, _api| {
let manager = GlobalHotKeyManager::new()?;