refactor(core): global shortcut is now provided by tao (#2031)

This commit is contained in:
Lucas Fernandes Nogueira 2021-06-21 12:29:26 -03:00 committed by GitHub
parent f7d21a4b86
commit 3280c4aa91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 375 additions and 155 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
**Breaking change**: The global shortcut API is now managed by `tao` so it cannot be accessed globally, the manager is now exposed on the `App` and `AppHandle` structs.

View File

@ -0,0 +1,6 @@
---
"tauri-runtime": patch
"tauri-runtime-wry": patch
---
Adds global shortcut interfaces.

View File

@ -25,7 +25,7 @@ members = [
]
[patch.crates-io]
tao = { git = "https://github.com/tauri-apps/tao", rev = "66360eea4ec6af8a52afcebb7700f486a0092168" }
tao = { git = "https://github.com/tauri-apps/tao", rev = "01fc43b05ea41463d512c0e3497971edc543ac9d" }
# default to small, optimized workspace release binaries
[profile.release]

View File

@ -13,8 +13,8 @@ use tauri_runtime::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
DetachedWindow, PendingWindow, WindowEvent,
},
Dispatch, Error, Icon, Params, Result, RunEvent, RunIteration, Runtime, RuntimeHandle,
UserAttentionType,
Dispatch, Error, GlobalShortcutManager, Icon, Params, Result, RunEvent, RunIteration, Runtime,
RuntimeHandle, UserAttentionType,
};
#[cfg(feature = "menu")]
@ -32,6 +32,7 @@ use tauri_utils::config::WindowConfig;
use uuid::Uuid;
use wry::{
application::{
accelerator::{Accelerator, AcceleratorId},
dpi::{
LogicalPosition as WryLogicalPosition, LogicalSize as WryLogicalSize,
PhysicalPosition as WryPhysicalPosition, PhysicalSize as WryPhysicalSize,
@ -40,6 +41,7 @@ use wry::{
event::{Event, WindowEvent as WryWindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget},
monitor::MonitorHandle,
platform::global_shortcut::{GlobalShortcut, ShortcutManager as WryShortcutManager},
window::{
Fullscreen, Icon as WindowIcon, UserAttentionType as WryUserAttentionType, Window,
WindowBuilder as WryWindowBuilder, WindowId,
@ -76,6 +78,118 @@ type CreateWebviewHandler =
Box<dyn FnOnce(&EventLoopWindowTarget<Message>) -> Result<WebviewWrapper> + Send>;
type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
type WindowEventListeners = Arc<Mutex<HashMap<Uuid, WindowEventHandler>>>;
type GlobalShortcutListeners = Arc<Mutex<HashMap<AcceleratorId, Box<dyn Fn() + Send>>>>;
macro_rules! dispatcher_getter {
($self: ident, $message: expr) => {{
if current_thread().id() == $self.context.main_thread_id
&& !$self.context.is_event_loop_running.load(Ordering::Relaxed)
{
panic!("This API cannot be called when the event loop is not running");
}
let (tx, rx) = channel();
$self
.context
.proxy
.send_event(Message::Window($self.window_id, $message(tx)))
.map_err(|_| Error::FailedToSendMessage)?;
rx.recv().unwrap()
}};
}
macro_rules! shortcut_getter {
($self: ident, $rx: expr, $message: expr) => {{
if current_thread().id() == $self.context.main_thread_id
&& !$self.context.is_event_loop_running.load(Ordering::Relaxed)
{
panic!("This API cannot be called when the event loop is not running");
}
$self
.context
.proxy
.send_event($message)
.map_err(|_| Error::FailedToSendMessage)?;
$rx.recv().unwrap()
}};
}
#[derive(Debug, Clone)]
struct GlobalShortcutWrapper(GlobalShortcut);
unsafe impl Send for GlobalShortcutWrapper {}
#[derive(Clone)]
struct GlobalShortcutManagerContext {
main_thread_id: ThreadId,
is_event_loop_running: Arc<AtomicBool>,
proxy: EventLoopProxy<Message>,
}
/// Wrapper around [`WryShortcutManager`].
#[derive(Clone)]
pub struct GlobalShortcutManagerHandle {
context: GlobalShortcutManagerContext,
shortcuts: HashMap<String, (AcceleratorId, GlobalShortcutWrapper)>,
listeners: GlobalShortcutListeners,
}
impl GlobalShortcutManager for GlobalShortcutManagerHandle {
fn is_registered(&self, accelerator: &str) -> Result<bool> {
let (tx, rx) = channel();
Ok(shortcut_getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::IsRegistered(
accelerator.parse().expect("invalid accelerator"),
tx
))
))
}
fn register<F: Fn() + Send + 'static>(&mut self, accelerator: &str, handler: F) -> Result<()> {
let wry_accelerator: Accelerator = accelerator.parse().expect("invalid accelerator");
let id = wry_accelerator.clone().id();
let (tx, rx) = channel();
let shortcut = shortcut_getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::Register(wry_accelerator, tx))
)?;
self.listeners.lock().unwrap().insert(id, Box::new(handler));
self.shortcuts.insert(accelerator.into(), (id, shortcut));
Ok(())
}
fn unregister_all(&mut self) -> Result<()> {
let (tx, rx) = channel();
shortcut_getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::UnregisterAll(tx))
)?;
self.listeners.lock().unwrap().clear();
self.shortcuts.clear();
Ok(())
}
fn unregister(&mut self, accelerator: &str) -> Result<()> {
if let Some((accelerator_id, shortcut)) = self.shortcuts.remove(accelerator) {
let (tx, rx) = channel();
shortcut_getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::Unregister(
GlobalShortcutWrapper(shortcut.0),
tx
))
)?;
self.listeners.lock().unwrap().remove(&accelerator_id);
}
Ok(())
}
}
/// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`Icon`].
pub struct WryIcon(WindowIcon);
@ -267,7 +381,7 @@ pub struct WindowBuilderWrapper {
inner: WryWindowBuilder,
center: bool,
#[cfg(feature = "menu")]
menu_items: HashMap<u32, WryCustomMenuItem>,
menu_items: HashMap<u16, WryCustomMenuItem>,
}
// safe since `menu_items` are read only here
@ -510,7 +624,7 @@ enum WindowMessage {
SetSkipTaskbar(bool),
DragWindow,
#[cfg(feature = "menu")]
UpdateMenuItem(u32, menu::MenuUpdate),
UpdateMenuItem(u16, menu::MenuUpdate),
}
#[derive(Debug, Clone)]
@ -522,12 +636,20 @@ enum WebviewMessage {
#[cfg(feature = "system-tray")]
#[derive(Clone)]
pub(crate) enum TrayMessage {
UpdateItem(u32, menu::MenuUpdate),
UpdateItem(u16, menu::MenuUpdate),
UpdateIcon(Icon),
#[cfg(windows)]
Remove,
}
#[derive(Clone)]
pub(crate) enum GlobalShortcutMessage {
IsRegistered(Accelerator, Sender<bool>),
Register(Accelerator, Sender<Result<GlobalShortcutWrapper>>),
Unregister(GlobalShortcutWrapper, Sender<Result<()>>),
UnregisterAll(Sender<Result<()>>),
}
#[derive(Clone)]
pub(crate) enum Message {
Task(MainTask),
@ -536,6 +658,7 @@ pub(crate) enum Message {
#[cfg(feature = "system-tray")]
Tray(TrayMessage),
CreateWebview(Arc<Mutex<Option<CreateWebviewHandler>>>, Sender<WindowId>),
GlobalShortcut(GlobalShortcutMessage),
}
#[derive(Clone)]
@ -555,23 +678,6 @@ pub struct WryDispatcher {
context: DispatcherContext,
}
macro_rules! dispatcher_getter {
($self: ident, $message: expr) => {{
if current_thread().id() == $self.context.main_thread_id
&& !$self.context.is_event_loop_running.load(Ordering::Relaxed)
{
panic!("This API cannot be called when the event loop is not running");
}
let (tx, rx) = channel();
$self
.context
.proxy
.send_event(Message::Window($self.window_id, $message(tx)))
.map_err(|_| Error::FailedToSendMessage)?;
rx.recv().unwrap()
}};
}
impl Dispatch for WryDispatcher {
type Runtime = Wry;
type WindowBuilder = WindowBuilderWrapper;
@ -959,7 +1065,7 @@ impl Dispatch for WryDispatcher {
}
#[cfg(feature = "menu")]
fn update_menu_item(&self, id: u32, update: menu::MenuUpdate) -> Result<()> {
fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> Result<()> {
self
.context
.proxy
@ -983,7 +1089,7 @@ struct WebviewWrapper {
label: String,
inner: WebView,
#[cfg(feature = "menu")]
menu_items: HashMap<u32, WryCustomMenuItem>,
menu_items: HashMap<u16, WryCustomMenuItem>,
#[cfg(feature = "menu")]
is_menu_visible: AtomicBool,
}
@ -991,6 +1097,8 @@ struct WebviewWrapper {
/// A Tauri [`Runtime`] wrapper around wry.
pub struct Wry {
main_thread_id: ThreadId,
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
global_shortcut_manager_handle: GlobalShortcutManagerHandle,
is_event_loop_running: Arc<AtomicBool>,
event_loop: EventLoop<Message>,
webviews: Arc<Mutex<HashMap<WindowId, WebviewWrapper>>>,
@ -1050,14 +1158,30 @@ impl RuntimeHandle for WryHandle {
impl Runtime for Wry {
type Dispatcher = WryDispatcher;
type Handle = WryHandle;
type GlobalShortcutManager = GlobalShortcutManagerHandle;
#[cfg(feature = "system-tray")]
type TrayHandler = SystemTrayHandle;
fn new() -> Result<Self> {
let event_loop = EventLoop::<Message>::with_user_event();
let global_shortcut_manager = WryShortcutManager::new(&event_loop);
let global_shortcut_listeners = GlobalShortcutListeners::default();
let proxy = event_loop.create_proxy();
let main_thread_id = current_thread().id();
let is_event_loop_running = Arc::new(AtomicBool::default());
Ok(Self {
main_thread_id: current_thread().id(),
is_event_loop_running: Default::default(),
main_thread_id,
global_shortcut_manager: Arc::new(Mutex::new(global_shortcut_manager)),
global_shortcut_manager_handle: GlobalShortcutManagerHandle {
context: GlobalShortcutManagerContext {
main_thread_id,
is_event_loop_running: is_event_loop_running.clone(),
proxy,
},
shortcuts: Default::default(),
listeners: global_shortcut_listeners,
},
is_event_loop_running,
event_loop,
webviews: Default::default(),
window_event_listeners: Default::default(),
@ -1081,6 +1205,10 @@ impl Runtime for Wry {
}
}
fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager {
self.global_shortcut_manager_handle.clone()
}
fn create_window<P: Params<Runtime = Self>>(
&self,
pending: PendingWindow<P>,
@ -1168,6 +1296,8 @@ impl Runtime for Wry {
let menu_event_listeners = self.menu_event_listeners.clone();
#[cfg(feature = "system-tray")]
let tray_context = self.tray_context.clone();
let global_shortcut_manager = self.global_shortcut_manager.clone();
let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
let mut iteration = RunIteration::default();
@ -1186,6 +1316,8 @@ impl Runtime for Wry {
callback: &callback,
webviews: webviews.lock().expect("poisoned webview collection"),
window_event_listeners: window_event_listeners.clone(),
global_shortcut_manager: global_shortcut_manager.clone(),
global_shortcut_manager_handle: global_shortcut_manager_handle.clone(),
#[cfg(feature = "menu")]
menu_event_listeners: menu_event_listeners.clone(),
#[cfg(feature = "system-tray")]
@ -1206,6 +1338,8 @@ impl Runtime for Wry {
let menu_event_listeners = self.menu_event_listeners.clone();
#[cfg(feature = "system-tray")]
let tray_context = self.tray_context;
let global_shortcut_manager = self.global_shortcut_manager.clone();
let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
self.event_loop.run(move |event, event_loop, control_flow| {
handle_event_loop(
@ -1216,6 +1350,8 @@ impl Runtime for Wry {
callback: &callback,
webviews: webviews.lock().expect("poisoned webview collection"),
window_event_listeners: window_event_listeners.clone(),
global_shortcut_manager: global_shortcut_manager.clone(),
global_shortcut_manager_handle: global_shortcut_manager_handle.clone(),
#[cfg(feature = "menu")]
menu_event_listeners: menu_event_listeners.clone(),
#[cfg(feature = "system-tray")]
@ -1230,6 +1366,8 @@ struct EventLoopIterationContext<'a> {
callback: &'a (dyn Fn(RunEvent) + 'static),
webviews: MutexGuard<'a, HashMap<WindowId, WebviewWrapper>>,
window_event_listeners: WindowEventListeners,
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
global_shortcut_manager_handle: GlobalShortcutManagerHandle,
#[cfg(feature = "menu")]
menu_event_listeners: MenuEventListeners,
#[cfg(feature = "system-tray")]
@ -1246,6 +1384,8 @@ fn handle_event_loop(
callback,
mut webviews,
window_event_listeners,
global_shortcut_manager,
global_shortcut_manager_handle,
#[cfg(feature = "menu")]
menu_event_listeners,
#[cfg(feature = "system-tray")]
@ -1254,6 +1394,13 @@ fn handle_event_loop(
*control_flow = ControlFlow::Wait;
match event {
Event::GlobalShortcutEvent(accelerator_id) => {
for (id, handler) in &*global_shortcut_manager_handle.listeners.lock().unwrap() {
if accelerator_id == *id {
handler();
}
}
}
#[cfg(feature = "menu")]
Event::MenuEvent {
menu_id,
@ -1504,6 +1651,44 @@ fn handle_event_loop(
}
}
},
Message::GlobalShortcut(message) => match message {
GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.is_registered(&accelerator),
)
.unwrap(),
GlobalShortcutMessage::Register(accelerator, tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.register(accelerator)
.map(GlobalShortcutWrapper)
.map_err(|e| Error::GlobalShortcut(Box::new(e))),
)
.unwrap(),
GlobalShortcutMessage::Unregister(shortcut, tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.unregister(shortcut.0)
.map_err(|e| Error::GlobalShortcut(Box::new(e))),
)
.unwrap(),
GlobalShortcutMessage::UnregisterAll(tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.unregister_all()
.map_err(|e| Error::GlobalShortcut(Box::new(e))),
)
.unwrap(),
},
},
_ => (),
}

View File

@ -45,7 +45,7 @@ pub type SystemTrayEventHandler = Box<dyn Fn(&SystemTrayEvent) + Send>;
#[cfg(feature = "system-tray")]
pub type SystemTrayEventListeners = Arc<Mutex<HashMap<Uuid, SystemTrayEventHandler>>>;
#[cfg(feature = "system-tray")]
pub type SystemTrayItems = Arc<Mutex<HashMap<u32, WryCustomMenuItem>>>;
pub type SystemTrayItems = Arc<Mutex<HashMap<u16, WryCustomMenuItem>>>;
#[cfg(feature = "system-tray")]
#[derive(Clone)]
@ -61,7 +61,7 @@ impl TrayHandle for SystemTrayHandle {
.send_event(Message::Tray(TrayMessage::UpdateIcon(icon)))
.map_err(|_| Error::FailedToSendMessage)
}
fn update_item(&self, id: u32, update: MenuUpdate) -> Result<()> {
fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> {
self
.proxy
.send_event(Message::Tray(TrayMessage::UpdateItem(id, update)))
@ -147,7 +147,7 @@ impl<'a, I: MenuId> From<&'a CustomMenuItem<I>> for MenuItemAttributesWrapper<'a
.with_selected(item.selected)
.with_id(WryMenuId(item.id_value()));
if let Some(accelerator) = item.keyboard_accelerator.as_ref() {
attributes = attributes.with_accelerators(accelerator);
attributes = attributes.with_accelerators(&accelerator.parse().expect("invalid accelerator"));
}
Self(attributes)
}
@ -191,7 +191,7 @@ impl From<SystemTrayMenuItem> for MenuItemWrapper {
#[cfg(feature = "menu")]
pub fn to_wry_menu<I: MenuId>(
custom_menu_items: &mut HashMap<u32, WryCustomMenuItem>,
custom_menu_items: &mut HashMap<u16, WryCustomMenuItem>,
menu: Menu<I>,
) -> MenuBar {
let mut wry_menu = MenuBar::new();
@ -224,7 +224,7 @@ pub fn to_wry_menu<I: MenuId>(
#[cfg(feature = "system-tray")]
pub fn to_wry_context_menu<I: MenuId>(
custom_menu_items: &mut HashMap<u32, WryCustomMenuItem>,
custom_menu_items: &mut HashMap<u16, WryCustomMenuItem>,
menu: SystemTrayMenu<I>,
) -> WryContextMenu {
let mut tray_menu = WryContextMenu::new();

View File

@ -116,6 +116,9 @@ pub enum Error {
/// Failed to get monitor on window operation.
#[error("failed to get monitor")]
FailedToGetMonitor,
/// Global shortcut error.
#[error(transparent)]
GlobalShortcut(Box<dyn std::error::Error + Send>),
}
/// Result type.
@ -193,7 +196,7 @@ pub enum RunEvent {
/// A system tray event.
pub enum SystemTrayEvent {
MenuItemClick(u32),
MenuItemClick(u16),
LeftClick {
position: PhysicalPosition<f64>,
size: PhysicalSize<f64>,
@ -228,12 +231,53 @@ pub trait RuntimeHandle: Send + Sized + Clone + 'static {
fn remove_system_tray(&self) -> crate::Result<()>;
}
/// A global shortcut manager.
pub trait GlobalShortcutManager {
/// Whether the application has registered the given `accelerator`.
///
/// # Panics
///
/// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
/// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
fn is_registered(&self, accelerator: &str) -> crate::Result<bool>;
/// Register a global shortcut of `accelerator`.
///
/// # Panics
///
/// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
/// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
fn register<F: Fn() + Send + 'static>(
&mut self,
accelerator: &str,
handler: F,
) -> crate::Result<()>;
/// Unregister all accelerators registered by the manager instance.
///
/// # Panics
///
/// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
/// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
fn unregister_all(&mut self) -> crate::Result<()>;
/// Unregister the provided `accelerator`.
///
/// # Panics
///
/// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
/// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
fn unregister(&mut self, accelerator: &str) -> crate::Result<()>;
}
/// The webview runtime interface.
pub trait Runtime: Sized + 'static {
/// The message dispatcher.
type Dispatcher: Dispatch<Runtime = Self>;
/// The runtime handle type.
type Handle: RuntimeHandle<Runtime = Self>;
/// The global shortcut manager type.
type GlobalShortcutManager: GlobalShortcutManager + Clone + Send;
/// The tray handler type.
#[cfg(feature = "system-tray")]
type TrayHandler: menu::TrayHandle + Clone + Send;
@ -244,6 +288,9 @@ pub trait Runtime: Sized + 'static {
/// Gets a runtime handle.
fn handle(&self) -> Self::Handle;
/// Gets the global shortcut manager.
fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager;
/// Create a new webview window.
fn create_window<P: Params<Runtime = Self>>(
&self,
@ -436,5 +483,5 @@ pub trait Dispatch: Clone + Send + Sized + 'static {
/// Applies the specified `update` to the menu item associated with the given `id`.
#[cfg(feature = "menu")]
fn update_menu_item(&self, id: u32, update: menu::MenuUpdate) -> crate::Result<()>;
fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> crate::Result<()>;
}

View File

@ -142,7 +142,7 @@ pub enum MenuUpdate {
pub trait TrayHandle {
fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>;
fn update_item(&self, id: u32, update: MenuUpdate) -> crate::Result<()>;
fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>;
}
/// A window menu.
@ -248,10 +248,10 @@ impl<I: MenuId> CustomMenuItem<I> {
}
#[doc(hidden)]
pub fn id_value(&self) -> u32 {
pub fn id_value(&self) -> u16 {
let mut s = DefaultHasher::new();
self.id.hash(&mut s);
s.finish() as u32
s.finish() as u16
}
}

View File

@ -51,7 +51,7 @@ pub enum WindowEvent {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MenuEvent {
pub menu_item_id: u32,
pub menu_item_id: u16,
}
/// A webview window that has yet to be built.

View File

@ -66,9 +66,6 @@ clap = { version = "=3.0.0-beta.2", optional = true }
# Notifications
notify-rust = { version = "4.5", optional = true }
# Global shortcut
tauri-hotkey = { version = "0.1.2", optional = true }
# HTTP
reqwest = { version = "0.11", features = [ "json", "multipart" ], optional = true }
bytes = { version = "1", features = [ "serde" ], optional = true }
@ -133,4 +130,4 @@ dialog-save = [ "raw-window-handle" ]
http-all = [ ]
http-request = [ ]
notification-all = [ "notify-rust" ]
global-shortcut-all = [ "tauri-hotkey" ]
global-shortcut-all = [ ]

View File

@ -76,10 +76,6 @@ pub enum Error {
#[cfg(feature = "cli")]
#[error("failed to parse CLI arguments: {0}")]
ParseCliArguments(#[from] clap::Error),
/// Shortcut error.
#[cfg(global_shortcut_all)]
#[error("shortcut error: {0}")]
Shortcut(#[from] tauri_hotkey::Error),
/// Shell error.
#[error("shell error: {0}")]
Shell(String),

View File

@ -36,10 +36,6 @@ pub mod cli;
#[cfg(feature = "cli")]
pub use clap;
/// Global shortcuts interface.
#[cfg(global_shortcut_all)]
pub mod shortcuts;
/// The desktop notifications API module.
#[cfg(notification_all)]
pub mod notification;

View File

@ -1,46 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
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()
}
/// Determines whether the given hotkey is registered or not.
pub fn is_registered(&self, shortcut: String) -> crate::api::Result<bool> {
let hotkey = parse_hotkey(&shortcut)?;
Ok(self.0.is_registered(&hotkey))
}
/// Registers a new shortcut handler.
pub fn register<H: FnMut() + Send + 'static>(
&mut self,
shortcut: String,
handler: H,
) -> crate::api::Result<()> {
let hotkey = parse_hotkey(&shortcut)?;
self.0.register(hotkey, handler)?;
Ok(())
}
/// Unregister a previously registered shortcut handler.
pub fn unregister(&mut self, shortcut: String) -> crate::api::Result<()> {
let hotkey = parse_hotkey(&shortcut)?;
self.0.unregister(&hotkey)?;
Ok(())
}
/// Unregisters all shortcuts registered by this application.
pub fn unregister_all(&mut self) -> crate::api::Result<()> {
self.0.unregister_all()?;
Ok(())
}
}

View File

@ -113,6 +113,7 @@ crate::manager::default_args! {
pub struct AppHandle<P: Params> {
runtime_handle: <P::Runtime as Runtime>::Handle,
manager: WindowManager<P>,
global_shortcut_manager: <P::Runtime as Runtime>::GlobalShortcutManager,
#[cfg(feature = "system-tray")]
tray_handle: Option<tray::SystemTrayHandle<P>>,
}
@ -123,6 +124,7 @@ impl<P: Params> Clone for AppHandle<P> {
Self {
runtime_handle: self.runtime_handle.clone(),
manager: self.manager.clone(),
global_shortcut_manager: self.global_shortcut_manager.clone(),
#[cfg(feature = "system-tray")]
tray_handle: self.tray_handle.clone(),
}
@ -147,6 +149,10 @@ impl<P: Params> ManagerBase<P> for AppHandle<P> {
fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())
}
fn app_handle(&self) -> AppHandle<P> {
self.clone()
}
}
crate::manager::default_args! {
@ -156,6 +162,7 @@ crate::manager::default_args! {
pub struct App<P: Params> {
runtime: Option<P::Runtime>,
manager: WindowManager<P>,
global_shortcut_manager: <P::Runtime as Runtime>::GlobalShortcutManager,
#[cfg(feature = "system-tray")]
tray_handle: Option<tray::SystemTrayHandle<P>>,
handle: AppHandle<P>,
@ -171,6 +178,10 @@ impl<P: Params> ManagerBase<P> for App<P> {
fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
RuntimeOrDispatch::Runtime(self.runtime.as_ref().unwrap())
}
fn app_handle(&self) -> AppHandle<P> {
self.handle()
}
}
macro_rules! shared_app_impl {
@ -209,6 +220,11 @@ macro_rules! shared_app_impl {
.expect("tray not configured; use the `Builder#system_tray` API first.")
}
/// Gets a copy of the global shortcut manager instance.
pub fn global_shortcut_manager(&self) -> <P::Runtime as Runtime>::GlobalShortcutManager {
self.global_shortcut_manager.clone()
}
/// The path resolver for the application.
pub fn path_resolver(&self) -> PathResolver {
PathResolver {
@ -700,15 +716,18 @@ where
let runtime = R::new()?;
let runtime_handle = runtime.handle();
let global_shortcut_manager = runtime.global_shortcut_manager();
let mut app = App {
runtime: Some(runtime),
manager: manager.clone(),
global_shortcut_manager: global_shortcut_manager.clone(),
#[cfg(feature = "system-tray")]
tray_handle: None,
handle: AppHandle {
runtime_handle,
manager,
global_shortcut_manager,
#[cfg(feature = "system-tray")]
tray_handle: None,
},
@ -726,9 +745,11 @@ where
let mut main_window = None;
for pending in self.pending_windows {
let pending = app.manager.prepare_window(pending, &pending_labels)?;
let pending = app
.manager
.prepare_window(app.handle.clone(), pending, &pending_labels)?;
let detached = app.runtime.as_ref().unwrap().create_window(pending)?;
let _window = app.manager.attach_window(detached);
let _window = app.manager.attach_window(app.handle(), detached);
#[cfg(feature = "updater")]
if main_window.is_none() {
main_window = Some(_window);

View File

@ -13,7 +13,7 @@ pub use crate::{
use std::{collections::HashMap, sync::Arc};
pub(crate) fn get_menu_ids<I: MenuId>(map: &mut HashMap<u32, I>, menu: &SystemTrayMenu<I>) {
pub(crate) fn get_menu_ids<I: MenuId>(map: &mut HashMap<u16, I>, menu: &SystemTrayMenu<I>) {
for item in &menu.items {
match item {
SystemTrayMenuEntry::CustomItem(c) => {
@ -78,7 +78,7 @@ pub enum SystemTrayEvent<I: MenuId> {
crate::manager::default_args! {
/// A handle to a system tray. Allows updating the context menu items.
pub struct SystemTrayHandle<P: Params> {
pub(crate) ids: Arc<HashMap<u32, P::SystemTrayMenuId>>,
pub(crate) ids: Arc<HashMap<u16, P::SystemTrayMenuId>>,
pub(crate) inner: <P::Runtime as Runtime>::TrayHandler,
}
}
@ -95,7 +95,7 @@ impl<P: Params> Clone for SystemTrayHandle<P> {
crate::manager::default_args! {
/// A handle to a system tray menu item.
pub struct SystemTrayMenuItemHandle<P: Params> {
id: u32,
id: u16,
tray_handler: <P::Runtime as Runtime>::TrayHandler,
}
}

View File

@ -7,17 +7,7 @@ use crate::{Params, Window};
use serde::Deserialize;
#[cfg(global_shortcut_all)]
use crate::{api::shortcuts::ShortcutManager, runtime::Dispatch};
#[cfg(global_shortcut_all)]
type ShortcutManagerHandle = std::sync::Arc<std::sync::Mutex<ShortcutManager>>;
#[cfg(global_shortcut_all)]
pub fn manager_handle() -> &'static ShortcutManagerHandle {
use once_cell::sync::Lazy;
static MANAGER: Lazy<ShortcutManagerHandle> = Lazy::new(Default::default);
&MANAGER
}
use crate::runtime::{GlobalShortcutManager, Runtime};
/// The API descriptor.
#[derive(Deserialize)]
@ -39,16 +29,17 @@ pub enum Cmd {
}
#[cfg(global_shortcut_all)]
fn register_shortcut<D: Dispatch>(
dispatcher: D,
manager: &mut ShortcutManager,
fn register_shortcut<P: Params>(
window: Window<P>,
manager: &mut <P::Runtime as Runtime>::GlobalShortcutManager,
shortcut: String,
handler: String,
) -> crate::Result<()> {
manager.register(shortcut.clone(), move || {
let callback_string = crate::api::rpc::format_callback(handler.to_string(), &shortcut)
let accelerator = shortcut.clone();
manager.register(&shortcut, move || {
let callback_string = crate::api::rpc::format_callback(handler.to_string(), &accelerator)
.expect("unable to serialize shortcut string to json");
let _ = dispatcher.eval_script(callback_string.as_str());
let _ = window.eval(callback_string.as_str());
})?;
Ok(())
}
@ -67,34 +58,38 @@ impl Cmd {
pub fn run<P: Params>(self, window: Window<P>) -> crate::Result<InvokeResponse> {
match self {
Self::Register { shortcut, handler } => {
let dispatcher = window.dispatcher();
let mut manager = manager_handle().lock().unwrap();
register_shortcut(dispatcher, &mut manager, shortcut, handler)?;
let mut manager = window.app_handle.global_shortcut_manager();
register_shortcut(window, &mut manager, shortcut, handler)?;
Ok(().into())
}
Self::RegisterAll { shortcuts, handler } => {
let dispatcher = window.dispatcher();
let mut manager = manager_handle().lock().unwrap();
let mut manager = window.app_handle.global_shortcut_manager();
for shortcut in shortcuts {
let dispatch = dispatcher.clone();
register_shortcut(dispatch, &mut manager, shortcut, handler.clone())?;
register_shortcut(window.clone(), &mut manager, shortcut, handler.clone())?;
}
Ok(().into())
}
Self::Unregister { shortcut } => {
let mut manager = manager_handle().lock().unwrap();
manager.unregister(shortcut)?;
window
.app_handle
.global_shortcut_manager()
.unregister(&shortcut)?;
Ok(().into())
}
Self::UnregisterAll => {
let mut manager = manager_handle().lock().unwrap();
manager.unregister_all()?;
window
.app_handle
.global_shortcut_manager()
.unregister_all()?;
Ok(().into())
}
Self::IsRegistered { shortcut } => {
let manager = manager_handle().lock().unwrap();
Ok(manager.is_registered(shortcut)?.into())
}
Self::IsRegistered { shortcut } => Ok(
window
.app_handle
.global_shortcut_manager()
.is_registered(&shortcut)?
.into(),
),
}
}
}

View File

@ -340,7 +340,7 @@ pub trait Manager<P: Params>: sealed::ManagerBase<P> {
/// Prevent implementation details from leaking out of the [`Manager`] trait.
pub(crate) mod sealed {
use crate::manager::WindowManager;
use crate::{app::AppHandle, manager::WindowManager};
use tauri_runtime::{Params, Runtime, RuntimeHandle};
/// A running [`Runtime`] or a dispatcher to it.
@ -361,6 +361,7 @@ pub(crate) mod sealed {
fn manager(&self) -> &WindowManager<P>;
fn runtime(&self) -> RuntimeOrDispatch<'_, P>;
fn app_handle(&self) -> AppHandle<P>;
/// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
fn create_new_window(
@ -369,7 +370,9 @@ pub(crate) mod sealed {
) -> crate::Result<crate::Window<P>> {
use crate::runtime::Dispatch;
let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
let pending = self.manager().prepare_window(pending, &labels)?;
let pending = self
.manager()
.prepare_window(self.app_handle(), pending, &labels)?;
match self.runtime() {
RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending).map_err(Into::into),
RuntimeOrDispatch::RuntimeHandle(handle) => {
@ -379,7 +382,7 @@ pub(crate) mod sealed {
dispatcher.create_window(pending).map_err(Into::into)
}
}
.map(|window| self.manager().attach_window(window))
.map(|window| self.manager().attach_window(self.app_handle(), window))
}
}
}

View File

@ -12,7 +12,7 @@ use crate::{
path::{resolve_path, BaseDirectory},
PackageInfo,
},
app::{GlobalWindowEvent, GlobalWindowEventListener},
app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener},
event::{Event, EventHandler, Listeners},
hooks::{InvokeHandler, OnPageLoad, PageLoadPayload},
plugin::PluginStore,
@ -101,7 +101,7 @@ crate::manager::default_args! {
menu: Option<Menu<P::MenuId>>,
/// Maps runtime id to a strongly typed menu id.
#[cfg(feature = "menu")]
menu_ids: HashMap<u32, P::MenuId>,
menu_ids: HashMap<u16, P::MenuId>,
/// Menu event listeners to all windows.
#[cfg(feature = "menu")]
menu_event_listeners: Arc<Vec<GlobalMenuEventListener<P>>>,
@ -209,7 +209,7 @@ impl<P: Params> Clone for WindowManager<P> {
}
#[cfg(feature = "menu")]
fn get_menu_ids<I: MenuId>(map: &mut HashMap<u32, I>, menu: &Menu<I>) {
fn get_menu_ids<I: MenuId>(map: &mut HashMap<u16, I>, menu: &Menu<I>) {
for item in &menu.items {
match item {
MenuEntry::CustomItem(c) => {
@ -280,7 +280,7 @@ impl<P: Params> WindowManager<P> {
/// Get the menu ids mapper.
#[cfg(feature = "menu")]
pub(crate) fn menu_ids(&self) -> HashMap<u32, P::MenuId> {
pub(crate) fn menu_ids(&self) -> HashMap<u16, P::MenuId> {
self.inner.menu_ids.clone()
}
@ -371,10 +371,10 @@ impl<P: Params> WindowManager<P> {
Ok(pending)
}
fn prepare_rpc_handler(&self) -> WebviewRpcHandler<P> {
fn prepare_rpc_handler(&self, app_handle: AppHandle<P>) -> WebviewRpcHandler<P> {
let manager = self.clone();
Box::new(move |window, request| {
let window = Window::new(manager.clone(), window);
let window = Window::new(manager.clone(), window, app_handle.clone());
let command = request.command.clone();
let arg = request
@ -446,12 +446,13 @@ impl<P: Params> WindowManager<P> {
}
}
fn prepare_file_drop(&self) -> FileDropHandler<P> {
fn prepare_file_drop(&self, app_handle: AppHandle<P>) -> FileDropHandler<P> {
let manager = self.clone();
Box::new(move |event, window| {
let manager = manager.clone();
let app_handle = app_handle.clone();
crate::async_runtime::block_on(async move {
let window = Window::new(manager.clone(), window);
let window = Window::new(manager.clone(), window, app_handle);
let _ = match event {
FileDropEvent::Hovered(paths) => {
window.emit(&tauri_event::<P::Event>("tauri://file-drop"), Some(paths))
@ -604,6 +605,7 @@ impl<P: Params> WindowManager<P> {
pub fn prepare_window(
&self,
app_handle: AppHandle<P>,
mut pending: PendingWindow<P>,
pending_labels: &[P::Label],
) -> crate::Result<PendingWindow<P>> {
@ -627,19 +629,19 @@ impl<P: Params> WindowManager<P> {
if is_local {
let label = pending.label.clone();
pending = self.prepare_pending_window(pending, label, pending_labels)?;
pending.rpc_handler = Some(self.prepare_rpc_handler());
pending.rpc_handler = Some(self.prepare_rpc_handler(app_handle.clone()));
}
if pending.webview_attributes.file_drop_handler_enabled {
pending.file_drop_handler = Some(self.prepare_file_drop());
pending.file_drop_handler = Some(self.prepare_file_drop(app_handle));
}
pending.url = url;
Ok(pending)
}
pub fn attach_window(&self, window: DetachedWindow<P>) -> Window<P> {
let window = Window::new(self.clone(), window);
pub fn attach_window(&self, app_handle: AppHandle<P>, window: DetachedWindow<P>) -> Window<P> {
let window = Window::new(self.clone(), window, app_handle);
let window_ = window.clone();
let window_event_listeners = self.inner.window_event_listeners.clone();

View File

@ -8,6 +8,7 @@ pub(crate) mod menu;
use crate::{
api::config::WindowUrl,
app::AppHandle,
command::{CommandArg, CommandItem},
event::{Event, EventHandler},
manager::WindowManager,
@ -85,11 +86,10 @@ crate::manager::default_args! {
/// the same application.
pub struct Window<P: Params> {
/// The webview window created by the runtime.
/// ok
window: DetachedWindow<P>,
/// The manager to associate this webview window with.
manager: WindowManager<P>,
pub(crate) app_handle: AppHandle<P>,
}
}
@ -98,6 +98,7 @@ impl<P: Params> Clone for Window<P> {
Self {
window: self.window.clone(),
manager: self.manager.clone(),
app_handle: self.app_handle.clone(),
}
}
}
@ -123,6 +124,10 @@ impl<P: Params> ManagerBase<P> for Window<P> {
&self.manager
}
fn app_handle(&self) -> AppHandle<P> {
self.app_handle.clone()
}
fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
RuntimeOrDispatch::Dispatch(self.dispatcher())
}
@ -137,8 +142,16 @@ impl<'de, P: Params> CommandArg<'de, P> for Window<P> {
impl<P: Params> Window<P> {
/// Create a new window that is attached to the manager.
pub(crate) fn new(manager: WindowManager<P>, window: DetachedWindow<P>) -> Self {
Self { window, manager }
pub(crate) fn new(
manager: WindowManager<P>,
window: DetachedWindow<P>,
app_handle: AppHandle<P>,
) -> Self {
Self {
window,
manager,
app_handle,
}
}
/// Creates a new webview window.

View File

@ -27,7 +27,7 @@ impl<I: MenuId> MenuEvent<I> {
crate::manager::default_args! {
/// A handle to a system tray. Allows updating the context menu items.
pub struct MenuHandle<P: Params> {
pub(crate) ids: HashMap<u32, P::MenuId>,
pub(crate) ids: HashMap<u16, P::MenuId>,
pub(crate) dispatcher: <P::Runtime as Runtime>::Dispatcher,
}
}
@ -44,7 +44,7 @@ impl<P: Params> Clone for MenuHandle<P> {
crate::manager::default_args! {
/// A handle to a system tray menu item.
pub struct MenuItemHandle<P: Params> {
id: u32,
id: u16,
dispatcher: <P::Runtime as Runtime>::Dispatcher,
}
}

View File

@ -27,7 +27,7 @@ describe('[CLI] cli.js template', () => {
const manifestFile = readFileSync(manifestPath).toString()
writeFileSync(
manifestPath,
`workspace = { }\n[patch.crates-io]\ntao = { git = "https://github.com/tauri-apps/tao", rev = "66360eea4ec6af8a52afcebb7700f486a0092168" }\n\n${manifestFile}`
`workspace = { }\n[patch.crates-io]\ntao = { git = "https://github.com/tauri-apps/tao", rev = "01fc43b05ea41463d512c0e3497971edc543ac9d" }\n\n${manifestFile}`
)
const { promise: buildPromise } = await build()