mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-25 03:33:36 +03:00
refactor: move to muda
and tray_icon
crates (#7535)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio> Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.studio> Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
parent
ec827760ab
commit
7fb419c326
5
.changes/config-tray-icon-tooltip.md
Normal file
5
.changes/config-tray-icon-tooltip.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri-utils': 'minor:feat'
|
||||
---
|
||||
|
||||
Add option to specify a tooltip text for the tray icon in the config.
|
5
.changes/config-tray-icon.md
Normal file
5
.changes/config-tray-icon.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri-utils': 'major:breaking'
|
||||
---
|
||||
|
||||
`systemTray` config option has been renamed to `trayIcon`.
|
6
.changes/runtime-create-window-handler.md
Normal file
6
.changes/runtime-create-window-handler.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
'tauri-runtime': 'minor:breaking'
|
||||
'tauri-runtime-wry': 'minor:breaking'
|
||||
---
|
||||
|
||||
`Dispatch::create_window`, `Runtime::create_window` and `RuntimeHandle::create_window` has been changed to accept a 3rd parameter which is a closure that takes `RawWindow` and to be executed right after the window is created and before the webview is added to the window.
|
5
.changes/runtime-defaultvbox.md
Normal file
5
.changes/runtime-defaultvbox.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri-runtime-wry': 'minor:feat'
|
||||
---
|
||||
|
||||
Add `Dispatch::default_vbox`
|
6
.changes/runtime-menu-system-tray.md
Normal file
6
.changes/runtime-menu-system-tray.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
'tauri-runtime': 'major:breaking'
|
||||
'tauri-runtime-wry': 'major:breaking'
|
||||
---
|
||||
|
||||
System tray and menu related APIs and structs have all been removed and are now implemented in tauri outside of the runtime-space.
|
6
.changes/runtime-new-args.md
Normal file
6
.changes/runtime-new-args.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
'tauri-runtime': 'minor:breaking'
|
||||
'tauri-runtime-wry': 'minor:breaking'
|
||||
---
|
||||
|
||||
`Runtime::new` and `Runtime::new_any_thread` now accept a `RuntimeInitArgs`.
|
6
.changes/system-tray-feat.md
Normal file
6
.changes/system-tray-feat.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
'tauri-runtime': 'major:breaking'
|
||||
'tauri-runtime-wry': 'major:breaking'
|
||||
---
|
||||
|
||||
Removed `system-tray` feature flag
|
5
.changes/tauri-app-handle-ref.md
Normal file
5
.changes/tauri-app-handle-ref.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri': 'major:breaking'
|
||||
---
|
||||
|
||||
Changed `App::handle` and `Manager::app_handle` to return a reference to an `AppHandle` instead of an owned value.
|
5
.changes/tauri-cleanup-before-exit.md
Normal file
5
.changes/tauri-cleanup-before-exit.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri': 'minor:feat'
|
||||
---
|
||||
|
||||
Add `App::cleanup_before_exit` and `AppHandle::cleanup_before_exit` to manually call the cleanup logic. **You should always exit the tauri app immediately after this function returns and not use any tauri-related APIs.**
|
5
.changes/tauri-defaultvbox.md
Normal file
5
.changes/tauri-defaultvbox.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri': 'minor:feat'
|
||||
---
|
||||
|
||||
On Linux, add `Window::default_vbox` to get a reference to the `gtk::Box` that contains the menu bar and the webview.
|
5
.changes/tauri-libxdo-feat.md
Normal file
5
.changes/tauri-libxdo-feat.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri': 'minor:feat'
|
||||
---
|
||||
|
||||
Add `linux-libxdo` feature flag (disabled by default) to enable linking to `libxdo` which is used to make `Cut`, `Copy`, `Paste` and `SelectAll` native menu items work on Linux.
|
17
.changes/tauri-menu-tray-refactor.md
Normal file
17
.changes/tauri-menu-tray-refactor.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
'tauri': 'major:breaking'
|
||||
---
|
||||
|
||||
The tray icon and menu have received a huge refactor with a lot of breaking changes in order to add new functionalities and improve the DX around using them and here is an overview of the changes:
|
||||
|
||||
- All menu and tray types are now exported from `tauri::menu` and `tauri::tray` modules with new names so make sure to check the new types.
|
||||
- Removed `tauri::Builder::system_tray`, instead you should use `tauri::tray::TrayIconBuilder` inside `tauri::Builder::setup` hook to create your tray icons.
|
||||
- Changed `tauri::Builder::menu` to be a function to accomodate for new menu changes, you can passe `tauri::menu::Menu::default` to it to create a default menu.
|
||||
- Renamed `tauri::Context` methods `system_tray_icon`, `tauri::Context::system_tray_icon_mut` and `tauri::Context::set_system_tray_icon` to `tauri::Context::tray_icon`, `tauri::Context::tray_icon_mut` and `tauri::Context::set_tray_icon` to be consistent with new type names.
|
||||
- Added `RunEvent::MenuEvent` and `RunEvent::TrayIconEvent`.
|
||||
- Added `App/AppHandle::set_menu`, `App/AppHandle::remove_menu`, `App/AppHandle::show_menu`, `App/AppHandle::hide_menu` and `App/AppHandle::menu` to access, remove, hide or show the app-wide menu that is used as the global menu on macOS and on all windows that don't have a specific menu set for it on Windows and Linux.
|
||||
- Added `Window::set_menu`, `Window::remove_menu`, `Window::show_menu`, `Window::hide_menu`, `Window::is_menu_visible` and `Window::menu` to access, remove, hide or show the menu on this window.
|
||||
- Added `Window::popup_menu` and `Window::popup_menu_at` to show a context menu on the window at the cursor position or at a specific position. You can also popup a context menu using `popup` and `popup_at` methods from `ContextMenu` trait which is implemented for `Menu` and `Submenu` types.
|
||||
- Added `App/AppHandle::tray`, `App/AppHandle::tray_by_id`, `App/AppHandle::remove_tray` and `App/AppHandle::remove_tray_by_id` to access or remove a registered tray.
|
||||
- Added `WindowBuilder/App/AppHandle::on_menu_event` to register a new menu event handler.
|
||||
- Added `App/AppHandle::on_tray_icon_event` to register a new tray event handler.
|
5
.changes/tauri-nsview.md
Normal file
5
.changes/tauri-nsview.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri': 'minor:feat'
|
||||
---
|
||||
|
||||
On macOS, add `Window::ns_view` to get a pointer to the NSWindow content view.
|
5
.changes/tauri-run_on_main_thread.md
Normal file
5
.changes/tauri-run_on_main_thread.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri': 'minor:feat'
|
||||
---
|
||||
|
||||
Expose `run_on_main_thread` method on `App` that is similar to `AppHandle::run_on_main_thread`.
|
5
.changes/tauri-tray-icon-feat-flag.md
Normal file
5
.changes/tauri-tray-icon-feat-flag.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri': 'major:breaking'
|
||||
---
|
||||
|
||||
Renamed `system-tray` feature flag to `tray-icon`.
|
2
.github/workflows/lint-core.yml
vendored
2
.github/workflows/lint-core.yml
vendored
@ -50,7 +50,7 @@ jobs:
|
||||
clippy:
|
||||
- { args: '', key: 'empty' }
|
||||
- {
|
||||
args: '--features compression,wry,isolation,custom-protocol,system-tray,test',
|
||||
args: '--features compression,wry,linux-ipc-protocol,isolation,custom-protocol,tray-icon,test',
|
||||
key: 'all'
|
||||
}
|
||||
- { args: '--features custom-protocol', key: 'custom-protocol' }
|
||||
|
2
.github/workflows/test-core.yml
vendored
2
.github/workflows/test-core.yml
vendored
@ -72,7 +72,7 @@ jobs:
|
||||
key: no-default
|
||||
}
|
||||
- {
|
||||
args: --features compression,wry,isolation,custom-protocol,system-tray,test,
|
||||
args: --features compression,wry,linux-ipc-protocol,isolation,custom-protocol,tray-icon,test,
|
||||
key: all
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,6 @@ exclude = [
|
||||
# examples that can be compiled with the tauri CLI
|
||||
"examples/api/src-tauri",
|
||||
"examples/resources/src-tauri",
|
||||
"examples/sidecar/src-tauri",
|
||||
"examples/web/core",
|
||||
"examples/file-associations/src-tauri",
|
||||
"examples/workspace",
|
||||
|
@ -43,7 +43,12 @@ pub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> {
|
||||
name: "tauri".into(),
|
||||
alias: None,
|
||||
kind: DependencyKind::Normal,
|
||||
all_cli_managed_features: Some(TauriConfig::all_features()),
|
||||
all_cli_managed_features: Some(
|
||||
TauriConfig::all_features()
|
||||
.into_iter()
|
||||
.filter(|f| f != &"tray-icon")
|
||||
.collect(),
|
||||
),
|
||||
expected_features: config
|
||||
.tauri
|
||||
.features()
|
||||
|
@ -120,7 +120,7 @@ impl CodegenContext {
|
||||
config_parent.join(icon).display()
|
||||
);
|
||||
}
|
||||
if let Some(tray_icon) = config.tauri.system_tray.as_ref().map(|t| &t.icon_path) {
|
||||
if let Some(tray_icon) = config.tauri.tray_icon.as_ref().map(|t| &t.icon_path) {
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
config_parent.join(tray_icon).display()
|
||||
|
@ -222,7 +222,7 @@ fn insert_into_xml(xml: &str, block_identifier: &str, parent_tag: &str, contents
|
||||
rewritten.push(line.to_string());
|
||||
}
|
||||
|
||||
rewritten.join("\n").to_string()
|
||||
rewritten.join("\n")
|
||||
}
|
||||
|
||||
pub fn update_android_manifest(block_identifier: &str, parent: &str, insert: String) -> Result<()> {
|
||||
@ -294,16 +294,14 @@ dependencies {"
|
||||
mod tests {
|
||||
#[test]
|
||||
fn insert_into_xml() {
|
||||
let manifest = format!(
|
||||
r#"<manifest>
|
||||
let manifest = r#"<manifest>
|
||||
<application>
|
||||
<intent-filter>
|
||||
</intent-filter>
|
||||
</application>
|
||||
</manifest>"#
|
||||
);
|
||||
</manifest>"#;
|
||||
let id = "tauritest";
|
||||
let new = super::insert_into_xml(&manifest, id, "application", "<something></something>");
|
||||
let new = super::insert_into_xml(manifest, id, "application", "<something></something>");
|
||||
|
||||
let block_id_comment = super::xml_block_comment(id);
|
||||
let expected = format!(
|
||||
|
@ -24,7 +24,7 @@ Tauri apps can have custom menus and have tray-type interfaces. They can be upda
|
||||
|
||||
## This module
|
||||
|
||||
- Embed, hash, and compress assets, including icons for the app as well as the system-tray.
|
||||
- Embed, hash, and compress assets, including icons for the app as well as the tray icon.
|
||||
- Parse `tauri.conf.json` at compile time and generate the Config struct.
|
||||
|
||||
To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.
|
||||
|
@ -264,8 +264,8 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
||||
);
|
||||
png_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
|
||||
}
|
||||
} else if target == Target::Linux {
|
||||
// handle default window icons for Linux targets
|
||||
} else {
|
||||
// handle default window icons for Unix targets
|
||||
let icon_path = find_icon(
|
||||
&config,
|
||||
&config_parent,
|
||||
@ -273,8 +273,6 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
||||
"icons/icon.png",
|
||||
);
|
||||
png_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
|
||||
} else {
|
||||
quote!(::std::option::Option::None)
|
||||
}
|
||||
};
|
||||
|
||||
@ -319,16 +317,16 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
||||
}
|
||||
);
|
||||
|
||||
let with_system_tray_icon_code = if target.is_desktop() {
|
||||
if let Some(tray) = &config.tauri.system_tray {
|
||||
let system_tray_icon_path = config_parent.join(&tray.icon_path);
|
||||
let ext = system_tray_icon_path.extension();
|
||||
let with_tray_icon_code = if target.is_desktop() {
|
||||
if let Some(tray) = &config.tauri.tray_icon {
|
||||
let tray_icon_icon_path = config_parent.join(&tray.icon_path);
|
||||
let ext = tray_icon_icon_path.extension();
|
||||
if ext.map_or(false, |e| e == "ico") {
|
||||
ico_icon(&root, &out_dir, system_tray_icon_path)
|
||||
.map(|i| quote!(context.set_system_tray_icon(#i);))?
|
||||
ico_icon(&root, &out_dir, tray_icon_icon_path)
|
||||
.map(|i| quote!(context.set_tray_icon(#i);))?
|
||||
} else if ext.map_or(false, |e| e == "png") {
|
||||
png_icon(&root, &out_dir, system_tray_icon_path)
|
||||
.map(|i| quote!(context.set_system_tray_icon(#i);))?
|
||||
png_icon(&root, &out_dir, tray_icon_icon_path)
|
||||
.map(|i| quote!(context.set_tray_icon(#i);))?
|
||||
} else {
|
||||
quote!(compile_error!(
|
||||
"The tray icon extension must be either `.ico` or `.png`."
|
||||
@ -432,7 +430,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
||||
#info_plist,
|
||||
#pattern,
|
||||
);
|
||||
#with_system_tray_icon_code
|
||||
#with_tray_icon_code
|
||||
context
|
||||
}))
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
//! [![](https://github.com/tauri-apps/tauri/raw/dev/.github/splash.png)](https://tauri.app)
|
||||
//!
|
||||
//! - Embed, hash, and compress assets, including icons for the app as well as the system-tray.
|
||||
//! - Embed, hash, and compress assets, including icons for the app as well as the tray icon.
|
||||
//! - Parse `tauri.conf.json` at compile time and generate the Config struct.
|
||||
|
||||
#![doc(
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Config",
|
||||
"description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler and define a system tray.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"bundle\": {}, \"security\": { \"csp\": null }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
|
||||
"description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler and define a tray icon.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"bundle\": {}, \"security\": { \"csp\": null }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
@ -223,11 +223,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"systemTray": {
|
||||
"description": "Configuration for app system tray.",
|
||||
"trayIcon": {
|
||||
"description": "Configuration for app tray icon.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SystemTrayConfig"
|
||||
"$ref": "#/definitions/TrayIconConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@ -2065,15 +2065,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SystemTrayConfig": {
|
||||
"description": "Configuration for application system tray icon.\n\nSee more: https://tauri.app/v1/api/config#systemtrayconfig",
|
||||
"TrayIconConfig": {
|
||||
"description": "Configuration for application tray icon.\n\nSee more: https://tauri.app/v1/api/config#trayiconconfig",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"iconPath"
|
||||
],
|
||||
"properties": {
|
||||
"iconPath": {
|
||||
"description": "Path to the default icon to use on the system tray.",
|
||||
"description": "Path to the default icon to use for the tray icon.",
|
||||
"type": "string"
|
||||
},
|
||||
"iconAsTemplate": {
|
||||
@ -2092,6 +2092,13 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"description": "Tray icon tooltip on Windows and macOS",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
@ -16,7 +16,7 @@ rust-version = { workspace = true }
|
||||
features = [ "dox" ]
|
||||
|
||||
[dependencies]
|
||||
wry = { version = "0.30", default-features = false, features = [ "file-drop", "protocol" ] }
|
||||
wry = { version = "0.31", default-features = false, features = [ "file-drop", "protocol" ] }
|
||||
tauri-runtime = { version = "0.13.0-alpha.6", path = "../tauri-runtime" }
|
||||
tauri-utils = { version = "2.0.0-alpha.6", path = "../tauri-utils" }
|
||||
uuid = { version = "1", features = [ "v4" ] }
|
||||
@ -26,9 +26,9 @@ raw-window-handle = "0.5"
|
||||
[target."cfg(windows)".dependencies]
|
||||
webview2-com = "0.25"
|
||||
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
version = "0.48"
|
||||
features = [ "Win32_Foundation" ]
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
version = "0.48"
|
||||
features = [ "Win32_Foundation" ]
|
||||
|
||||
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||
gtk = { version = "0.16", features = [ "v3_24" ] }
|
||||
@ -39,12 +39,11 @@ percent-encoding = "2.1"
|
||||
cocoa = "0.24"
|
||||
|
||||
[target."cfg(target_os = \"android\")".dependencies]
|
||||
jni = "0.20"
|
||||
jni = "0.21"
|
||||
|
||||
[features]
|
||||
dox = [ "wry/dox" ]
|
||||
devtools = [ "wry/devtools", "tauri-runtime/devtools" ]
|
||||
system-tray = [ "tauri-runtime/system-tray", "wry/tray" ]
|
||||
macos-private-api = [
|
||||
"wry/fullscreen",
|
||||
"wry/transparent",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,238 +0,0 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pub use tauri_runtime::{
|
||||
menu::{
|
||||
Menu, MenuEntry, MenuItem, MenuUpdate, Submenu, SystemTrayMenu, SystemTrayMenuEntry,
|
||||
SystemTrayMenuItem, TrayHandle,
|
||||
},
|
||||
Icon, SystemTrayEvent,
|
||||
};
|
||||
use wry::application::event_loop::EventLoopWindowTarget;
|
||||
pub use wry::application::{
|
||||
event::TrayEvent,
|
||||
event_loop::EventLoopProxy,
|
||||
menu::{
|
||||
ContextMenu as WryContextMenu, CustomMenuItem as WryCustomMenuItem, MenuItem as WryMenuItem,
|
||||
},
|
||||
system_tray::Icon as WryTrayIcon,
|
||||
TrayId as WryTrayId,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use wry::application::platform::macos::{
|
||||
CustomMenuItemExtMacOS, SystemTrayBuilderExtMacOS, SystemTrayExtMacOS,
|
||||
};
|
||||
|
||||
use wry::application::system_tray::{SystemTray as WrySystemTray, SystemTrayBuilder};
|
||||
|
||||
use crate::{send_user_message, Context, Error, Message, Result, TrayId, TrayMessage};
|
||||
|
||||
use tauri_runtime::{menu::MenuHash, SystemTray, UserEvent};
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
pub type GlobalSystemTrayEventHandler = Box<dyn Fn(TrayId, &SystemTrayEvent) + Send>;
|
||||
pub type GlobalSystemTrayEventListeners = Arc<Mutex<Vec<Arc<GlobalSystemTrayEventHandler>>>>;
|
||||
|
||||
pub type SystemTrayEventHandler = Box<dyn Fn(&SystemTrayEvent) + Send>;
|
||||
pub type SystemTrayEventListeners = Arc<Mutex<Vec<Arc<SystemTrayEventHandler>>>>;
|
||||
pub type SystemTrayItems = Arc<Mutex<HashMap<u16, WryCustomMenuItem>>>;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct TrayContext {
|
||||
pub tray: Arc<Mutex<Option<WrySystemTray>>>,
|
||||
pub listeners: SystemTrayEventListeners,
|
||||
pub items: SystemTrayItems,
|
||||
}
|
||||
|
||||
impl fmt::Debug for TrayContext {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TrayContext")
|
||||
.field("items", &self.items)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SystemTrayManager {
|
||||
pub trays: Arc<Mutex<HashMap<TrayId, TrayContext>>>,
|
||||
pub global_listeners: GlobalSystemTrayEventListeners,
|
||||
}
|
||||
|
||||
impl fmt::Debug for SystemTrayManager {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SystemTrayManager")
|
||||
.field("trays", &self.trays)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around a [`wry::application::system_tray::Icon`] that can be created from an [`WindowIcon`].
|
||||
pub struct TrayIcon(pub(crate) WryTrayIcon);
|
||||
|
||||
impl TryFrom<Icon> for TrayIcon {
|
||||
type Error = Error;
|
||||
fn try_from(icon: Icon) -> std::result::Result<Self, Self::Error> {
|
||||
WryTrayIcon::from_rgba(icon.rgba, icon.width, icon.height)
|
||||
.map(Self)
|
||||
.map_err(crate::icon_err)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_tray<T>(
|
||||
id: WryTrayId,
|
||||
system_tray: SystemTray,
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
) -> crate::Result<(WrySystemTray, HashMap<u16, WryCustomMenuItem>)> {
|
||||
let icon = TrayIcon::try_from(system_tray.icon.expect("tray icon not set"))?;
|
||||
|
||||
let mut items = HashMap::new();
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = SystemTrayBuilder::new(
|
||||
icon.0,
|
||||
system_tray
|
||||
.menu
|
||||
.map(|menu| to_wry_context_menu(&mut items, menu)),
|
||||
)
|
||||
.with_id(id);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
builder = builder
|
||||
.with_icon_as_template(system_tray.icon_as_template)
|
||||
.with_menu_on_left_click(system_tray.menu_on_left_click);
|
||||
|
||||
if let Some(title) = system_tray.title {
|
||||
builder = builder.with_title(&title);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tooltip) = system_tray.tooltip {
|
||||
builder = builder.with_tooltip(&tooltip);
|
||||
}
|
||||
|
||||
let tray = builder
|
||||
.build(event_loop)
|
||||
.map_err(|e| Error::SystemTray(Box::new(e)))?;
|
||||
|
||||
Ok((tray, items))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SystemTrayHandle<T: UserEvent> {
|
||||
pub(crate) context: Context<T>,
|
||||
pub(crate) id: TrayId,
|
||||
pub(crate) proxy: EventLoopProxy<super::Message<T>>,
|
||||
}
|
||||
|
||||
impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
|
||||
fn set_icon(&self, icon: Icon) -> Result<()> {
|
||||
self
|
||||
.proxy
|
||||
.send_event(Message::Tray(self.id, TrayMessage::UpdateIcon(icon)))
|
||||
.map_err(|_| Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_menu(&self, menu: SystemTrayMenu) -> Result<()> {
|
||||
self
|
||||
.proxy
|
||||
.send_event(Message::Tray(self.id, TrayMessage::UpdateMenu(menu)))
|
||||
.map_err(|_| Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> {
|
||||
self
|
||||
.proxy
|
||||
.send_event(Message::Tray(self.id, TrayMessage::UpdateItem(id, update)))
|
||||
.map_err(|_| Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_icon_as_template(&self, is_template: bool) -> tauri_runtime::Result<()> {
|
||||
self
|
||||
.proxy
|
||||
.send_event(Message::Tray(
|
||||
self.id,
|
||||
TrayMessage::UpdateIconAsTemplate(is_template),
|
||||
))
|
||||
.map_err(|_| Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_title(&self, title: &str) -> tauri_runtime::Result<()> {
|
||||
self
|
||||
.proxy
|
||||
.send_event(Message::Tray(
|
||||
self.id,
|
||||
TrayMessage::UpdateTitle(title.to_owned()),
|
||||
))
|
||||
.map_err(|_| Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_tooltip(&self, tooltip: &str) -> Result<()> {
|
||||
self
|
||||
.proxy
|
||||
.send_event(Message::Tray(
|
||||
self.id,
|
||||
TrayMessage::UpdateTooltip(tooltip.to_owned()),
|
||||
))
|
||||
.map_err(|_| Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn destroy(&self) -> Result<()> {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
send_user_message(
|
||||
&self.context,
|
||||
Message::Tray(self.id, TrayMessage::Destroy(tx)),
|
||||
)?;
|
||||
rx.recv().unwrap()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SystemTrayMenuItem> for crate::MenuItemWrapper {
|
||||
fn from(item: SystemTrayMenuItem) -> Self {
|
||||
match item {
|
||||
SystemTrayMenuItem::Separator => Self(WryMenuItem::Separator),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_wry_context_menu(
|
||||
custom_menu_items: &mut HashMap<MenuHash, WryCustomMenuItem>,
|
||||
menu: SystemTrayMenu,
|
||||
) -> WryContextMenu {
|
||||
let mut tray_menu = WryContextMenu::new();
|
||||
for item in menu.items {
|
||||
match item {
|
||||
SystemTrayMenuEntry::CustomItem(c) => {
|
||||
#[allow(unused_mut)]
|
||||
let mut item = tray_menu.add_item(crate::MenuItemAttributesWrapper::from(&c).0);
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(native_image) = c.native_image {
|
||||
item.set_native_image(crate::NativeImageWrapper::from(native_image).0);
|
||||
}
|
||||
custom_menu_items.insert(c.id, item);
|
||||
}
|
||||
SystemTrayMenuEntry::NativeItem(i) => {
|
||||
tray_menu.add_native_item(crate::MenuItemWrapper::from(i).0);
|
||||
}
|
||||
SystemTrayMenuEntry::Submenu(submenu) => {
|
||||
tray_menu.add_submenu(
|
||||
&submenu.title,
|
||||
submenu.enabled,
|
||||
to_wry_context_menu(custom_menu_items, submenu.inner),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
tray_menu
|
||||
}
|
@ -42,12 +42,12 @@ features = [ "Win32_Foundation" ]
|
||||
gtk = { version = "0.16", features = [ "v3_24" ] }
|
||||
|
||||
[target."cfg(target_os = \"android\")".dependencies]
|
||||
jni = "0.20"
|
||||
jni = "0.21"
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
url = "2"
|
||||
|
||||
[features]
|
||||
devtools = [ ]
|
||||
system-tray = [ ]
|
||||
macos-private-api = [ ]
|
||||
|
||||
|
@ -20,8 +20,6 @@ use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod http;
|
||||
/// Create window and system tray menus.
|
||||
pub mod menu;
|
||||
/// Types useful for interacting with a user's monitors.
|
||||
pub mod monitor;
|
||||
pub mod webview;
|
||||
@ -31,7 +29,7 @@ use monitor::Monitor;
|
||||
use webview::WindowBuilder;
|
||||
use window::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
CursorIcon, DetachedWindow, PendingWindow, WindowEvent,
|
||||
CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent,
|
||||
};
|
||||
|
||||
use crate::http::{
|
||||
@ -41,156 +39,6 @@ use crate::http::{
|
||||
InvalidUri,
|
||||
};
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
use std::fmt;
|
||||
|
||||
pub type TrayId = u16;
|
||||
pub type TrayEventHandler = dyn Fn(&SystemTrayEvent) + Send + 'static;
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[non_exhaustive]
|
||||
pub struct SystemTray {
|
||||
pub id: TrayId,
|
||||
pub icon: Option<Icon>,
|
||||
pub menu: Option<menu::SystemTrayMenu>,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub icon_as_template: bool,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub menu_on_left_click: bool,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub title: Option<String>,
|
||||
pub on_event: Option<Box<TrayEventHandler>>,
|
||||
pub tooltip: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
impl fmt::Debug for SystemTray {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut d = f.debug_struct("SystemTray");
|
||||
d.field("id", &self.id)
|
||||
.field("icon", &self.icon)
|
||||
.field("menu", &self.menu);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
d.field("icon_as_template", &self.icon_as_template)
|
||||
.field("menu_on_left_click", &self.menu_on_left_click)
|
||||
.field("title", &self.title);
|
||||
}
|
||||
d.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
impl Clone for SystemTray {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
icon: self.icon.clone(),
|
||||
menu: self.menu.clone(),
|
||||
on_event: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
icon_as_template: self.icon_as_template,
|
||||
#[cfg(target_os = "macos")]
|
||||
menu_on_left_click: self.menu_on_left_click,
|
||||
#[cfg(target_os = "macos")]
|
||||
title: self.title.clone(),
|
||||
tooltip: self.tooltip.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
impl Default for SystemTray {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: rand::random(),
|
||||
icon: None,
|
||||
menu: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
icon_as_template: false,
|
||||
#[cfg(target_os = "macos")]
|
||||
menu_on_left_click: false,
|
||||
#[cfg(target_os = "macos")]
|
||||
title: None,
|
||||
on_event: None,
|
||||
tooltip: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
impl SystemTray {
|
||||
/// Creates a new system tray that only renders an icon.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn menu(&self) -> Option<&menu::SystemTrayMenu> {
|
||||
self.menu.as_ref()
|
||||
}
|
||||
|
||||
/// Sets the tray id.
|
||||
#[must_use]
|
||||
pub fn with_id(mut self, id: TrayId) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the tray icon.
|
||||
#[must_use]
|
||||
pub fn with_icon(mut self, icon: Icon) -> Self {
|
||||
self.icon.replace(icon);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the tray icon as template.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
|
||||
self.icon_as_template = is_template;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether the menu should appear when the tray receives a left click. Defaults to `true`.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
pub fn with_menu_on_left_click(mut self, menu_on_left_click: bool) -> Self {
|
||||
self.menu_on_left_click = menu_on_left_click;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
pub fn with_title(mut self, title: &str) -> Self {
|
||||
self.title = Some(title.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the tray icon tooltip.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported
|
||||
#[must_use]
|
||||
pub fn with_tooltip(mut self, tooltip: &str) -> Self {
|
||||
self.tooltip = Some(tooltip.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the menu to show when the system tray is right clicked.
|
||||
#[must_use]
|
||||
pub fn with_menu(mut self, menu: menu::SystemTrayMenu) -> Self {
|
||||
self.menu.replace(menu);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_event<F: Fn(&SystemTrayEvent) + Send + 'static>(mut self, f: F) -> Self {
|
||||
self.on_event.replace(Box::new(f));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of user attention requested on a window.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
@ -243,11 +91,6 @@ pub enum Error {
|
||||
/// Failed to serialize/deserialize.
|
||||
#[error("JSON error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
/// Encountered an error creating the app system tray.
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
|
||||
#[error("error encountered during tray setup: {0}")]
|
||||
SystemTray(Box<dyn std::error::Error + Send + Sync>),
|
||||
/// Failed to load window icon.
|
||||
#[error("invalid icon: {0}")]
|
||||
InvalidIcon(Box<dyn std::error::Error + Send + Sync>),
|
||||
@ -328,24 +171,6 @@ pub enum ExitRequestedEventAction {
|
||||
Prevent,
|
||||
}
|
||||
|
||||
/// A system tray event.
|
||||
#[derive(Debug)]
|
||||
pub enum SystemTrayEvent {
|
||||
MenuItemClick(u16),
|
||||
LeftClick {
|
||||
position: PhysicalPosition<f64>,
|
||||
size: PhysicalSize<f64>,
|
||||
},
|
||||
RightClick {
|
||||
position: PhysicalPosition<f64>,
|
||||
size: PhysicalSize<f64>,
|
||||
},
|
||||
DoubleClick {
|
||||
position: PhysicalPosition<f64>,
|
||||
size: PhysicalSize<f64>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Metadata for a runtime event loop iteration on `run_iteration`.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct RunIteration {
|
||||
@ -373,22 +198,15 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
|
||||
fn create_proxy(&self) -> <Self::Runtime as Runtime<T>>::EventLoopProxy;
|
||||
|
||||
/// Create a new webview window.
|
||||
fn create_window(
|
||||
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
||||
&self,
|
||||
pending: PendingWindow<T, Self::Runtime>,
|
||||
before_webview_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self::Runtime>>;
|
||||
|
||||
/// Run a task on the main thread.
|
||||
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;
|
||||
|
||||
/// Adds an icon to the system tray with the specified menu items.
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "system-tray"))))]
|
||||
fn system_tray(
|
||||
&self,
|
||||
system_tray: SystemTray,
|
||||
) -> Result<<Self::Runtime as Runtime<T>>::TrayHandler>;
|
||||
|
||||
fn raw_display_handle(&self) -> RawDisplayHandle;
|
||||
|
||||
fn primary_monitor(&self) -> Option<Monitor>;
|
||||
@ -407,9 +225,9 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
|
||||
/// Finds an Android class in the project scope.
|
||||
#[cfg(target_os = "android")]
|
||||
fn find_class<'a>(
|
||||
&'a self,
|
||||
env: jni::JNIEnv<'a>,
|
||||
activity: jni::objects::JObject<'a>,
|
||||
&self,
|
||||
env: &mut jni::JNIEnv<'a>,
|
||||
activity: &jni::objects::JObject<'_>,
|
||||
name: impl Into<String>,
|
||||
) -> std::result::Result<jni::objects::JClass<'a>, jni::errors::Error>;
|
||||
|
||||
@ -419,34 +237,35 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
|
||||
#[cfg(target_os = "android")]
|
||||
fn run_on_android_context<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce(jni::JNIEnv<'_>, jni::objects::JObject<'_>, jni::objects::JObject<'_>)
|
||||
+ Send
|
||||
+ 'static;
|
||||
F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static;
|
||||
}
|
||||
|
||||
pub trait EventLoopProxy<T: UserEvent>: Debug + Clone + Send + Sync {
|
||||
fn send_event(&self, event: T) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RuntimeInitArgs {
|
||||
#[cfg(windows)]
|
||||
pub msg_hook: Option<Box<dyn FnMut(*const std::ffi::c_void) -> bool + 'static>>,
|
||||
}
|
||||
|
||||
/// The webview runtime interface.
|
||||
pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
|
||||
/// The message dispatcher.
|
||||
type Dispatcher: Dispatch<T, Runtime = Self>;
|
||||
/// The runtime handle type.
|
||||
type Handle: RuntimeHandle<T, Runtime = Self>;
|
||||
/// The tray handler type.
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
type TrayHandler: menu::TrayHandle;
|
||||
/// The proxy type.
|
||||
type EventLoopProxy: EventLoopProxy<T>;
|
||||
|
||||
/// Creates a new webview runtime. Must be used on the main thread.
|
||||
fn new() -> Result<Self>;
|
||||
fn new(args: RuntimeInitArgs) -> Result<Self>;
|
||||
|
||||
/// Creates a new webview runtime on any thread.
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "linux"))))]
|
||||
fn new_any_thread() -> Result<Self>;
|
||||
fn new_any_thread(args: RuntimeInitArgs) -> Result<Self>;
|
||||
|
||||
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
|
||||
fn create_proxy(&self) -> Self::EventLoopProxy;
|
||||
@ -455,17 +274,11 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
|
||||
fn handle(&self) -> Self::Handle;
|
||||
|
||||
/// Create a new webview window.
|
||||
fn create_window(&self, pending: PendingWindow<T, Self>) -> Result<DetachedWindow<T, Self>>;
|
||||
|
||||
/// Adds the icon to the system tray with the specified menu items.
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
|
||||
fn system_tray(&self, system_tray: SystemTray) -> Result<Self::TrayHandler>;
|
||||
|
||||
/// Registers a system tray event handler.
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
|
||||
fn on_system_tray_event<F: Fn(TrayId, &SystemTrayEvent) + Send + 'static>(&mut self, f: F);
|
||||
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
||||
&self,
|
||||
pending: PendingWindow<T, Self>,
|
||||
before_webview_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self>>;
|
||||
|
||||
fn primary_monitor(&self) -> Option<Monitor>;
|
||||
fn available_monitors(&self) -> Vec<Monitor>;
|
||||
@ -520,9 +333,6 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
/// Registers a window event handler.
|
||||
fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> Uuid;
|
||||
|
||||
/// Registers a window event handler.
|
||||
fn on_menu_event<F: Fn(&window::MenuEvent) + Send + 'static>(&self, f: F) -> Uuid;
|
||||
|
||||
/// Runs a closure with the platform webview object as argument.
|
||||
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()>;
|
||||
|
||||
@ -606,9 +416,6 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
/// Gets the window's current title.
|
||||
fn title(&self) -> Result<String>;
|
||||
|
||||
/// Gets the window menu current visibility state.
|
||||
fn is_menu_visible(&self) -> Result<bool>;
|
||||
|
||||
/// Returns the monitor on which the window currently resides.
|
||||
///
|
||||
/// Returns None if current monitor can't be detected.
|
||||
@ -632,6 +439,16 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
))]
|
||||
fn gtk_window(&self) -> Result<gtk::ApplicationWindow>;
|
||||
|
||||
/// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window.
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
fn default_vbox(&self) -> Result<gtk::Box>;
|
||||
|
||||
fn raw_window_handle(&self) -> Result<raw_window_handle::RawWindowHandle>;
|
||||
|
||||
/// Returns the current window theme.
|
||||
@ -651,9 +468,10 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> Result<()>;
|
||||
|
||||
/// Create a new webview window.
|
||||
fn create_window(
|
||||
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
||||
&mut self,
|
||||
pending: PendingWindow<T, Self::Runtime>,
|
||||
before_webview_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self::Runtime>>;
|
||||
|
||||
/// Updates the window resizable flag.
|
||||
@ -701,12 +519,6 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
/// Unminimizes the window.
|
||||
fn unminimize(&self) -> Result<()>;
|
||||
|
||||
/// Shows the window menu.
|
||||
fn show_menu(&self) -> Result<()>;
|
||||
|
||||
/// Hides the window menu.
|
||||
fn hide_menu(&self) -> Result<()>;
|
||||
|
||||
/// Shows the window.
|
||||
fn show(&self) -> Result<()>;
|
||||
|
||||
@ -780,7 +592,4 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
|
||||
/// Executes javascript on the window this [`Dispatch`] represents.
|
||||
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>;
|
||||
|
||||
/// Applies the specified `update` to the menu item associated with the given `id`.
|
||||
fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> Result<()>;
|
||||
}
|
||||
|
@ -1,747 +0,0 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
pub type MenuHash = u16;
|
||||
pub type MenuId = String;
|
||||
pub type MenuIdRef<'a> = &'a str;
|
||||
|
||||
/// Named images defined by the system.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NativeImage {
|
||||
/// An add item template image.
|
||||
Add,
|
||||
/// Advanced preferences toolbar icon for the preferences window.
|
||||
Advanced,
|
||||
/// A Bluetooth template image.
|
||||
Bluetooth,
|
||||
/// Bookmarks image suitable for a template.
|
||||
Bookmarks,
|
||||
/// A caution image.
|
||||
Caution,
|
||||
/// A color panel toolbar icon.
|
||||
ColorPanel,
|
||||
/// A column view mode template image.
|
||||
ColumnView,
|
||||
/// A computer icon.
|
||||
Computer,
|
||||
/// An enter full-screen mode template image.
|
||||
EnterFullScreen,
|
||||
/// Permissions for all users.
|
||||
Everyone,
|
||||
/// An exit full-screen mode template image.
|
||||
ExitFullScreen,
|
||||
/// A cover flow view mode template image.
|
||||
FlowView,
|
||||
/// A folder image.
|
||||
Folder,
|
||||
/// A burnable folder icon.
|
||||
FolderBurnable,
|
||||
/// A smart folder icon.
|
||||
FolderSmart,
|
||||
/// A link template image.
|
||||
FollowLinkFreestanding,
|
||||
/// A font panel toolbar icon.
|
||||
FontPanel,
|
||||
/// A `go back` template image.
|
||||
GoLeft,
|
||||
/// A `go forward` template image.
|
||||
GoRight,
|
||||
/// Home image suitable for a template.
|
||||
Home,
|
||||
/// An iChat Theater template image.
|
||||
IChatTheater,
|
||||
/// An icon view mode template image.
|
||||
IconView,
|
||||
/// An information toolbar icon.
|
||||
Info,
|
||||
/// A template image used to denote invalid data.
|
||||
InvalidDataFreestanding,
|
||||
/// A generic left-facing triangle template image.
|
||||
LeftFacingTriangle,
|
||||
/// A list view mode template image.
|
||||
ListView,
|
||||
/// A locked padlock template image.
|
||||
LockLocked,
|
||||
/// An unlocked padlock template image.
|
||||
LockUnlocked,
|
||||
/// A horizontal dash, for use in menus.
|
||||
MenuMixedState,
|
||||
/// A check mark template image, for use in menus.
|
||||
MenuOnState,
|
||||
/// A MobileMe icon.
|
||||
MobileMe,
|
||||
/// A drag image for multiple items.
|
||||
MultipleDocuments,
|
||||
/// A network icon.
|
||||
Network,
|
||||
/// A path button template image.
|
||||
Path,
|
||||
/// General preferences toolbar icon for the preferences window.
|
||||
PreferencesGeneral,
|
||||
/// A Quick Look template image.
|
||||
QuickLook,
|
||||
/// A refresh template image.
|
||||
RefreshFreestanding,
|
||||
/// A refresh template image.
|
||||
Refresh,
|
||||
/// A remove item template image.
|
||||
Remove,
|
||||
/// A reveal contents template image.
|
||||
RevealFreestanding,
|
||||
/// A generic right-facing triangle template image.
|
||||
RightFacingTriangle,
|
||||
/// A share view template image.
|
||||
Share,
|
||||
/// A slideshow template image.
|
||||
Slideshow,
|
||||
/// A badge for a `smart` item.
|
||||
SmartBadge,
|
||||
/// Small green indicator, similar to iChat’s available image.
|
||||
StatusAvailable,
|
||||
/// Small clear indicator.
|
||||
StatusNone,
|
||||
/// Small yellow indicator, similar to iChat’s idle image.
|
||||
StatusPartiallyAvailable,
|
||||
/// Small red indicator, similar to iChat’s unavailable image.
|
||||
StatusUnavailable,
|
||||
/// A stop progress template image.
|
||||
StopProgressFreestanding,
|
||||
/// A stop progress button template image.
|
||||
StopProgress,
|
||||
|
||||
/// An image of the empty trash can.
|
||||
TrashEmpty,
|
||||
/// An image of the full trash can.
|
||||
TrashFull,
|
||||
/// Permissions for a single user.
|
||||
User,
|
||||
/// User account toolbar icon for the preferences window.
|
||||
UserAccounts,
|
||||
/// Permissions for a group of users.
|
||||
UserGroup,
|
||||
/// Permissions for guests.
|
||||
UserGuest,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MenuUpdate {
|
||||
/// Modifies the enabled state of the menu item.
|
||||
SetEnabled(bool),
|
||||
/// Modifies the title (label) of the menu item.
|
||||
SetTitle(String),
|
||||
/// Modifies the selected state of the menu item.
|
||||
SetSelected(bool),
|
||||
/// Update native image.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
|
||||
SetNativeImage(NativeImage),
|
||||
}
|
||||
|
||||
pub trait TrayHandle: fmt::Debug + Clone + Send + Sync {
|
||||
fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>;
|
||||
fn set_menu(&self, menu: crate::menu::SystemTrayMenu) -> crate::Result<()>;
|
||||
fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>;
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()>;
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_title(&self, title: &str) -> crate::Result<()>;
|
||||
fn set_tooltip(&self, tooltip: &str) -> crate::Result<()>;
|
||||
fn destroy(&self) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
/// A window menu.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct Menu {
|
||||
pub items: Vec<MenuEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct Submenu {
|
||||
pub title: String,
|
||||
pub enabled: bool,
|
||||
pub inner: Menu,
|
||||
}
|
||||
|
||||
impl Submenu {
|
||||
/// Creates a new submenu with the given title and menu items.
|
||||
pub fn new<S: Into<String>>(title: S, menu: Menu) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
enabled: true,
|
||||
inner: menu,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
/// Creates a new window menu.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Creates a menu filled with default menu items and submenus.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows**:
|
||||
/// - File
|
||||
/// - CloseWindow
|
||||
/// - Quit
|
||||
/// - Edit
|
||||
/// - Cut
|
||||
/// - Copy
|
||||
/// - Paste
|
||||
/// - Window
|
||||
/// - Minimize
|
||||
/// - CloseWindow
|
||||
///
|
||||
/// - **Linux**:
|
||||
/// - File
|
||||
/// - CloseWindow
|
||||
/// - Quit
|
||||
/// - Window
|
||||
/// - Minimize
|
||||
/// - CloseWindow
|
||||
///
|
||||
/// - **macOS**:
|
||||
/// - App
|
||||
/// - About
|
||||
/// - Separator
|
||||
/// - Services
|
||||
/// - Separator
|
||||
/// - Hide
|
||||
/// - HideOthers
|
||||
/// - ShowAll
|
||||
/// - Separator
|
||||
/// - Quit
|
||||
/// - File
|
||||
/// - CloseWindow
|
||||
/// - Edit
|
||||
/// - Undo
|
||||
/// - Redo
|
||||
/// - Separator
|
||||
/// - Cut
|
||||
/// - Copy
|
||||
/// - Paste
|
||||
/// - SelectAll
|
||||
/// - View
|
||||
/// - EnterFullScreen
|
||||
/// - Window
|
||||
/// - Minimize
|
||||
/// - Zoom
|
||||
/// - Separator
|
||||
/// - CloseWindow
|
||||
pub fn os_default(#[allow(unused)] app_name: &str) -> Self {
|
||||
let mut menu = Menu::new();
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
menu = menu.add_submenu(Submenu::new(
|
||||
app_name,
|
||||
Menu::new()
|
||||
.add_native_item(MenuItem::About(
|
||||
app_name.to_string(),
|
||||
AboutMetadata::default(),
|
||||
))
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_native_item(MenuItem::Services)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_native_item(MenuItem::Hide)
|
||||
.add_native_item(MenuItem::HideOthers)
|
||||
.add_native_item(MenuItem::ShowAll)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_native_item(MenuItem::Quit),
|
||||
));
|
||||
}
|
||||
|
||||
let mut file_menu = Menu::new();
|
||||
file_menu = file_menu.add_native_item(MenuItem::CloseWindow);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
file_menu = file_menu.add_native_item(MenuItem::Quit);
|
||||
}
|
||||
menu = menu.add_submenu(Submenu::new("File", file_menu));
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let mut edit_menu = Menu::new();
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Undo);
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Redo);
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Separator);
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Cut);
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Copy);
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Paste);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::SelectAll);
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
menu = menu.add_submenu(Submenu::new("Edit", edit_menu));
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
menu = menu.add_submenu(Submenu::new(
|
||||
"View",
|
||||
Menu::new().add_native_item(MenuItem::EnterFullScreen),
|
||||
));
|
||||
}
|
||||
|
||||
let mut window_menu = Menu::new();
|
||||
window_menu = window_menu.add_native_item(MenuItem::Minimize);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
window_menu = window_menu.add_native_item(MenuItem::Zoom);
|
||||
window_menu = window_menu.add_native_item(MenuItem::Separator);
|
||||
}
|
||||
window_menu = window_menu.add_native_item(MenuItem::CloseWindow);
|
||||
menu = menu.add_submenu(Submenu::new("Window", window_menu));
|
||||
|
||||
menu
|
||||
}
|
||||
|
||||
/// Creates a new window menu with the given items.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use tauri_runtime::menu::{Menu, MenuItem, CustomMenuItem, Submenu};
|
||||
/// Menu::with_items([
|
||||
/// MenuItem::SelectAll.into(),
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// MenuItem::Redo.into(),
|
||||
/// CustomMenuItem::new("toggle", "Toggle visibility").into(),
|
||||
/// Submenu::new("View", Menu::new()).into(),
|
||||
/// ]);
|
||||
/// ```
|
||||
pub fn with_items<I: IntoIterator<Item = MenuEntry>>(items: I) -> Self {
|
||||
Self {
|
||||
items: items.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the custom menu item to the menu.
|
||||
#[must_use]
|
||||
pub fn add_item(mut self, item: CustomMenuItem) -> Self {
|
||||
self.items.push(MenuEntry::CustomItem(item));
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a native item to the menu.
|
||||
#[must_use]
|
||||
pub fn add_native_item(mut self, item: MenuItem) -> Self {
|
||||
self.items.push(MenuEntry::NativeItem(item));
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an entry with submenu.
|
||||
#[must_use]
|
||||
pub fn add_submenu(mut self, submenu: Submenu) -> Self {
|
||||
self.items.push(MenuEntry::Submenu(submenu));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom menu item.
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct CustomMenuItem {
|
||||
pub id: MenuHash,
|
||||
pub id_str: MenuId,
|
||||
pub title: String,
|
||||
pub keyboard_accelerator: Option<String>,
|
||||
pub enabled: bool,
|
||||
pub selected: bool,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub native_image: Option<NativeImage>,
|
||||
}
|
||||
|
||||
impl CustomMenuItem {
|
||||
/// Create new custom menu item.
|
||||
pub fn new<I: Into<String>, T: Into<String>>(id: I, title: T) -> Self {
|
||||
let id_str = id.into();
|
||||
Self {
|
||||
id: Self::hash(&id_str),
|
||||
id_str,
|
||||
title: title.into(),
|
||||
keyboard_accelerator: None,
|
||||
enabled: true,
|
||||
selected: false,
|
||||
#[cfg(target_os = "macos")]
|
||||
native_image: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Assign a keyboard shortcut to the menu action.
|
||||
#[must_use]
|
||||
pub fn accelerator<T: Into<String>>(mut self, accelerator: T) -> Self {
|
||||
self.keyboard_accelerator.replace(accelerator.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
|
||||
#[must_use]
|
||||
/// A native image do render on the menu item.
|
||||
pub fn native_image(mut self, image: NativeImage) -> Self {
|
||||
self.native_image.replace(image);
|
||||
self
|
||||
}
|
||||
|
||||
/// Mark the item as disabled.
|
||||
#[must_use]
|
||||
pub fn disabled(mut self) -> Self {
|
||||
self.enabled = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Mark the item as selected.
|
||||
#[must_use]
|
||||
pub fn selected(mut self) -> Self {
|
||||
self.selected = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn hash(id: &str) -> MenuHash {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
id.hash(&mut hasher);
|
||||
hasher.finish() as MenuHash
|
||||
}
|
||||
}
|
||||
|
||||
/// A system tray menu.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct SystemTrayMenu {
|
||||
pub items: Vec<SystemTrayMenuEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct SystemTraySubmenu {
|
||||
pub title: String,
|
||||
pub enabled: bool,
|
||||
pub inner: SystemTrayMenu,
|
||||
}
|
||||
|
||||
impl SystemTraySubmenu {
|
||||
/// Creates a new submenu with the given title and menu items.
|
||||
pub fn new<S: Into<String>>(title: S, menu: SystemTrayMenu) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
enabled: true,
|
||||
inner: menu,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemTrayMenu {
|
||||
/// Creates a new system tray menu.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Adds the custom menu item to the system tray menu.
|
||||
#[must_use]
|
||||
pub fn add_item(mut self, item: CustomMenuItem) -> Self {
|
||||
self.items.push(SystemTrayMenuEntry::CustomItem(item));
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a native item to the system tray menu.
|
||||
#[must_use]
|
||||
pub fn add_native_item(mut self, item: SystemTrayMenuItem) -> Self {
|
||||
self.items.push(SystemTrayMenuEntry::NativeItem(item));
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an entry with submenu.
|
||||
#[must_use]
|
||||
pub fn add_submenu(mut self, submenu: SystemTraySubmenu) -> Self {
|
||||
self.items.push(SystemTrayMenuEntry::Submenu(submenu));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// An entry on the system tray menu.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SystemTrayMenuEntry {
|
||||
/// A custom item.
|
||||
CustomItem(CustomMenuItem),
|
||||
/// A native item.
|
||||
NativeItem(SystemTrayMenuItem),
|
||||
/// An entry with submenu.
|
||||
Submenu(SystemTraySubmenu),
|
||||
}
|
||||
|
||||
/// System tray menu item.
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum SystemTrayMenuItem {
|
||||
/// A separator.
|
||||
Separator,
|
||||
}
|
||||
|
||||
/// An entry on the system tray menu.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MenuEntry {
|
||||
/// A custom item.
|
||||
CustomItem(CustomMenuItem),
|
||||
/// A native item.
|
||||
NativeItem(MenuItem),
|
||||
/// An entry with submenu.
|
||||
Submenu(Submenu),
|
||||
}
|
||||
|
||||
impl From<CustomMenuItem> for MenuEntry {
|
||||
fn from(item: CustomMenuItem) -> Self {
|
||||
Self::CustomItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MenuItem> for MenuEntry {
|
||||
fn from(item: MenuItem) -> Self {
|
||||
Self::NativeItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Submenu> for MenuEntry {
|
||||
fn from(submenu: Submenu) -> Self {
|
||||
Self::Submenu(submenu)
|
||||
}
|
||||
}
|
||||
|
||||
/// Application metadata for the [`MenuItem::About`] action.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / macOS / Android / iOS:** The metadata is ignored on these platforms.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct AboutMetadata {
|
||||
/// The application name.
|
||||
pub version: Option<String>,
|
||||
/// The authors of the application.
|
||||
pub authors: Option<Vec<String>>,
|
||||
/// Application comments.
|
||||
pub comments: Option<String>,
|
||||
/// The copyright of the application.
|
||||
pub copyright: Option<String>,
|
||||
/// The license of the application.
|
||||
pub license: Option<String>,
|
||||
/// The application website.
|
||||
pub website: Option<String>,
|
||||
/// The website label.
|
||||
pub website_label: Option<String>,
|
||||
}
|
||||
|
||||
impl AboutMetadata {
|
||||
/// Creates the default metadata for the [`MenuItem::About`] action, which is just empty.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Defines the application version.
|
||||
pub fn version(mut self, version: impl Into<String>) -> Self {
|
||||
self.version.replace(version.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the application authors.
|
||||
pub fn authors(mut self, authors: Vec<String>) -> Self {
|
||||
self.authors.replace(authors);
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the application comments.
|
||||
pub fn comments(mut self, comments: impl Into<String>) -> Self {
|
||||
self.comments.replace(comments.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the application copyright.
|
||||
pub fn copyright(mut self, copyright: impl Into<String>) -> Self {
|
||||
self.copyright.replace(copyright.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the application license.
|
||||
pub fn license(mut self, license: impl Into<String>) -> Self {
|
||||
self.license.replace(license.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the application's website link.
|
||||
pub fn website(mut self, website: impl Into<String>) -> Self {
|
||||
self.website.replace(website.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the application's website link name.
|
||||
pub fn website_label(mut self, website_label: impl Into<String>) -> Self {
|
||||
self.website_label.replace(website_label.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A menu item, bound to a pre-defined action or `Custom` emit an event. Note that status bar only
|
||||
/// supports `Custom` menu item variants. And on the menu bar, some platforms might not support some
|
||||
/// of the variants. Unsupported variant will be no-op on such platform.
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum MenuItem {
|
||||
/// Shows a standard "About" item.
|
||||
///
|
||||
/// The first value is the application name, and the second is its metadata.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
/// - **Linux:** The metadata is only applied on Linux
|
||||
///
|
||||
About(String, AboutMetadata),
|
||||
|
||||
/// A standard "hide the app" menu item.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS:** Unsupported
|
||||
///
|
||||
Hide,
|
||||
|
||||
/// A standard "Services" menu item.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
Services,
|
||||
|
||||
/// A "hide all other windows" menu item.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
HideOthers,
|
||||
|
||||
/// A menu item to show all the windows for this app.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
ShowAll,
|
||||
|
||||
/// Close the current window.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS:** Unsupported
|
||||
///
|
||||
CloseWindow,
|
||||
|
||||
/// A "quit this app" menu icon.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS:** Unsupported
|
||||
///
|
||||
Quit,
|
||||
|
||||
/// A menu item for enabling copying (often text) from responders.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS / Linux:** Unsupported
|
||||
///
|
||||
Copy,
|
||||
|
||||
/// A menu item for enabling cutting (often text) from responders.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS / Linux:** Unsupported
|
||||
///
|
||||
Cut,
|
||||
|
||||
/// An "undo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle
|
||||
/// of events.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
Undo,
|
||||
|
||||
/// An "redo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle
|
||||
/// of events.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
Redo,
|
||||
|
||||
/// A menu item for selecting all (often text) from responders.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS / Linux:** Unsupported
|
||||
///
|
||||
SelectAll,
|
||||
|
||||
/// A menu item for pasting (often text) into responders.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS / Linux:** Unsupported
|
||||
///
|
||||
Paste,
|
||||
|
||||
/// A standard "enter full screen" item.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
EnterFullScreen,
|
||||
|
||||
/// An item for minimizing the window with the standard system controls.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS:** Unsupported
|
||||
///
|
||||
Minimize,
|
||||
|
||||
/// An item for instructing the app to zoom
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
Zoom,
|
||||
|
||||
/// Represents a Separator
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS:** Unsupported
|
||||
///
|
||||
Separator,
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
|
||||
//! Items specific to the [`Runtime`](crate::Runtime)'s webview.
|
||||
|
||||
use crate::{menu::Menu, window::DetachedWindow, Icon};
|
||||
use crate::{window::DetachedWindow, Icon};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri_utils::TitleBarStyle;
|
||||
@ -153,10 +153,6 @@ pub trait WindowBuilder: WindowBuilderBase {
|
||||
/// Initializes a new webview builder from a [`WindowConfig`]
|
||||
fn with_config(config: WindowConfig) -> Self;
|
||||
|
||||
/// Sets the menu for the window.
|
||||
#[must_use]
|
||||
fn menu(self, menu: Menu) -> Self;
|
||||
|
||||
/// Show window in the center of the screen.
|
||||
#[must_use]
|
||||
fn center(self) -> Self;
|
||||
@ -330,9 +326,6 @@ pub trait WindowBuilder: WindowBuilderBase {
|
||||
|
||||
/// Whether the icon was set or not.
|
||||
fn has_icon(&self) -> bool;
|
||||
|
||||
/// Gets the window menu.
|
||||
fn get_menu(&self) -> Option<&Menu>;
|
||||
}
|
||||
|
||||
/// IPC handler.
|
||||
|
@ -6,19 +6,20 @@
|
||||
|
||||
use crate::{
|
||||
http::{Request as HttpRequest, Response as HttpResponse},
|
||||
menu::{Menu, MenuEntry, MenuHash, MenuId},
|
||||
webview::{WebviewAttributes, WebviewIpcHandler},
|
||||
Dispatch, Runtime, UserEvent, WindowBuilder,
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use tauri_utils::{config::WindowConfig, Theme};
|
||||
use url::Url;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
path::PathBuf,
|
||||
sync::{mpsc::Sender, Arc, Mutex},
|
||||
sync::mpsc::Sender,
|
||||
};
|
||||
|
||||
type UriSchemeProtocol =
|
||||
@ -82,25 +83,6 @@ pub enum FileDropEvent {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// A menu event.
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MenuEvent {
|
||||
pub menu_item_id: u16,
|
||||
}
|
||||
|
||||
fn get_menu_ids(map: &mut HashMap<MenuHash, MenuId>, menu: &Menu) {
|
||||
for item in &menu.items {
|
||||
match item {
|
||||
MenuEntry::CustomItem(c) => {
|
||||
map.insert(c.id, c.id_str.clone());
|
||||
}
|
||||
MenuEntry::Submenu(s) => get_menu_ids(map, &s.inner),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the appearance of the mouse cursor.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
@ -209,10 +191,10 @@ impl<'de> Deserialize<'de> for CursorIcon {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub struct CreationContext<'a> {
|
||||
pub env: jni::JNIEnv<'a>,
|
||||
pub activity: jni::objects::JObject<'a>,
|
||||
pub webview: jni::objects::JObject<'a>,
|
||||
pub struct CreationContext<'a, 'b> {
|
||||
pub env: &'a mut jni::JNIEnv<'b>,
|
||||
pub activity: &'a jni::objects::JObject<'b>,
|
||||
pub webview: &'a jni::objects::JObject<'b>,
|
||||
}
|
||||
|
||||
/// A webview window that has yet to be built.
|
||||
@ -231,9 +213,6 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
|
||||
/// How to handle IPC calls on the webview window.
|
||||
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
|
||||
|
||||
/// Maps runtime id to a string menu id.
|
||||
pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
|
||||
|
||||
/// A handler to decide if incoming url is allowed to navigate.
|
||||
pub navigation_handler: Option<Box<NavigationHandler>>,
|
||||
|
||||
@ -243,7 +222,7 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub on_webview_created:
|
||||
Option<Box<dyn Fn(CreationContext<'_>) -> Result<(), jni::errors::Error> + Send>>,
|
||||
Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
|
||||
|
||||
pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
|
||||
}
|
||||
@ -268,10 +247,6 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
|
||||
webview_attributes: WebviewAttributes,
|
||||
label: impl Into<String>,
|
||||
) -> crate::Result<Self> {
|
||||
let mut menu_ids = HashMap::new();
|
||||
if let Some(menu) = window_builder.get_menu() {
|
||||
get_menu_ids(&mut menu_ids, menu);
|
||||
}
|
||||
let label = label.into();
|
||||
if !is_label_valid(&label) {
|
||||
Err(crate::Error::InvalidWindowLabel)
|
||||
@ -282,7 +257,6 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
|
||||
uri_scheme_protocols: Default::default(),
|
||||
label,
|
||||
ipc_handler: None,
|
||||
menu_ids: Arc::new(Mutex::new(menu_ids)),
|
||||
navigation_handler: Default::default(),
|
||||
url: "tauri://localhost".to_string(),
|
||||
#[cfg(target_os = "android")]
|
||||
@ -300,10 +274,7 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
|
||||
) -> crate::Result<Self> {
|
||||
let window_builder =
|
||||
<<R::Dispatcher as Dispatch<T>>::WindowBuilder>::with_config(window_config);
|
||||
let mut menu_ids = HashMap::new();
|
||||
if let Some(menu) = window_builder.get_menu() {
|
||||
get_menu_ids(&mut menu_ids, menu);
|
||||
}
|
||||
|
||||
let label = label.into();
|
||||
if !is_label_valid(&label) {
|
||||
Err(crate::Error::InvalidWindowLabel)
|
||||
@ -314,7 +285,6 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
|
||||
uri_scheme_protocols: Default::default(),
|
||||
label,
|
||||
ipc_handler: None,
|
||||
menu_ids: Arc::new(Mutex::new(menu_ids)),
|
||||
navigation_handler: Default::default(),
|
||||
url: "tauri://localhost".to_string(),
|
||||
#[cfg(target_os = "android")]
|
||||
@ -324,15 +294,6 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn set_menu(mut self, menu: Menu) -> Self {
|
||||
let mut menu_ids = HashMap::new();
|
||||
get_menu_ids(&mut menu_ids, &menu);
|
||||
*self.menu_ids.lock().unwrap() = menu_ids;
|
||||
self.window_builder = self.window_builder.menu(menu);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn register_uri_scheme_protocol<
|
||||
N: Into<String>,
|
||||
H: Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + Send + Sync + 'static,
|
||||
@ -349,7 +310,7 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn on_webview_created<
|
||||
F: Fn(CreationContext<'_>) -> Result<(), jni::errors::Error> + Send + 'static,
|
||||
F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
|
||||
>(
|
||||
mut self,
|
||||
f: F,
|
||||
@ -367,9 +328,6 @@ pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
|
||||
|
||||
/// The [`Dispatch`](crate::Dispatch) associated with the window.
|
||||
pub dispatcher: R::Dispatcher,
|
||||
|
||||
/// Maps runtime id to a string menu id.
|
||||
pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
|
||||
}
|
||||
|
||||
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
|
||||
@ -377,7 +335,6 @@ impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
|
||||
Self {
|
||||
label: self.label.clone(),
|
||||
dispatcher: self.dispatcher.clone(),
|
||||
menu_ids: self.menu_ids.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -396,3 +353,28 @@ impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {
|
||||
self.label.eq(&other.label)
|
||||
}
|
||||
}
|
||||
|
||||
/// A raw window type that contains fields to access
|
||||
/// the HWND on Windows, gtk::ApplicationWindow on Linux and
|
||||
/// NSView on macOS.
|
||||
pub struct RawWindow<'a> {
|
||||
#[cfg(windows)]
|
||||
pub hwnd: isize,
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
pub gtk_window: &'a gtk::ApplicationWindow,
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
pub default_vbox: Option<&'a gtk::Box>,
|
||||
pub _marker: &'a PhantomData<()>,
|
||||
}
|
||||
|
@ -1416,9 +1416,9 @@ pub struct TauriConfig {
|
||||
/// Security configuration.
|
||||
#[serde(default)]
|
||||
pub security: SecurityConfig,
|
||||
/// Configuration for app system tray.
|
||||
#[serde(alias = "system-tray")]
|
||||
pub system_tray: Option<SystemTrayConfig>,
|
||||
/// Configuration for app tray icon.
|
||||
#[serde(alias = "tray-icon")]
|
||||
pub tray_icon: Option<TrayIconConfig>,
|
||||
/// MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.
|
||||
#[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
|
||||
pub macos_private_api: bool,
|
||||
@ -1428,7 +1428,7 @@ impl TauriConfig {
|
||||
/// Returns all Cargo features.
|
||||
pub fn all_features() -> Vec<&'static str> {
|
||||
vec![
|
||||
"system-tray",
|
||||
"tray-icon",
|
||||
"macos-private-api",
|
||||
"isolation",
|
||||
"protocol-asset",
|
||||
@ -1438,8 +1438,8 @@ impl TauriConfig {
|
||||
/// Returns the enabled Cargo features.
|
||||
pub fn features(&self) -> Vec<&str> {
|
||||
let mut features = Vec::new();
|
||||
if self.system_tray.is_some() {
|
||||
features.push("system-tray");
|
||||
if self.tray_icon.is_some() {
|
||||
features.push("tray-icon");
|
||||
}
|
||||
if self.macos_private_api {
|
||||
features.push("macos-private-api");
|
||||
@ -1550,15 +1550,15 @@ pub struct UpdaterWindowsConfig {
|
||||
pub install_mode: WindowsUpdateInstallMode,
|
||||
}
|
||||
|
||||
/// Configuration for application system tray icon.
|
||||
/// Configuration for application tray icon.
|
||||
///
|
||||
/// See more: https://tauri.app/v1/api/config#systemtrayconfig
|
||||
/// See more: https://tauri.app/v1/api/config#trayiconconfig
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct SystemTrayConfig {
|
||||
/// Path to the default icon to use on the system tray.
|
||||
pub struct TrayIconConfig {
|
||||
/// Path to the default icon to use for the tray icon.
|
||||
#[serde(alias = "icon-path")]
|
||||
pub icon_path: PathBuf,
|
||||
/// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
|
||||
@ -1569,6 +1569,8 @@ pub struct SystemTrayConfig {
|
||||
pub menu_on_left_click: bool,
|
||||
/// Title for MacOS tray
|
||||
pub title: Option<String>,
|
||||
/// Tray icon tooltip on Windows and macOS
|
||||
pub tooltip: Option<String>,
|
||||
}
|
||||
|
||||
/// General configuration for the iOS target.
|
||||
@ -1838,7 +1840,7 @@ impl PackageConfig {
|
||||
|
||||
/// The Tauri configuration object.
|
||||
/// It is read from a file where you can define your frontend assets,
|
||||
/// configure the bundler and define a system tray.
|
||||
/// configure the bundler and define a tray icon.
|
||||
///
|
||||
/// The configuration file is generated by the
|
||||
/// [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in
|
||||
@ -2566,19 +2568,21 @@ mod build {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for SystemTrayConfig {
|
||||
impl ToTokens for TrayIconConfig {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let icon_as_template = self.icon_as_template;
|
||||
let menu_on_left_click = self.menu_on_left_click;
|
||||
let icon_path = path_buf_lit(&self.icon_path);
|
||||
let title = opt_str_lit(self.title.as_ref());
|
||||
let tooltip = opt_str_lit(self.tooltip.as_ref());
|
||||
literal_struct!(
|
||||
tokens,
|
||||
SystemTrayConfig,
|
||||
TrayIconConfig,
|
||||
icon_path,
|
||||
icon_as_template,
|
||||
menu_on_left_click,
|
||||
title
|
||||
title,
|
||||
tooltip
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2615,7 +2619,7 @@ mod build {
|
||||
let windows = vec_lit(&self.windows, identity);
|
||||
let bundle = &self.bundle;
|
||||
let security = &self.security;
|
||||
let system_tray = opt_lit(self.system_tray.as_ref());
|
||||
let tray_icon = opt_lit(self.tray_icon.as_ref());
|
||||
let macos_private_api = self.macos_private_api;
|
||||
|
||||
literal_struct!(
|
||||
@ -2625,7 +2629,7 @@ mod build {
|
||||
windows,
|
||||
bundle,
|
||||
security,
|
||||
system_tray,
|
||||
tray_icon,
|
||||
macos_private_api
|
||||
);
|
||||
}
|
||||
@ -2718,7 +2722,7 @@ mod test {
|
||||
dangerous_remote_domain_ipc_access: Vec::new(),
|
||||
asset_protocol: AssetProtocolConfig::default(),
|
||||
},
|
||||
system_tray: None,
|
||||
tray_icon: None,
|
||||
macos_private_api: false,
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,6 @@ no-default-features = true
|
||||
features = [
|
||||
"wry",
|
||||
"custom-protocol",
|
||||
"system-tray",
|
||||
"devtools",
|
||||
"icon-png",
|
||||
"protocol-asset",
|
||||
@ -68,6 +67,11 @@ infer = { version = "0.9", optional = true }
|
||||
png = { version = "0.17", optional = true }
|
||||
ico = { version = "0.2.0", optional = true }
|
||||
|
||||
|
||||
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies]
|
||||
muda = { version = "0.8", default-features = false }
|
||||
tray-icon = { version = "0.8", default-features = false, optional = true }
|
||||
|
||||
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||
gtk = { version = "0.16", features = [ "v3_24" ] }
|
||||
glib = "0.16"
|
||||
@ -81,16 +85,16 @@ objc = "0.2"
|
||||
[target."cfg(windows)".dependencies]
|
||||
webview2-com = "0.25"
|
||||
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
version = "0.48"
|
||||
features = [ "Win32_Foundation" ]
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
version = "0.48"
|
||||
features = [ "Win32_Foundation" ]
|
||||
|
||||
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
|
||||
log = "0.4"
|
||||
heck = "0.4"
|
||||
|
||||
[target."cfg(target_os = \"android\")".dependencies]
|
||||
jni = "0.20"
|
||||
jni = "0.21"
|
||||
|
||||
[target."cfg(target_os = \"ios\")".dependencies]
|
||||
libc = "0.2"
|
||||
@ -114,18 +118,19 @@ tokio = { version = "1", features = [ "full" ] }
|
||||
cargo_toml = "0.15"
|
||||
|
||||
[features]
|
||||
default = [ "wry", "compression", "objc-exception" ]
|
||||
default = [ "wry", "compression", "objc-exception", "tray-icon?/common-controls-v6", "muda/common-controls-v6" ]
|
||||
tray-icon = [ "dep:tray-icon" ]
|
||||
test = [ ]
|
||||
compression = [ "tauri-macros/compression", "tauri-utils/compression" ]
|
||||
wry = [ "tauri-runtime-wry" ]
|
||||
objc-exception = [ "tauri-runtime-wry/objc-exception" ]
|
||||
linux-ipc-protocol = [ "tauri-runtime-wry/linux-protocol-body", "webkit2gtk/v2_40" ]
|
||||
linux-libxdo = [ "tray-icon/libxdo", "muda/libxdo" ]
|
||||
isolation = [ "tauri-utils/isolation", "tauri-macros/isolation" ]
|
||||
custom-protocol = [ "tauri-macros/custom-protocol" ]
|
||||
native-tls = [ "reqwest/native-tls" ]
|
||||
native-tls-vendored = [ "reqwest/native-tls-vendored" ]
|
||||
rustls-tls = [ "reqwest/rustls-tls" ]
|
||||
system-tray = [ "tauri-runtime/system-tray", "tauri-runtime-wry/system-tray" ]
|
||||
devtools = [ "tauri-runtime/devtools", "tauri-runtime-wry/devtools" ]
|
||||
dox = [ "tauri-runtime-wry/dox" ]
|
||||
process-relaunch-dangerous-allow-symlink-macos = [ "tauri-utils/process-relaunch-dangerous-allow-symlink-macos" ]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,704 +0,0 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pub use crate::{
|
||||
runtime::{
|
||||
menu::{
|
||||
MenuHash, MenuId, MenuIdRef, MenuUpdate, SystemTrayMenu, SystemTrayMenuEntry, TrayHandle,
|
||||
},
|
||||
window::dpi::{PhysicalPosition, PhysicalSize},
|
||||
RuntimeHandle, SystemTrayEvent as RuntimeSystemTrayEvent,
|
||||
},
|
||||
Icon, Runtime,
|
||||
};
|
||||
use crate::{sealed::RuntimeOrDispatch, Manager};
|
||||
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use tauri_macros::default_runtime;
|
||||
use tauri_runtime::TrayId;
|
||||
use tauri_utils::debug_eprintln;
|
||||
|
||||
use std::{
|
||||
collections::{hash_map::DefaultHasher, HashMap},
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
type TrayEventHandler = dyn Fn(SystemTrayEvent) + Send + Sync + 'static;
|
||||
|
||||
pub(crate) fn get_menu_ids(map: &mut HashMap<MenuHash, MenuId>, menu: &SystemTrayMenu) {
|
||||
for item in &menu.items {
|
||||
match item {
|
||||
SystemTrayMenuEntry::CustomItem(c) => {
|
||||
map.insert(c.id, c.id_str.clone());
|
||||
}
|
||||
SystemTrayMenuEntry::Submenu(s) => get_menu_ids(map, &s.inner),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a System Tray instance.
|
||||
#[derive(Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct SystemTray {
|
||||
/// The tray identifier. Defaults to a random string.
|
||||
pub id: String,
|
||||
/// The tray icon.
|
||||
pub icon: Option<tauri_runtime::Icon>,
|
||||
/// The tray menu.
|
||||
pub menu: Option<SystemTrayMenu>,
|
||||
/// Whether the icon is a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) icon or not.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub icon_as_template: bool,
|
||||
/// Whether the menu should appear when the tray receives a left click. Defaults to `true`
|
||||
#[cfg(target_os = "macos")]
|
||||
pub menu_on_left_click: bool,
|
||||
on_event: Option<Arc<TrayEventHandler>>,
|
||||
// TODO: icon_as_template and menu_on_left_click should be an Option instead :(
|
||||
#[cfg(target_os = "macos")]
|
||||
menu_on_left_click_set: bool,
|
||||
#[cfg(target_os = "macos")]
|
||||
icon_as_template_set: bool,
|
||||
#[cfg(target_os = "macos")]
|
||||
title: Option<String>,
|
||||
tooltip: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for SystemTray {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut d = f.debug_struct("SystemTray");
|
||||
d.field("id", &self.id)
|
||||
.field("icon", &self.icon)
|
||||
.field("menu", &self.menu);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
d.field("icon_as_template", &self.icon_as_template)
|
||||
.field("menu_on_left_click", &self.menu_on_left_click);
|
||||
}
|
||||
d.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SystemTray {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: Alphanumeric.sample_string(&mut rand::thread_rng(), 16),
|
||||
icon: None,
|
||||
menu: None,
|
||||
on_event: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
icon_as_template: false,
|
||||
#[cfg(target_os = "macos")]
|
||||
menu_on_left_click: false,
|
||||
#[cfg(target_os = "macos")]
|
||||
icon_as_template_set: false,
|
||||
#[cfg(target_os = "macos")]
|
||||
menu_on_left_click_set: false,
|
||||
#[cfg(target_os = "macos")]
|
||||
title: None,
|
||||
tooltip: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemTray {
|
||||
/// Creates a new system tray that only renders an icon.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::SystemTray;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let tray_handle = SystemTray::new().build(app)?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub(crate) fn menu(&self) -> Option<&SystemTrayMenu> {
|
||||
self.menu.as_ref()
|
||||
}
|
||||
|
||||
/// Sets the tray identifier, used to retrieve its handle and to identify a tray event source.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::SystemTray;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let tray_handle = SystemTray::new()
|
||||
/// .with_id("tray-id")
|
||||
/// .build(app)?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn with_id<I: Into<String>>(mut self, id: I) -> Self {
|
||||
self.id = id.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the tray [`Icon`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::{Icon, SystemTray};
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let tray_handle = SystemTray::new()
|
||||
/// // dummy and invalid Rgba icon; see the Icon documentation for more information
|
||||
/// .with_icon(Icon::Rgba { rgba: Vec::new(), width: 0, height: 0 })
|
||||
/// .build(app)?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn with_icon<I: TryInto<tauri_runtime::Icon>>(mut self, icon: I) -> Self
|
||||
where
|
||||
I::Error: std::error::Error,
|
||||
{
|
||||
match icon.try_into() {
|
||||
Ok(icon) => {
|
||||
self.icon.replace(icon);
|
||||
}
|
||||
Err(e) => {
|
||||
debug_eprintln!("Failed to load tray icon: {}", e);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc).
|
||||
///
|
||||
/// Images you mark as template images should consist of only black and clear colors.
|
||||
/// You can use the alpha channel in the image to adjust the opacity of black content.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::SystemTray;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let mut tray_builder = SystemTray::new();
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// {
|
||||
/// tray_builder = tray_builder.with_icon_as_template(true);
|
||||
/// }
|
||||
/// let tray_handle = tray_builder.build(app)?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
|
||||
self.icon_as_template_set = true;
|
||||
self.icon_as_template = is_template;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether the menu should appear when the tray receives a left click. Defaults to `true`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::SystemTray;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let mut tray_builder = SystemTray::new();
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// {
|
||||
/// tray_builder = tray_builder.with_menu_on_left_click(false);
|
||||
/// }
|
||||
/// let tray_handle = tray_builder.build(app)?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
pub fn with_menu_on_left_click(mut self, menu_on_left_click: bool) -> Self {
|
||||
self.menu_on_left_click_set = true;
|
||||
self.menu_on_left_click = menu_on_left_click;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the menu title`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::SystemTray;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let mut tray_builder = SystemTray::new();
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// {
|
||||
/// tray_builder = tray_builder.with_title("My App");
|
||||
/// }
|
||||
/// let tray_handle = tray_builder.build(app)?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
pub fn with_title(mut self, title: &str) -> Self {
|
||||
self.title = Some(title.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the tray icon tooltip.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::SystemTray;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let tray_handle = SystemTray::new().with_tooltip("My App").build(app)?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn with_tooltip(mut self, tooltip: &str) -> Self {
|
||||
self.tooltip = Some(tooltip.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the event listener for this system tray.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::{Icon, Manager, SystemTray, SystemTrayEvent};
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let handle = app.handle();
|
||||
/// let id = "tray-id";
|
||||
/// SystemTray::new()
|
||||
/// .with_id(id)
|
||||
/// .on_event(move |event| {
|
||||
/// let tray_handle = handle.tray_handle_by_id(id).unwrap();
|
||||
/// match event {
|
||||
/// // show window with id "main" when the tray is left clicked
|
||||
/// SystemTrayEvent::LeftClick { .. } => {
|
||||
/// let window = handle.get_window("main").unwrap();
|
||||
/// window.show().unwrap();
|
||||
/// window.set_focus().unwrap();
|
||||
/// }
|
||||
/// _ => {}
|
||||
/// }
|
||||
/// })
|
||||
/// .build(app)?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn on_event<F: Fn(SystemTrayEvent) + Send + Sync + 'static>(mut self, f: F) -> Self {
|
||||
self.on_event.replace(Arc::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the menu to show when the system tray is right clicked.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu};
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let tray_handle = SystemTray::new()
|
||||
/// .with_menu(
|
||||
/// SystemTrayMenu::new()
|
||||
/// .add_item(CustomMenuItem::new("quit", "Quit"))
|
||||
/// .add_item(CustomMenuItem::new("open", "Open"))
|
||||
/// )
|
||||
/// .build(app)?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn with_menu(mut self, menu: SystemTrayMenu) -> Self {
|
||||
self.menu.replace(menu);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds and shows the system tray.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu};
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let tray_handle = SystemTray::new()
|
||||
/// .with_menu(
|
||||
/// SystemTrayMenu::new()
|
||||
/// .add_item(CustomMenuItem::new("quit", "Quit"))
|
||||
/// .add_item(CustomMenuItem::new("open", "Open"))
|
||||
/// )
|
||||
/// .build(app)?;
|
||||
///
|
||||
/// tray_handle.get_item("quit").set_enabled(false);
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn build<R: Runtime, M: Manager<R>>(
|
||||
mut self,
|
||||
manager: &M,
|
||||
) -> crate::Result<SystemTrayHandle<R>> {
|
||||
let mut ids = HashMap::new();
|
||||
if let Some(menu) = self.menu() {
|
||||
get_menu_ids(&mut ids, menu);
|
||||
}
|
||||
let ids = Arc::new(Mutex::new(ids));
|
||||
|
||||
if self.icon.is_none() {
|
||||
if let Some(tray_icon) = &manager.manager().inner.tray_icon {
|
||||
self = self.with_icon(tray_icon.clone());
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if !self.icon_as_template_set {
|
||||
self.icon_as_template = manager
|
||||
.config()
|
||||
.tauri
|
||||
.system_tray
|
||||
.as_ref()
|
||||
.map_or(false, |t| t.icon_as_template);
|
||||
}
|
||||
if !self.menu_on_left_click_set {
|
||||
self.menu_on_left_click = manager
|
||||
.config()
|
||||
.tauri
|
||||
.system_tray
|
||||
.as_ref()
|
||||
.map_or(false, |t| t.menu_on_left_click);
|
||||
}
|
||||
if self.title.is_none() {
|
||||
self.title = manager
|
||||
.config()
|
||||
.tauri
|
||||
.system_tray
|
||||
.as_ref()
|
||||
.and_then(|t| t.title.clone())
|
||||
}
|
||||
}
|
||||
|
||||
let tray_id = self.id.clone();
|
||||
|
||||
let mut runtime_tray = tauri_runtime::SystemTray::new();
|
||||
runtime_tray = runtime_tray.with_id(hash(&self.id));
|
||||
if let Some(i) = self.icon {
|
||||
runtime_tray = runtime_tray.with_icon(i);
|
||||
}
|
||||
|
||||
if let Some(menu) = self.menu {
|
||||
runtime_tray = runtime_tray.with_menu(menu);
|
||||
}
|
||||
|
||||
if let Some(on_event) = self.on_event {
|
||||
let ids_ = ids.clone();
|
||||
let tray_id_ = tray_id.clone();
|
||||
runtime_tray = runtime_tray.on_event(move |event| {
|
||||
on_event(SystemTrayEvent::from_runtime_event(
|
||||
event,
|
||||
tray_id_.clone(),
|
||||
&ids_,
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
runtime_tray = runtime_tray.with_icon_as_template(self.icon_as_template);
|
||||
runtime_tray = runtime_tray.with_menu_on_left_click(self.menu_on_left_click);
|
||||
if let Some(title) = self.title {
|
||||
runtime_tray = runtime_tray.with_title(&title);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tooltip) = self.tooltip {
|
||||
runtime_tray = runtime_tray.with_tooltip(&tooltip);
|
||||
}
|
||||
|
||||
let id = runtime_tray.id;
|
||||
let tray_handler = match manager.runtime() {
|
||||
RuntimeOrDispatch::Runtime(r) => r.system_tray(runtime_tray),
|
||||
RuntimeOrDispatch::RuntimeHandle(h) => h.system_tray(runtime_tray),
|
||||
RuntimeOrDispatch::Dispatch(_) => manager
|
||||
.app_handle()
|
||||
.runtime_handle
|
||||
.system_tray(runtime_tray),
|
||||
}?;
|
||||
|
||||
let tray_handle = SystemTrayHandle {
|
||||
id,
|
||||
ids,
|
||||
inner: tray_handler,
|
||||
};
|
||||
manager.manager().attach_tray(tray_id, tray_handle.clone());
|
||||
|
||||
Ok(tray_handle)
|
||||
}
|
||||
}
|
||||
|
||||
fn hash(id: &str) -> MenuHash {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
id.hash(&mut hasher);
|
||||
hasher.finish() as MenuHash
|
||||
}
|
||||
|
||||
/// System tray event.
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
|
||||
#[non_exhaustive]
|
||||
pub enum SystemTrayEvent {
|
||||
/// Tray context menu item was clicked.
|
||||
#[non_exhaustive]
|
||||
MenuItemClick {
|
||||
/// The tray id.
|
||||
tray_id: String,
|
||||
/// The id of the menu item.
|
||||
id: MenuId,
|
||||
},
|
||||
/// Tray icon received a left click.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** Unsupported
|
||||
#[non_exhaustive]
|
||||
LeftClick {
|
||||
/// The tray id.
|
||||
tray_id: String,
|
||||
/// The position of the tray icon.
|
||||
position: PhysicalPosition<f64>,
|
||||
/// The size of the tray icon.
|
||||
size: PhysicalSize<f64>,
|
||||
},
|
||||
/// Tray icon received a right click.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** Unsupported
|
||||
/// - **macOS:** `Ctrl` + `Left click` fire this event.
|
||||
#[non_exhaustive]
|
||||
RightClick {
|
||||
/// The tray id.
|
||||
tray_id: String,
|
||||
/// The position of the tray icon.
|
||||
position: PhysicalPosition<f64>,
|
||||
/// The size of the tray icon.
|
||||
size: PhysicalSize<f64>,
|
||||
},
|
||||
/// Fired when a menu item receive a `Double click`
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS / Linux:** Unsupported
|
||||
///
|
||||
#[non_exhaustive]
|
||||
DoubleClick {
|
||||
/// The tray id.
|
||||
tray_id: String,
|
||||
/// The position of the tray icon.
|
||||
position: PhysicalPosition<f64>,
|
||||
/// The size of the tray icon.
|
||||
size: PhysicalSize<f64>,
|
||||
},
|
||||
}
|
||||
|
||||
impl SystemTrayEvent {
|
||||
pub(crate) fn from_runtime_event(
|
||||
event: &RuntimeSystemTrayEvent,
|
||||
tray_id: String,
|
||||
menu_ids: &Arc<Mutex<HashMap<u16, String>>>,
|
||||
) -> Self {
|
||||
match event {
|
||||
RuntimeSystemTrayEvent::MenuItemClick(id) => Self::MenuItemClick {
|
||||
tray_id,
|
||||
id: menu_ids.lock().unwrap().get(id).unwrap().clone(),
|
||||
},
|
||||
RuntimeSystemTrayEvent::LeftClick { position, size } => Self::LeftClick {
|
||||
tray_id,
|
||||
position: *position,
|
||||
size: *size,
|
||||
},
|
||||
RuntimeSystemTrayEvent::RightClick { position, size } => Self::RightClick {
|
||||
tray_id,
|
||||
position: *position,
|
||||
size: *size,
|
||||
},
|
||||
RuntimeSystemTrayEvent::DoubleClick { position, size } => Self::DoubleClick {
|
||||
tray_id,
|
||||
position: *position,
|
||||
size: *size,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a system tray. Allows updating the context menu items.
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
#[derive(Debug)]
|
||||
pub struct SystemTrayHandle<R: Runtime> {
|
||||
pub(crate) id: TrayId,
|
||||
pub(crate) ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
|
||||
pub(crate) inner: R::TrayHandler,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for SystemTrayHandle<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
ids: self.ids.clone(),
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a system tray menu item.
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
#[derive(Debug)]
|
||||
pub struct SystemTrayMenuItemHandle<R: Runtime> {
|
||||
id: MenuHash,
|
||||
tray_handler: R::TrayHandler,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for SystemTrayMenuItemHandle<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
tray_handler: self.tray_handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> SystemTrayHandle<R> {
|
||||
/// Gets a handle to the menu item that has the specified `id`.
|
||||
pub fn get_item(&self, id: MenuIdRef<'_>) -> SystemTrayMenuItemHandle<R> {
|
||||
let ids = self.ids.lock().unwrap();
|
||||
let iter = ids.iter();
|
||||
for (raw, item_id) in iter {
|
||||
if item_id == id {
|
||||
return SystemTrayMenuItemHandle {
|
||||
id: *raw,
|
||||
tray_handler: self.inner.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
panic!("item id not found")
|
||||
}
|
||||
|
||||
/// Attempts to get a handle to the menu item that has the specified `id`, return an error if `id` is not found.
|
||||
pub fn try_get_item(&self, id: MenuIdRef<'_>) -> Option<SystemTrayMenuItemHandle<R>> {
|
||||
self
|
||||
.ids
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|i| i.1 == id)
|
||||
.map(|i| SystemTrayMenuItemHandle {
|
||||
id: *i.0,
|
||||
tray_handler: self.inner.clone(),
|
||||
})
|
||||
}
|
||||
/// Updates the tray icon.
|
||||
pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
|
||||
self.inner.set_icon(icon.try_into()?).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Updates the tray menu.
|
||||
pub fn set_menu(&self, menu: SystemTrayMenu) -> crate::Result<()> {
|
||||
let mut ids = HashMap::new();
|
||||
get_menu_ids(&mut ids, &menu);
|
||||
self.inner.set_menu(menu)?;
|
||||
*self.ids.lock().unwrap() = ids;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()> {
|
||||
self
|
||||
.inner
|
||||
.set_icon_as_template(is_template)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Adds the title to the tray menu
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_title(&self, title: &str) -> crate::Result<()> {
|
||||
self.inner.set_title(title).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Set the tooltip for this tray icon.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported
|
||||
pub fn set_tooltip(&self, tooltip: &str) -> crate::Result<()> {
|
||||
self.inner.set_tooltip(tooltip).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Destroys this system tray.
|
||||
pub fn destroy(&self) -> crate::Result<()> {
|
||||
self.inner.destroy().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> SystemTrayMenuItemHandle<R> {
|
||||
/// Modifies the enabled state of the menu item.
|
||||
pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
|
||||
self
|
||||
.tray_handler
|
||||
.update_item(self.id, MenuUpdate::SetEnabled(enabled))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Modifies the title (label) of the menu item.
|
||||
pub fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()> {
|
||||
self
|
||||
.tray_handler
|
||||
.update_item(self.id, MenuUpdate::SetTitle(title.into()))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Modifies the selected state of the menu item.
|
||||
pub fn set_selected(&self, selected: bool) -> crate::Result<()> {
|
||||
self
|
||||
.tray_handler
|
||||
.update_item(self.id, MenuUpdate::SetSelected(selected))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Sets the native image for this item.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
|
||||
pub fn set_native_image(&self, image: crate::NativeImage) -> crate::Result<()> {
|
||||
self
|
||||
.tray_handler
|
||||
.update_item(self.id, MenuUpdate::SetNativeImage(image))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
@ -89,4 +89,25 @@ pub enum Error {
|
||||
#[cfg(target_os = "android")]
|
||||
#[error("jni error: {0}")]
|
||||
Jni(#[from] jni::errors::Error),
|
||||
/// Failed to receive message .
|
||||
#[error("failed to receive message")]
|
||||
FailedToReceiveMessage,
|
||||
/// Menu error.
|
||||
#[error("menu error: {0}")]
|
||||
#[cfg(desktop)]
|
||||
Menu(#[from] muda::Error),
|
||||
/// Bad menu icon error.
|
||||
#[error(transparent)]
|
||||
#[cfg(desktop)]
|
||||
BadMenuIcon(#[from] muda::BadIcon),
|
||||
/// Tray icon error.
|
||||
#[error("tray icon error: {0}")]
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))]
|
||||
Tray(#[from] tray_icon::Error),
|
||||
/// Bad tray icon error.
|
||||
#[error(transparent)]
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))]
|
||||
BadTrayIcon(#[from] tray_icon::BadIcon),
|
||||
}
|
||||
|
@ -5,18 +5,18 @@
|
||||
use crate::Runtime;
|
||||
use jni::{
|
||||
errors::Error as JniError,
|
||||
objects::{JObject, JValue},
|
||||
objects::{JObject, JValueOwned},
|
||||
JNIEnv,
|
||||
};
|
||||
use serde_json::Value as JsonValue;
|
||||
use tauri_runtime::RuntimeHandle;
|
||||
|
||||
fn json_to_java<'a, R: Runtime>(
|
||||
env: JNIEnv<'a>,
|
||||
activity: JObject<'a>,
|
||||
env: &mut JNIEnv<'a>,
|
||||
activity: &JObject<'_>,
|
||||
runtime_handle: &R::Handle,
|
||||
json: &JsonValue,
|
||||
) -> Result<(&'static str, JValue<'a>), JniError> {
|
||||
) -> Result<(&'static str, JValueOwned<'a>), JniError> {
|
||||
let (class, v) = match json {
|
||||
JsonValue::Null => ("Ljava/lang/Object;", JObject::null().into()),
|
||||
JsonValue::Bool(val) => ("Z", (*val).into()),
|
||||
@ -40,27 +40,30 @@ fn json_to_java<'a, R: Runtime>(
|
||||
for v in val {
|
||||
let (signature, val) = json_to_java::<R>(env, activity, runtime_handle, v)?;
|
||||
env.call_method(
|
||||
data,
|
||||
&data,
|
||||
"put",
|
||||
format!("({signature})Lorg/json/JSONArray;"),
|
||||
&[val],
|
||||
&[val.borrow()],
|
||||
)?;
|
||||
}
|
||||
|
||||
("Ljava/lang/Object;", data.into())
|
||||
}
|
||||
JsonValue::Object(val) => {
|
||||
let js_object_class =
|
||||
runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?;
|
||||
let data = env.new_object(js_object_class, "()V", &[])?;
|
||||
let data = {
|
||||
let js_object_class =
|
||||
runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?;
|
||||
env.new_object(js_object_class, "()V", &[])?
|
||||
};
|
||||
|
||||
for (key, value) in val {
|
||||
let (signature, val) = json_to_java::<R>(env, activity, runtime_handle, value)?;
|
||||
let key = env.new_string(key)?;
|
||||
env.call_method(
|
||||
data,
|
||||
&data,
|
||||
"put",
|
||||
format!("(Ljava/lang/String;{signature})Lapp/tauri/plugin/JSObject;"),
|
||||
&[env.new_string(key)?.into(), val],
|
||||
&[(&key).into(), val.borrow()],
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -71,17 +74,25 @@ fn json_to_java<'a, R: Runtime>(
|
||||
}
|
||||
|
||||
pub fn to_jsobject<'a, R: Runtime>(
|
||||
env: JNIEnv<'a>,
|
||||
activity: JObject<'a>,
|
||||
env: &mut JNIEnv<'a>,
|
||||
activity: &JObject<'_>,
|
||||
runtime_handle: &R::Handle,
|
||||
json: &JsonValue,
|
||||
) -> Result<JValue<'a>, JniError> {
|
||||
) -> Result<JValueOwned<'a>, JniError> {
|
||||
if let JsonValue::Object(_) = json {
|
||||
json_to_java::<R>(env, activity, runtime_handle, json).map(|(_class, data)| data)
|
||||
} else {
|
||||
// currently the Kotlin lib cannot handle nulls or raw values, it must be an object
|
||||
let js_object_class = runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?;
|
||||
let data = env.new_object(js_object_class, "()V", &[])?;
|
||||
Ok(data.into())
|
||||
Ok(empty_object::<R>(env, activity, runtime_handle)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_object<'a, R: Runtime>(
|
||||
env: &mut JNIEnv<'a>,
|
||||
activity: &JObject<'_>,
|
||||
runtime_handle: &R::Handle,
|
||||
) -> Result<JObject<'a>, JniError> {
|
||||
// currently the Kotlin lib cannot handle nulls or raw values, it must be an object
|
||||
let js_object_class = runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?;
|
||||
let data = env.new_object(js_object_class, "()V", &[])?;
|
||||
Ok(data)
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
//! - **dox**: Internal feature to generate Rust documentation without linking on Linux.
|
||||
//! - **objc-exception**: Wrap each msg_send! in a @try/@catch and panics if an exception is caught, preventing Objective-C from unwinding into Rust.
|
||||
//! - **linux-ipc-protocol**: Use custom protocol for faster IPC on Linux. Requires webkit2gtk v2.40 or above.
|
||||
//! - **linux-libxdo**: Enables linking to libxdo which enables Cut, Copy, Paste and SelectAll menu items to work on Linux.
|
||||
//! - **isolation**: Enables the isolation pattern. Enabled by default if the `tauri > pattern > use` config option is set to `isolation` on the `tauri.conf.json` file.
|
||||
//! - **custom-protocol**: Feature managed by the Tauri CLI. When enabled, Tauri assumes a production environment instead of a development one.
|
||||
//! - **devtools**: Enables the developer tools (Web inspector) and [`Window::open_devtools`]. Enabled by default on debug builds.
|
||||
@ -25,7 +26,7 @@
|
||||
//! - **native-tls-vendored**: Compile and statically link to a vendored copy of OpenSSL.
|
||||
//! - **rustls-tls**: Provides TLS support to connect over HTTPS using rustls.
|
||||
//! - **process-relaunch-dangerous-allow-symlink-macos**: Allows the [`process::current_binary`] function to allow symlinks on macOS (this is dangerous, see the Security section in the documentation website).
|
||||
//! - **system-tray**: Enables application system tray API. Enabled by default if the `systemTray` config is defined on the `tauri.conf.json` file.
|
||||
//! - **tray-icon**: Enables application tray icon APIs. Enabled by default if the `trayIcon` config is defined on the `tauri.conf.json` file.
|
||||
//! - **macos-private-api**: Enables features only available in **macOS**'s private APIs, currently the `transparent` window functionality and the `fullScreenEnabled` preference setting to `true`. Enabled by default if the `tauri > macosPrivateApi` config flag is set to `true` on the `tauri.conf.json` file.
|
||||
//! - **window-data-url**: Enables usage of data URLs on the webview.
|
||||
//! - **compression** *(enabled by default): Enables asset compression. You should only disable this if you want faster compile times in release builds - it produces larger binaries.
|
||||
@ -92,6 +93,8 @@ use tauri_runtime as runtime;
|
||||
mod ios;
|
||||
#[cfg(target_os = "android")]
|
||||
mod jni_helpers;
|
||||
#[cfg(desktop)]
|
||||
pub mod menu;
|
||||
/// Path APIs.
|
||||
pub mod path;
|
||||
pub mod process;
|
||||
@ -99,6 +102,9 @@ pub mod process;
|
||||
pub mod scope;
|
||||
mod state;
|
||||
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))]
|
||||
pub mod tray;
|
||||
pub use tauri_utils as utils;
|
||||
|
||||
/// A Tauri [`Runtime`] wrapper around wry.
|
||||
@ -130,14 +136,20 @@ macro_rules! android_binding {
|
||||
|
||||
// this function is a glue between PluginManager.kt > handlePluginResponse and Rust
|
||||
#[allow(non_snake_case)]
|
||||
pub fn handlePluginResponse(env: JNIEnv, _: JClass, id: i32, success: JString, error: JString) {
|
||||
::tauri::handle_android_plugin_response(env, id, success, error);
|
||||
pub fn handlePluginResponse(
|
||||
mut env: JNIEnv,
|
||||
_: JClass,
|
||||
id: i32,
|
||||
success: JString,
|
||||
error: JString,
|
||||
) {
|
||||
::tauri::handle_android_plugin_response(&mut env, id, success, error);
|
||||
}
|
||||
|
||||
// this function is a glue between PluginManager.kt > sendChannelData and Rust
|
||||
#[allow(non_snake_case)]
|
||||
pub fn sendChannelData(env: JNIEnv, _: JClass, id: i64, data: JString) {
|
||||
::tauri::send_channel_data(env, id, data);
|
||||
pub fn sendChannelData(mut env: JNIEnv, _: JClass, id: i64, data: JString) {
|
||||
::tauri::send_channel_data(&mut env, id, data);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -156,7 +168,11 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type SyncTask = Box<dyn FnOnce() + Send>;
|
||||
|
||||
use serde::Serialize;
|
||||
use std::{collections::HashMap, fmt, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Debug},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
// Export types likely to be used by the application.
|
||||
pub use runtime::http;
|
||||
@ -167,22 +183,12 @@ pub use tauri_runtime_wry::webview_version;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
|
||||
pub use runtime::{menu::NativeImage, ActivationPolicy};
|
||||
pub use runtime::ActivationPolicy;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use self::utils::TitleBarStyle;
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
|
||||
pub use {
|
||||
self::app::tray::{SystemTray, SystemTrayEvent, SystemTrayHandle, SystemTrayMenuItemHandle},
|
||||
self::runtime::menu::{SystemTrayMenu, SystemTrayMenuItem, SystemTraySubmenu},
|
||||
};
|
||||
pub use {
|
||||
self::app::WindowMenuEvent,
|
||||
self::event::{Event, EventHandler},
|
||||
self::runtime::menu::{AboutMetadata, CustomMenuItem, Menu, MenuEntry, MenuItem, Submenu},
|
||||
self::window::menu::MenuEvent,
|
||||
};
|
||||
|
||||
pub use self::event::{Event, EventHandler};
|
||||
pub use {
|
||||
self::app::{
|
||||
App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, RunEvent,
|
||||
@ -249,7 +255,15 @@ pub fn log_stdout() {
|
||||
|
||||
/// The user event type.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EventLoopMessage {}
|
||||
pub enum EventLoopMessage {
|
||||
/// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu.
|
||||
#[cfg(desktop)]
|
||||
MenuEvent(menu::MenuEvent),
|
||||
/// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu.
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))]
|
||||
TrayIconEvent(tray::TrayIconEvent),
|
||||
}
|
||||
|
||||
/// The webview runtime interface. A wrapper around [`runtime::Runtime`] with the proper user event type associated.
|
||||
pub trait Runtime: runtime::Runtime<EventLoopMessage> {}
|
||||
@ -388,8 +402,8 @@ pub struct Context<A: Assets> {
|
||||
pub(crate) assets: Arc<A>,
|
||||
pub(crate) default_window_icon: Option<Icon>,
|
||||
pub(crate) app_icon: Option<Vec<u8>>,
|
||||
#[cfg(desktop)]
|
||||
pub(crate) system_tray_icon: Option<Icon>,
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
pub(crate) tray_icon: Option<Icon>,
|
||||
pub(crate) package_info: PackageInfo,
|
||||
pub(crate) _info_plist: (),
|
||||
pub(crate) pattern: Pattern,
|
||||
@ -404,8 +418,8 @@ impl<A: Assets> fmt::Debug for Context<A> {
|
||||
.field("package_info", &self.package_info)
|
||||
.field("pattern", &self.pattern);
|
||||
|
||||
#[cfg(desktop)]
|
||||
d.field("system_tray_icon", &self.system_tray_icon);
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
d.field("tray_icon", &self.tray_icon);
|
||||
|
||||
d.finish()
|
||||
}
|
||||
@ -449,17 +463,19 @@ impl<A: Assets> Context<A> {
|
||||
}
|
||||
|
||||
/// The icon to use on the system tray UI.
|
||||
#[cfg(desktop)]
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))]
|
||||
#[inline(always)]
|
||||
pub fn system_tray_icon(&self) -> Option<&Icon> {
|
||||
self.system_tray_icon.as_ref()
|
||||
pub fn tray_icon(&self) -> Option<&Icon> {
|
||||
self.tray_icon.as_ref()
|
||||
}
|
||||
|
||||
/// A mutable reference to the icon to use on the system tray UI.
|
||||
#[cfg(desktop)]
|
||||
/// A mutable reference to the icon to use on the tray icon.
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))]
|
||||
#[inline(always)]
|
||||
pub fn system_tray_icon_mut(&mut self) -> &mut Option<Icon> {
|
||||
&mut self.system_tray_icon
|
||||
pub fn tray_icon_mut(&mut self) -> &mut Option<Icon> {
|
||||
&mut self.tray_icon
|
||||
}
|
||||
|
||||
/// Package information.
|
||||
@ -497,8 +513,8 @@ impl<A: Assets> Context<A> {
|
||||
assets,
|
||||
default_window_icon,
|
||||
app_icon,
|
||||
#[cfg(desktop)]
|
||||
system_tray_icon: None,
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
tray_icon: None,
|
||||
package_info,
|
||||
_info_plist: info_plist,
|
||||
pattern,
|
||||
@ -506,10 +522,11 @@ impl<A: Assets> Context<A> {
|
||||
}
|
||||
|
||||
/// Sets the app tray icon.
|
||||
#[cfg(desktop)]
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))]
|
||||
#[inline(always)]
|
||||
pub fn set_system_tray_icon(&mut self, icon: Icon) {
|
||||
self.system_tray_icon.replace(icon);
|
||||
pub fn set_tray_icon(&mut self, icon: Icon) {
|
||||
self.tray_icon.replace(icon);
|
||||
}
|
||||
|
||||
/// Sets the app shell scope.
|
||||
@ -524,7 +541,7 @@ impl<A: Assets> Context<A> {
|
||||
/// Manages a running application.
|
||||
pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
|
||||
/// The application handle associated with this manager.
|
||||
fn app_handle(&self) -> AppHandle<R> {
|
||||
fn app_handle(&self) -> &AppHandle<R> {
|
||||
self.managed_app_handle()
|
||||
}
|
||||
|
||||
@ -648,7 +665,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let handle = app.handle();
|
||||
/// let handle = app.handle().clone();
|
||||
/// let handler = app.listen_global("ready", move |event| {
|
||||
/// println!("app is ready");
|
||||
///
|
||||
@ -845,7 +862,7 @@ pub(crate) mod sealed {
|
||||
/// The manager behind the [`Managed`] item.
|
||||
fn manager(&self) -> &WindowManager<R>;
|
||||
fn runtime(&self) -> RuntimeOrDispatch<'_, R>;
|
||||
fn managed_app_handle(&self) -> AppHandle<R>;
|
||||
fn managed_app_handle(&self) -> &AppHandle<R>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -895,6 +912,23 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
macro_rules! run_main_thread {
|
||||
($self:ident, $ex:expr) => {{
|
||||
use std::sync::mpsc::channel;
|
||||
let (tx, rx) = channel();
|
||||
let self_ = $self.clone();
|
||||
let task = move || {
|
||||
let _ = tx.send($ex(self_));
|
||||
};
|
||||
$self.app_handle.run_on_main_thread(Box::new(task))?;
|
||||
rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage)
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) use run_main_thread;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_utils {
|
||||
use proptest::prelude::*;
|
||||
|
@ -10,6 +10,10 @@ use std::{
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
#[cfg(desktop)]
|
||||
use crate::menu::{Menu, MenuId};
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
use crate::tray::{TrayIcon, TrayIconId};
|
||||
use serde::Serialize;
|
||||
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
||||
use url::Url;
|
||||
@ -23,10 +27,7 @@ use tauri_utils::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
AppHandle, GlobalMenuEventListener, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad,
|
||||
PageLoadPayload, WindowMenuEvent,
|
||||
},
|
||||
app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, PageLoadPayload},
|
||||
event::{assert_event_name_is_valid, Event, EventHandler, Listeners},
|
||||
ipc::{Invoke, InvokeHandler, InvokeResponder},
|
||||
pattern::PatternJavascript,
|
||||
@ -49,11 +50,14 @@ use crate::{
|
||||
WindowEvent,
|
||||
};
|
||||
|
||||
#[cfg(desktop)]
|
||||
use crate::app::GlobalMenuEventListener;
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
use crate::app::GlobalTrayIconEventListener;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
use crate::path::BaseDirectory;
|
||||
|
||||
use crate::{runtime::menu::Menu, MenuEvent};
|
||||
|
||||
const WINDOW_RESIZED_EVENT: &str = "tauri://resize";
|
||||
const WINDOW_MOVED_EVENT: &str = "tauri://move";
|
||||
const WINDOW_CLOSE_REQUESTED_EVENT: &str = "tauri://close-requested";
|
||||
@ -65,7 +69,6 @@ const WINDOW_THEME_CHANGED: &str = "tauri://theme-changed";
|
||||
const WINDOW_FILE_DROP_EVENT: &str = "tauri://file-drop";
|
||||
const WINDOW_FILE_DROP_HOVER_EVENT: &str = "tauri://file-drop-hover";
|
||||
const WINDOW_FILE_DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled";
|
||||
const MENU_EVENT: &str = "tauri://menu";
|
||||
|
||||
pub(crate) const PROCESS_IPC_MESSAGE_FN: &str =
|
||||
include_str!("../scripts/process-ipc-message-fn.js");
|
||||
@ -210,9 +213,7 @@ fn replace_csp_nonce(
|
||||
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
pub struct InnerWindowManager<R: Runtime> {
|
||||
windows: Mutex<HashMap<String, Window<R>>>,
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
pub(crate) trays: Mutex<HashMap<String, crate::SystemTrayHandle<R>>>,
|
||||
pub(crate) windows: Mutex<HashMap<String, Window<R>>>,
|
||||
pub(crate) plugins: Mutex<PluginStore<R>>,
|
||||
listeners: Listeners,
|
||||
pub(crate) state: Arc<StateManager>,
|
||||
@ -227,18 +228,42 @@ pub struct InnerWindowManager<R: Runtime> {
|
||||
assets: Arc<dyn Assets>,
|
||||
pub(crate) default_window_icon: Option<Icon>,
|
||||
pub(crate) app_icon: Option<Vec<u8>>,
|
||||
#[cfg(desktop)]
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
pub(crate) tray_icon: Option<Icon>,
|
||||
|
||||
package_info: PackageInfo,
|
||||
/// The webview protocols available to all windows.
|
||||
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol<R>>>,
|
||||
/// A set containing a reference to the active menus, including
|
||||
/// the app-wide menu and the window-specific menus
|
||||
///
|
||||
/// This should be mainly used to acceess [`Menu::haccel`]
|
||||
/// to setup the accelerator handling in the event loop
|
||||
#[cfg(desktop)]
|
||||
pub menus: Arc<Mutex<HashMap<MenuId, Menu<R>>>>,
|
||||
/// The menu set to all windows.
|
||||
menu: Option<Menu>,
|
||||
#[cfg(desktop)]
|
||||
pub(crate) menu: Arc<Mutex<Option<Menu<R>>>>,
|
||||
/// Menu event listeners to all windows.
|
||||
menu_event_listeners: Arc<Vec<GlobalMenuEventListener<R>>>,
|
||||
#[cfg(desktop)]
|
||||
pub(crate) menu_event_listeners: Arc<Mutex<Vec<GlobalMenuEventListener<AppHandle<R>>>>>,
|
||||
/// Menu event listeners to specific windows.
|
||||
#[cfg(desktop)]
|
||||
pub(crate) window_menu_event_listeners:
|
||||
Arc<Mutex<HashMap<String, GlobalMenuEventListener<Window<R>>>>>,
|
||||
/// Window event listeners to all windows.
|
||||
window_event_listeners: Arc<Vec<GlobalWindowEventListener<R>>>,
|
||||
/// Tray icons
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
pub(crate) tray_icons: Arc<Mutex<Vec<TrayIcon<R>>>>,
|
||||
/// Global Tray icon event listeners.
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
pub(crate) global_tray_event_listeners:
|
||||
Arc<Mutex<Vec<GlobalTrayIconEventListener<AppHandle<R>>>>>,
|
||||
/// Tray icon event listeners.
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
pub(crate) tray_event_listeners:
|
||||
Arc<Mutex<HashMap<TrayIconId, GlobalTrayIconEventListener<TrayIcon<R>>>>>,
|
||||
/// Responder for invoke calls.
|
||||
invoke_responder: Option<Arc<InvokeResponder<R>>>,
|
||||
/// The script that initializes the invoke system.
|
||||
@ -257,10 +282,9 @@ impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
|
||||
.field("default_window_icon", &self.default_window_icon)
|
||||
.field("app_icon", &self.app_icon)
|
||||
.field("package_info", &self.package_info)
|
||||
.field("menu", &self.menu)
|
||||
.field("pattern", &self.pattern);
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
d.field("tray_icon", &self.tray_icon);
|
||||
|
||||
d.finish()
|
||||
@ -303,7 +327,7 @@ impl<R: Runtime> Clone for WindowManager<R> {
|
||||
}
|
||||
|
||||
impl<R: Runtime> WindowManager<R> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
pub(crate) fn with_handlers(
|
||||
#[allow(unused_mut)] mut context: Context<impl Assets>,
|
||||
plugins: PluginStore<R>,
|
||||
@ -312,7 +336,10 @@ impl<R: Runtime> WindowManager<R> {
|
||||
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol<R>>>,
|
||||
state: StateManager,
|
||||
window_event_listeners: Vec<GlobalWindowEventListener<R>>,
|
||||
(menu, menu_event_listeners): (Option<Menu>, Vec<GlobalMenuEventListener<R>>),
|
||||
#[cfg(desktop)] window_menu_event_listeners: HashMap<
|
||||
String,
|
||||
GlobalMenuEventListener<Window<R>>,
|
||||
>,
|
||||
(invoke_responder, invoke_initialization_script): (Option<Arc<InvokeResponder<R>>>, String),
|
||||
) -> Self {
|
||||
// generate a random isolation key at runtime
|
||||
@ -324,8 +351,6 @@ impl<R: Runtime> WindowManager<R> {
|
||||
Self {
|
||||
inner: Arc::new(InnerWindowManager {
|
||||
windows: Mutex::default(),
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
trays: Default::default(),
|
||||
plugins: Mutex::new(plugins),
|
||||
listeners: Listeners::default(),
|
||||
state: Arc::new(state),
|
||||
@ -335,14 +360,26 @@ impl<R: Runtime> WindowManager<R> {
|
||||
assets: context.assets,
|
||||
default_window_icon: context.default_window_icon,
|
||||
app_icon: context.app_icon,
|
||||
#[cfg(desktop)]
|
||||
tray_icon: context.system_tray_icon,
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
tray_icon: context.tray_icon,
|
||||
package_info: context.package_info,
|
||||
pattern: context.pattern,
|
||||
uri_scheme_protocols,
|
||||
menu,
|
||||
menu_event_listeners: Arc::new(menu_event_listeners),
|
||||
#[cfg(desktop)]
|
||||
menus: Default::default(),
|
||||
#[cfg(desktop)]
|
||||
menu: Default::default(),
|
||||
#[cfg(desktop)]
|
||||
menu_event_listeners: Default::default(),
|
||||
#[cfg(desktop)]
|
||||
window_menu_event_listeners: Arc::new(Mutex::new(window_menu_event_listeners)),
|
||||
window_event_listeners: Arc::new(window_event_listeners),
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
tray_icons: Default::default(),
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
global_tray_event_listeners: Default::default(),
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
tray_event_listeners: Default::default(),
|
||||
invoke_responder,
|
||||
invoke_initialization_script,
|
||||
}),
|
||||
@ -363,6 +400,83 @@ impl<R: Runtime> WindowManager<R> {
|
||||
self.inner.state.clone()
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
pub(crate) fn prepare_window_menu_creation_handler(
|
||||
&self,
|
||||
window_menu: Option<&crate::window::WindowMenu<R>>,
|
||||
) -> Option<impl Fn(tauri_runtime::window::RawWindow<'_>)> {
|
||||
{
|
||||
if let Some(menu) = window_menu {
|
||||
self
|
||||
.menus_stash_lock()
|
||||
.insert(menu.menu.id().clone(), menu.menu.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
return None;
|
||||
|
||||
#[cfg_attr(target_os = "macos", allow(unused_variables, unreachable_code))]
|
||||
if let Some(menu) = &window_menu {
|
||||
let menu = menu.menu.clone();
|
||||
Some(move |raw: tauri_runtime::window::RawWindow<'_>| {
|
||||
#[cfg(target_os = "windows")]
|
||||
let _ = menu.inner().init_for_hwnd(raw.hwnd as _);
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
let _ = menu
|
||||
.inner()
|
||||
.init_for_gtk_window(raw.gtk_window, raw.default_vbox);
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// App-wide menu.
|
||||
#[cfg(desktop)]
|
||||
pub(crate) fn menu_lock(&self) -> MutexGuard<'_, Option<Menu<R>>> {
|
||||
self.inner.menu.lock().expect("poisoned window manager")
|
||||
}
|
||||
|
||||
/// Menus stash.
|
||||
#[cfg(desktop)]
|
||||
pub(crate) fn menus_stash_lock(&self) -> MutexGuard<'_, HashMap<MenuId, Menu<R>>> {
|
||||
self.inner.menus.lock().expect("poisoned window manager")
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
pub(crate) fn is_menu_in_use<I: PartialEq<MenuId>>(&self, id: &I) -> bool {
|
||||
self
|
||||
.menu_lock()
|
||||
.as_ref()
|
||||
.map(|m| id.eq(m.id()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Menus stash.
|
||||
#[cfg(desktop)]
|
||||
pub(crate) fn insert_menu_into_stash(&self, menu: &Menu<R>) {
|
||||
self
|
||||
.menus_stash_lock()
|
||||
.insert(menu.id().clone(), menu.clone());
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option<&MenuId>) {
|
||||
if let Some(id) = id {
|
||||
let is_used_by_a_window = self.windows_lock().values().any(|w| w.is_menu_in_use(id));
|
||||
if !(self.is_menu_in_use(id) || is_used_by_a_window) {
|
||||
self.menus_stash_lock().remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The invoke responder.
|
||||
pub(crate) fn invoke_responder(&self) -> Option<Arc<InvokeResponder<R>>> {
|
||||
self.inner.invoke_responder.clone()
|
||||
@ -1037,12 +1151,6 @@ impl<R: Runtime> WindowManager<R> {
|
||||
}
|
||||
}
|
||||
|
||||
if pending.window_builder.get_menu().is_none() {
|
||||
if let Some(menu) = &self.inner.menu {
|
||||
pending = pending.set_menu(menu.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
pending = pending.on_webview_created(move |ctx| {
|
||||
@ -1136,12 +1244,19 @@ impl<R: Runtime> WindowManager<R> {
|
||||
Ok(pending)
|
||||
}
|
||||
|
||||
pub fn attach_window(
|
||||
pub(crate) fn attach_window(
|
||||
&self,
|
||||
app_handle: AppHandle<R>,
|
||||
window: DetachedWindow<EventLoopMessage, R>,
|
||||
#[cfg(desktop)] menu: Option<crate::window::WindowMenu<R>>,
|
||||
) -> Window<R> {
|
||||
let window = Window::new(self.clone(), window, app_handle);
|
||||
let window = Window::new(
|
||||
self.clone(),
|
||||
window,
|
||||
app_handle,
|
||||
#[cfg(desktop)]
|
||||
menu,
|
||||
);
|
||||
|
||||
let window_ = window.clone();
|
||||
let window_event_listeners = self.inner.window_event_listeners.clone();
|
||||
@ -1155,19 +1270,6 @@ impl<R: Runtime> WindowManager<R> {
|
||||
});
|
||||
}
|
||||
});
|
||||
{
|
||||
let window_ = window.clone();
|
||||
let menu_event_listeners = self.inner.menu_event_listeners.clone();
|
||||
window.on_menu_event(move |event| {
|
||||
let _ = on_menu_event(&window_, &event);
|
||||
for handler in menu_event_listeners.iter() {
|
||||
handler(WindowMenuEvent {
|
||||
window: window_.clone(),
|
||||
menu_item_id: event.menu_item_id.clone(),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// insert the window into our manager
|
||||
{
|
||||
@ -1297,33 +1399,6 @@ impl<R: Runtime> WindowManager<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tray APIs
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
impl<R: Runtime> WindowManager<R> {
|
||||
pub fn get_tray(&self, id: &str) -> Option<crate::SystemTrayHandle<R>> {
|
||||
self.inner.trays.lock().unwrap().get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn trays(&self) -> HashMap<String, crate::SystemTrayHandle<R>> {
|
||||
self.inner.trays.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn attach_tray(&self, id: String, tray: crate::SystemTrayHandle<R>) {
|
||||
self.inner.trays.lock().unwrap().insert(id, tray);
|
||||
}
|
||||
|
||||
pub fn get_tray_by_runtime_id(&self, id: u16) -> Option<(String, crate::SystemTrayHandle<R>)> {
|
||||
let trays = self.inner.trays.lock().unwrap();
|
||||
let iter = trays.iter();
|
||||
for (tray_id, tray) in iter {
|
||||
if tray.id == id {
|
||||
return Some((tray_id.clone(), tray.clone()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn on_window_event<R: Runtime>(
|
||||
window: &Window<R>,
|
||||
manager: &WindowManager<R>,
|
||||
@ -1396,10 +1471,6 @@ struct ScaleFactorChanged {
|
||||
size: PhysicalSize<u32>,
|
||||
}
|
||||
|
||||
fn on_menu_event<R: Runtime>(window: &Window<R>, event: &MenuEvent) -> crate::Result<()> {
|
||||
window.emit(MENU_EVENT, event.menu_item_id.clone())
|
||||
}
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
fn request_to_path(request: &tauri_runtime::http::Request, base_url: &str) -> String {
|
||||
let mut path = request
|
||||
|
90
core/tauri/src/menu/builders/check.rs
Normal file
90
core/tauri/src/menu/builders/check.rs
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{menu::CheckMenuItem, menu::MenuId, Manager, Runtime};
|
||||
|
||||
/// A builder type for [`CheckMenuItem`]
|
||||
pub struct CheckMenuItemBuilder {
|
||||
id: Option<MenuId>,
|
||||
text: String,
|
||||
enabled: bool,
|
||||
checked: bool,
|
||||
accelerator: Option<String>,
|
||||
}
|
||||
|
||||
impl CheckMenuItemBuilder {
|
||||
/// Create a new menu item builder.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn new<S: AsRef<str>>(text: S) -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
text: text.as_ref().to_string(),
|
||||
enabled: true,
|
||||
checked: true,
|
||||
accelerator: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new menu item builder with the specified id.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(id: I, text: S) -> Self {
|
||||
Self {
|
||||
id: Some(id.into()),
|
||||
text: text.as_ref().to_string(),
|
||||
enabled: true,
|
||||
checked: true,
|
||||
accelerator: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the id for this menu item.
|
||||
pub fn id<I: Into<MenuId>>(mut self, id: I) -> Self {
|
||||
self.id.replace(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the enabled state for this menu item.
|
||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||
self.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the checked state for this menu item.
|
||||
pub fn checked(mut self, checked: bool) -> Self {
|
||||
self.checked = checked;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the accelerator for this menu item.
|
||||
pub fn accelerator<S: AsRef<str>>(mut self, accelerator: S) -> Self {
|
||||
self.accelerator.replace(accelerator.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the menu item
|
||||
pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> CheckMenuItem<R> {
|
||||
if let Some(id) = self.id {
|
||||
CheckMenuItem::with_id(
|
||||
manager,
|
||||
id,
|
||||
self.text,
|
||||
self.enabled,
|
||||
self.checked,
|
||||
self.accelerator,
|
||||
)
|
||||
} else {
|
||||
CheckMenuItem::new(
|
||||
manager,
|
||||
self.text,
|
||||
self.enabled,
|
||||
self.checked,
|
||||
self.accelerator,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
128
core/tauri/src/menu/builders/icon.rs
Normal file
128
core/tauri/src/menu/builders/icon.rs
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use muda::{MenuId, NativeIcon};
|
||||
|
||||
use crate::{menu::IconMenuItem, Icon, Manager, Runtime};
|
||||
|
||||
/// A builder type for [`IconMenuItem`]
|
||||
pub struct IconMenuItemBuilder {
|
||||
id: Option<MenuId>,
|
||||
text: String,
|
||||
enabled: bool,
|
||||
icon: Option<Icon>,
|
||||
native_icon: Option<NativeIcon>,
|
||||
accelerator: Option<String>,
|
||||
}
|
||||
|
||||
impl IconMenuItemBuilder {
|
||||
/// Create a new menu item builder.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn new<S: AsRef<str>>(text: S) -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
text: text.as_ref().to_string(),
|
||||
enabled: true,
|
||||
icon: None,
|
||||
native_icon: None,
|
||||
accelerator: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new menu item builder with the specified id.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(id: I, text: S) -> Self {
|
||||
Self {
|
||||
id: Some(id.into()),
|
||||
text: text.as_ref().to_string(),
|
||||
enabled: true,
|
||||
icon: None,
|
||||
native_icon: None,
|
||||
accelerator: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the id for this menu item.
|
||||
pub fn id<I: Into<MenuId>>(mut self, id: I) -> Self {
|
||||
self.id.replace(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the enabled state for this menu item.
|
||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||
self.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the accelerator for this menu item.
|
||||
pub fn accelerator<S: AsRef<str>>(mut self, accelerator: S) -> Self {
|
||||
self.accelerator.replace(accelerator.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the icon for this menu item.
|
||||
///
|
||||
/// **Note:** This method conflicts with [`Self::native_icon`]
|
||||
/// so calling one of them, will reset the other.
|
||||
pub fn icon(mut self, icon: Icon) -> Self {
|
||||
self.icon.replace(icon);
|
||||
self.native_icon = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the icon for this menu item.
|
||||
///
|
||||
/// **Note:** This method conflicts with [`Self::icon`]
|
||||
/// so calling one of them, will reset the other.
|
||||
pub fn native_icon(mut self, icon: NativeIcon) -> Self {
|
||||
self.native_icon.replace(icon);
|
||||
self.icon = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the menu item
|
||||
pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> IconMenuItem<R> {
|
||||
if self.icon.is_some() {
|
||||
if let Some(id) = self.id {
|
||||
IconMenuItem::with_id(
|
||||
manager,
|
||||
id,
|
||||
self.text,
|
||||
self.enabled,
|
||||
self.icon,
|
||||
self.accelerator,
|
||||
)
|
||||
} else {
|
||||
IconMenuItem::new(
|
||||
manager,
|
||||
self.text,
|
||||
self.enabled,
|
||||
self.icon,
|
||||
self.accelerator,
|
||||
)
|
||||
}
|
||||
} else if let Some(id) = self.id {
|
||||
IconMenuItem::with_id_and_native_icon(
|
||||
manager,
|
||||
id,
|
||||
self.text,
|
||||
self.enabled,
|
||||
self.native_icon,
|
||||
self.accelerator,
|
||||
)
|
||||
} else {
|
||||
IconMenuItem::with_native_icon(
|
||||
manager,
|
||||
self.text,
|
||||
self.enabled,
|
||||
self.native_icon,
|
||||
self.accelerator,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
321
core/tauri/src/menu/builders/menu.rs
Normal file
321
core/tauri/src/menu/builders/menu.rs
Normal file
@ -0,0 +1,321 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{menu::*, Icon, Manager, Runtime};
|
||||
|
||||
/// A builder type for [`Menu`]
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tauri::menu::*;
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(move |app| {
|
||||
/// let handle = app.handle();
|
||||
/// # let icon1 = tauri::Icon::Rgba {
|
||||
/// # rgba: Vec::new(),
|
||||
/// # width: 0,
|
||||
/// # height: 0,
|
||||
/// # };
|
||||
/// let menu = MenuBuilder::new(handle)
|
||||
/// .item(&MenuItem::new(handle, "MenuItem 1", true, None))
|
||||
/// .items(&[
|
||||
/// &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None),
|
||||
/// &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None),
|
||||
/// ])
|
||||
/// .separator()
|
||||
/// .cut()
|
||||
/// .copy()
|
||||
/// .paste()
|
||||
/// .separator()
|
||||
/// .text("MenuItem 2")
|
||||
/// .check("CheckMenuItem 2")
|
||||
/// .icon("IconMenuItem 2", app.default_window_icon().cloned().unwrap())
|
||||
/// .build()?;
|
||||
/// app.set_menu(menu);
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub struct MenuBuilder<'m, R: Runtime, M: Manager<R>> {
|
||||
id: Option<MenuId>,
|
||||
manager: &'m M,
|
||||
items: Vec<MenuItemKind<R>>,
|
||||
}
|
||||
|
||||
impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
|
||||
/// Create a new menu builder.
|
||||
pub fn new(manager: &'m M) -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
items: Vec::new(),
|
||||
manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new menu builder with the specified id.
|
||||
pub fn with_id<I: Into<MenuId>>(manager: &'m M, id: I) -> Self {
|
||||
Self {
|
||||
id: Some(id.into()),
|
||||
items: Vec::new(),
|
||||
manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the id for this menu.
|
||||
pub fn id<I: Into<MenuId>>(mut self, id: I) -> Self {
|
||||
self.id.replace(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add this item to the menu.
|
||||
pub fn item(mut self, item: &dyn IsMenuItem<R>) -> Self {
|
||||
self.items.push(item.kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add these items to the menu.
|
||||
pub fn items(mut self, items: &[&dyn IsMenuItem<R>]) -> Self {
|
||||
for item in items {
|
||||
self = self.item(*item);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a [MenuItem] to the menu.
|
||||
pub fn text<S: AsRef<str>>(mut self, text: S) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(MenuItem::new(self.manager, text, true, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a [CheckMenuItem] to the menu.
|
||||
pub fn check<S: AsRef<str>>(mut self, text: S) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(CheckMenuItem::new(self.manager, text, true, true, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an [IconMenuItem] to the menu.
|
||||
pub fn icon<S: AsRef<str>>(mut self, text: S, icon: Icon) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(IconMenuItem::new(self.manager, text, true, Some(icon), None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an [IconMenuItem] with a native icon to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
pub fn native_icon<S: AsRef<str>>(mut self, text: S, icon: NativeIcon) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(IconMenuItem::with_native_icon(self.manager, text, true, Some(icon), None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Separator menu item to the menu.
|
||||
pub fn separator(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::separator(self.manager).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Copy menu item to the menu.
|
||||
pub fn copy(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::copy(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Cut menu item to the menu.
|
||||
pub fn cut(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::cut(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Paste menu item to the menu.
|
||||
pub fn paste(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::paste(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add SelectAll menu item to the menu.
|
||||
pub fn select_all(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::select_all(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Undo menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn undo(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::undo(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
/// Add Redo menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn redo(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::redo(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Minimize window menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn minimize(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::minimize(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Maximize window menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn maximize(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::maximize(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Fullscreen menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn fullscreen(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::fullscreen(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Hide window menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn hide(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::hide(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Hide other windows menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn hide_others(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::hide_others(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Show all app windows menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn show_all(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::show_all(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Close window menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn close_window(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::close_window(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Quit app menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn quit(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::quit(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add About app menu item to the menu.
|
||||
pub fn about(mut self, metadata: Option<AboutMetadata>) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::about(self.manager, None, metadata).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Services menu item to the menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn services(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::services(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds this menu
|
||||
pub fn build(self) -> crate::Result<Menu<R>> {
|
||||
if self.items.is_empty() {
|
||||
Ok(if let Some(id) = self.id {
|
||||
Menu::with_id(self.manager, id)
|
||||
} else {
|
||||
Menu::new(self.manager)
|
||||
})
|
||||
} else {
|
||||
let items = self
|
||||
.items
|
||||
.iter()
|
||||
.map(|i| i as &dyn IsMenuItem<R>)
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(id) = self.id {
|
||||
Menu::with_id_and_items(self.manager, id, &items)
|
||||
} else {
|
||||
Menu::with_items(self.manager, &items)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
core/tauri/src/menu/builders/mod.rs
Normal file
20
core/tauri/src/menu/builders/mod.rs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#![cfg(desktop)]
|
||||
|
||||
//! A module containting menu builder types
|
||||
|
||||
pub use muda::AboutMetadataBuilder;
|
||||
|
||||
mod menu;
|
||||
pub use menu::MenuBuilder;
|
||||
mod normal;
|
||||
pub use normal::MenuItemBuilder;
|
||||
mod submenu;
|
||||
pub use submenu::SubmenuBuilder;
|
||||
mod check;
|
||||
pub use check::CheckMenuItemBuilder;
|
||||
mod icon;
|
||||
pub use icon::IconMenuItemBuilder;
|
68
core/tauri/src/menu/builders/normal.rs
Normal file
68
core/tauri/src/menu/builders/normal.rs
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{menu::MenuId, menu::MenuItem, Manager, Runtime};
|
||||
|
||||
/// A builder type for [`MenuItem`]
|
||||
pub struct MenuItemBuilder {
|
||||
id: Option<MenuId>,
|
||||
text: String,
|
||||
enabled: bool,
|
||||
accelerator: Option<String>,
|
||||
}
|
||||
|
||||
impl MenuItemBuilder {
|
||||
/// Create a new menu item builder.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn new<S: AsRef<str>>(text: S) -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
text: text.as_ref().to_string(),
|
||||
enabled: true,
|
||||
accelerator: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new menu item builder with the specified id.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(id: I, text: S) -> Self {
|
||||
Self {
|
||||
id: Some(id.into()),
|
||||
text: text.as_ref().to_string(),
|
||||
enabled: true,
|
||||
accelerator: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the id for this menu item.
|
||||
pub fn id<I: Into<MenuId>>(mut self, id: I) -> Self {
|
||||
self.id.replace(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the enabled state for this menu item.
|
||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||
self.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the accelerator for this menu item.
|
||||
pub fn accelerator<S: AsRef<str>>(mut self, accelerator: S) -> Self {
|
||||
self.accelerator.replace(accelerator.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the menu item
|
||||
pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> MenuItem<R> {
|
||||
if let Some(id) = self.id {
|
||||
MenuItem::with_id(manager, id, self.text, self.enabled, self.accelerator)
|
||||
} else {
|
||||
MenuItem::new(manager, self.text, self.enabled, self.accelerator)
|
||||
}
|
||||
}
|
||||
}
|
342
core/tauri/src/menu/builders/submenu.rs
Normal file
342
core/tauri/src/menu/builders/submenu.rs
Normal file
@ -0,0 +1,342 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{menu::*, Icon, Manager, Runtime};
|
||||
|
||||
/// A builder type for [`Submenu`]
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tauri::menu::*;
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(move |app| {
|
||||
/// let handle = app.handle();
|
||||
/// # let icon1 = tauri::Icon::Rgba {
|
||||
/// # rgba: Vec::new(),
|
||||
/// # width: 0,
|
||||
/// # height: 0,
|
||||
/// # };
|
||||
/// # let icon2 = icon1.clone();
|
||||
/// let menu = Menu::new(handle);
|
||||
/// let submenu = SubmenuBuilder::new(handle, "File")
|
||||
/// .item(&MenuItem::new(handle, "MenuItem 1", true, None))
|
||||
/// .items(&[
|
||||
/// &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None),
|
||||
/// &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None),
|
||||
/// ])
|
||||
/// .separator()
|
||||
/// .cut()
|
||||
/// .copy()
|
||||
/// .paste()
|
||||
/// .separator()
|
||||
/// .text("MenuItem 2")
|
||||
/// .check("CheckMenuItem 2")
|
||||
/// .icon("IconMenuItem 2", app.default_window_icon().cloned().unwrap())
|
||||
/// .build()?;
|
||||
/// menu.append(&submenu)?;
|
||||
/// app.set_menu(menu);
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub struct SubmenuBuilder<'m, R: Runtime, M: Manager<R>> {
|
||||
id: Option<MenuId>,
|
||||
manager: &'m M,
|
||||
text: String,
|
||||
enabled: bool,
|
||||
items: Vec<MenuItemKind<R>>,
|
||||
}
|
||||
|
||||
impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
|
||||
/// Create a new submenu builder.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn new<S: AsRef<str>>(manager: &'m M, text: S) -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
items: Vec::new(),
|
||||
text: text.as_ref().to_string(),
|
||||
enabled: true,
|
||||
manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new submenu builder with the specified id.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(manager: &'m M, id: I, text: S) -> Self {
|
||||
Self {
|
||||
id: Some(id.into()),
|
||||
text: text.as_ref().to_string(),
|
||||
enabled: true,
|
||||
items: Vec::new(),
|
||||
manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the id for this submenu.
|
||||
pub fn id<I: Into<MenuId>>(mut self, id: I) -> Self {
|
||||
self.id.replace(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the enabled state for the submenu.
|
||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||
self.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add this item to the submenu.
|
||||
pub fn item(mut self, item: &dyn IsMenuItem<R>) -> Self {
|
||||
self.items.push(item.kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add these items to the submenu.
|
||||
pub fn items(mut self, items: &[&dyn IsMenuItem<R>]) -> Self {
|
||||
for item in items {
|
||||
self = self.item(*item);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a [MenuItem] to the submenu.
|
||||
pub fn text<S: AsRef<str>>(mut self, text: S) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(MenuItem::new(self.manager, text, true, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a [CheckMenuItem] to the submenu.
|
||||
pub fn check<S: AsRef<str>>(mut self, text: S) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(CheckMenuItem::new(self.manager, text, true, true, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an [IconMenuItem] to the submenu.
|
||||
pub fn icon<S: AsRef<str>>(mut self, text: S, icon: Icon) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(IconMenuItem::new(self.manager, text, true, Some(icon), None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an [IconMenuItem] with a native icon to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
pub fn native_icon<S: AsRef<str>>(mut self, text: S, icon: NativeIcon) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(IconMenuItem::with_native_icon(self.manager, text, true, Some(icon), None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Separator menu item to the submenu.
|
||||
pub fn separator(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::separator(self.manager).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Copy menu item to the submenu.
|
||||
pub fn copy(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::copy(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Cut menu item to the submenu.
|
||||
pub fn cut(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::cut(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Paste menu item to the submenu.
|
||||
pub fn paste(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::paste(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add SelectAll menu item to the submenu.
|
||||
pub fn select_all(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::select_all(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Undo menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn undo(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::undo(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
/// Add Redo menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn redo(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::redo(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Minimize window menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn minimize(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::minimize(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Maximize window menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn maximize(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::maximize(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Fullscreen menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn fullscreen(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::fullscreen(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Hide window menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn hide(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::hide(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Hide other windows menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn hide_others(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::hide_others(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Show all app windows menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn show_all(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::show_all(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Close window menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn close_window(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::close_window(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Quit app menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn quit(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::quit(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add About app menu item to the submenu.
|
||||
pub fn about(mut self, metadata: Option<AboutMetadata>) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::about(self.manager, None, metadata).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Services menu item to the submenu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn services(mut self) -> Self {
|
||||
self
|
||||
.items
|
||||
.push(PredefinedMenuItem::services(self.manager, None).kind());
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds this submenu
|
||||
pub fn build(self) -> crate::Result<Submenu<R>> {
|
||||
if self.items.is_empty() {
|
||||
Ok(if let Some(id) = self.id {
|
||||
Submenu::with_id(self.manager, id, self.text, self.enabled)
|
||||
} else {
|
||||
Submenu::new(self.manager, self.text, self.enabled)
|
||||
})
|
||||
} else {
|
||||
let items = self
|
||||
.items
|
||||
.iter()
|
||||
.map(|i| i as &dyn IsMenuItem<R>)
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(id) = self.id {
|
||||
Submenu::with_id_and_items(self.manager, id, self.text, self.enabled, &items)
|
||||
} else {
|
||||
Submenu::with_items(self.manager, self.text, self.enabled, &items)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
148
core/tauri/src/menu/check.rs
Normal file
148
core/tauri/src/menu/check.rs
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime};
|
||||
|
||||
/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
|
||||
///
|
||||
/// [`Menu`]: super::Menu
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub struct CheckMenuItem<R: Runtime> {
|
||||
pub(crate) id: MenuId,
|
||||
pub(crate) inner: muda::CheckMenuItem,
|
||||
pub(crate) app_handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for CheckMenuItem<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id.clone(),
|
||||
inner: self.inner.clone(),
|
||||
app_handle: self.app_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// We make sure it always runs on the main thread.
|
||||
unsafe impl<R: Runtime> Sync for CheckMenuItem<R> {}
|
||||
unsafe impl<R: Runtime> Send for CheckMenuItem<R> {}
|
||||
|
||||
impl<R: Runtime> super::sealed::IsMenuItemBase for CheckMenuItem<R> {
|
||||
fn inner(&self) -> &dyn muda::IsMenuItem {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> super::IsMenuItem<R> for CheckMenuItem<R> {
|
||||
fn kind(&self) -> super::MenuItemKind<R> {
|
||||
super::MenuItemKind::Check(self.clone())
|
||||
}
|
||||
|
||||
fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> CheckMenuItem<R> {
|
||||
/// Create a new menu item.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn new<M: Manager<R>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
checked: bool,
|
||||
acccelerator: Option<S>,
|
||||
) -> Self {
|
||||
let item = muda::CheckMenuItem::new(
|
||||
text,
|
||||
enabled,
|
||||
checked,
|
||||
acccelerator.and_then(|s| s.as_ref().parse().ok()),
|
||||
);
|
||||
Self {
|
||||
id: item.id().clone(),
|
||||
inner: item,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new menu item with the specified id.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn with_id<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
id: I,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
checked: bool,
|
||||
acccelerator: Option<S>,
|
||||
) -> Self {
|
||||
let item = muda::CheckMenuItem::with_id(
|
||||
id,
|
||||
text,
|
||||
enabled,
|
||||
checked,
|
||||
acccelerator.and_then(|s| s.as_ref().parse().ok()),
|
||||
);
|
||||
Self {
|
||||
id: item.id().clone(),
|
||||
inner: item,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The application handle associated with this type.
|
||||
pub fn app_handle(&self) -> &AppHandle<R> {
|
||||
&self.app_handle
|
||||
}
|
||||
|
||||
/// Returns a unique identifier associated with this menu item.
|
||||
pub fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Get the text for this menu item.
|
||||
pub fn text(&self) -> crate::Result<String> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.text())
|
||||
}
|
||||
|
||||
/// Set the text for this menu item. `text` could optionally contain
|
||||
/// an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
|
||||
let text = text.as_ref().to_string();
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
|
||||
}
|
||||
|
||||
/// Get whether this menu item is enabled or not.
|
||||
pub fn is_enabled(&self) -> crate::Result<bool> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.is_enabled())
|
||||
}
|
||||
|
||||
/// Enable or disable this menu item.
|
||||
pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled))
|
||||
}
|
||||
|
||||
/// Set this menu item accelerator.
|
||||
pub fn set_accelerator<S: AsRef<str>>(&self, acccelerator: Option<S>) -> crate::Result<()> {
|
||||
let accel = acccelerator.and_then(|s| s.as_ref().parse().ok());
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Get whether this check menu item is checked or not.
|
||||
pub fn is_checked(&self) -> crate::Result<bool> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.is_checked())
|
||||
}
|
||||
|
||||
/// Check or Uncheck this check menu item.
|
||||
pub fn set_checked(&self, checked: bool) -> crate::Result<()> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_checked(checked))
|
||||
}
|
||||
}
|
214
core/tauri/src/menu/icon.rs
Normal file
214
core/tauri/src/menu/icon.rs
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::NativeIcon;
|
||||
use crate::{menu::MenuId, run_main_thread, AppHandle, Icon, Manager, Runtime};
|
||||
|
||||
/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
|
||||
///
|
||||
/// [`Menu`]: super::Menu
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub struct IconMenuItem<R: Runtime> {
|
||||
pub(crate) id: MenuId,
|
||||
pub(crate) inner: muda::IconMenuItem,
|
||||
pub(crate) app_handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for IconMenuItem<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id.clone(),
|
||||
inner: self.inner.clone(),
|
||||
app_handle: self.app_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// We make sure it always runs on the main thread.
|
||||
unsafe impl<R: Runtime> Sync for IconMenuItem<R> {}
|
||||
unsafe impl<R: Runtime> Send for IconMenuItem<R> {}
|
||||
|
||||
impl<R: Runtime> super::sealed::IsMenuItemBase for IconMenuItem<R> {
|
||||
fn inner(&self) -> &dyn muda::IsMenuItem {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> super::IsMenuItem<R> for IconMenuItem<R> {
|
||||
fn kind(&self) -> super::MenuItemKind<R> {
|
||||
super::MenuItemKind::Icon(self.clone())
|
||||
}
|
||||
|
||||
fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> IconMenuItem<R> {
|
||||
/// Create a new menu item.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn new<M: Manager<R>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
icon: Option<Icon>,
|
||||
acccelerator: Option<S>,
|
||||
) -> Self {
|
||||
let item = muda::IconMenuItem::new(
|
||||
text,
|
||||
enabled,
|
||||
icon.and_then(|i| i.try_into().ok()),
|
||||
acccelerator.and_then(|s| s.as_ref().parse().ok()),
|
||||
);
|
||||
Self {
|
||||
id: item.id().clone(),
|
||||
inner: item,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new menu item with the specified id.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn with_id<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
id: I,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
icon: Option<Icon>,
|
||||
acccelerator: Option<S>,
|
||||
) -> Self {
|
||||
let item = muda::IconMenuItem::with_id(
|
||||
id,
|
||||
text,
|
||||
enabled,
|
||||
icon.and_then(|i| i.try_into().ok()),
|
||||
acccelerator.and_then(|s| s.as_ref().parse().ok()),
|
||||
);
|
||||
Self {
|
||||
id: item.id().clone(),
|
||||
inner: item,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new icon menu item but with a native icon.
|
||||
///
|
||||
/// See [`IconMenuItem::new`] for more info.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
pub fn with_native_icon<M: Manager<R>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
native_icon: Option<NativeIcon>,
|
||||
acccelerator: Option<S>,
|
||||
) -> Self {
|
||||
let item = muda::IconMenuItem::with_native_icon(
|
||||
text,
|
||||
enabled,
|
||||
native_icon,
|
||||
acccelerator.and_then(|s| s.as_ref().parse().ok()),
|
||||
);
|
||||
Self {
|
||||
id: item.id().clone(),
|
||||
inner: item,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new icon menu item with the specified id but with a native icon.
|
||||
///
|
||||
/// See [`IconMenuItem::new`] for more info.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
pub fn with_id_and_native_icon<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
id: I,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
native_icon: Option<NativeIcon>,
|
||||
acccelerator: Option<S>,
|
||||
) -> Self {
|
||||
let item = muda::IconMenuItem::with_id_and_native_icon(
|
||||
id,
|
||||
text,
|
||||
enabled,
|
||||
native_icon,
|
||||
acccelerator.and_then(|s| s.as_ref().parse().ok()),
|
||||
);
|
||||
Self {
|
||||
id: item.id().clone(),
|
||||
inner: item,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The application handle associated with this type.
|
||||
pub fn app_handle(&self) -> &AppHandle<R> {
|
||||
&self.app_handle
|
||||
}
|
||||
|
||||
/// Returns a unique identifier associated with this menu item.
|
||||
pub fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Get the text for this menu item.
|
||||
pub fn text(&self) -> crate::Result<String> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.text())
|
||||
}
|
||||
|
||||
/// Set the text for this menu item. `text` could optionally contain
|
||||
/// an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
|
||||
let text = text.as_ref().to_string();
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
|
||||
}
|
||||
|
||||
/// Get whether this menu item is enabled or not.
|
||||
pub fn is_enabled(&self) -> crate::Result<bool> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.is_enabled())
|
||||
}
|
||||
|
||||
/// Enable or disable this menu item.
|
||||
pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled))
|
||||
}
|
||||
|
||||
/// Set this menu item accelerator.
|
||||
pub fn set_accelerator<S: AsRef<str>>(&self, acccelerator: Option<S>) -> crate::Result<()> {
|
||||
let accel = acccelerator.and_then(|s| s.as_ref().parse().ok());
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Change this menu item icon or remove it.
|
||||
pub fn set_icon(&self, icon: Option<Icon>) -> crate::Result<()> {
|
||||
run_main_thread!(self, |self_: Self| self_
|
||||
.inner
|
||||
.set_icon(icon.and_then(|i| i.try_into().ok())))
|
||||
}
|
||||
|
||||
/// Change this menu item icon to a native image or remove it.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
pub fn set_native_icon(&mut self, _icon: Option<NativeIcon>) -> crate::Result<()> {
|
||||
#[cfg(target_os = "macos")]
|
||||
return run_main_thread!(self, |mut self_: Self| self_.inner.set_native_icon(_icon));
|
||||
#[allow(unreachable_code)]
|
||||
Ok(())
|
||||
}
|
||||
}
|
397
core/tauri/src/menu/menu.rs
Normal file
397
core/tauri/src/menu/menu.rs
Normal file
@ -0,0 +1,397 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::sealed::ContextMenuBase;
|
||||
use super::{IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu};
|
||||
use crate::Window;
|
||||
use crate::{run_main_thread, AppHandle, Manager, Position, Runtime};
|
||||
use muda::ContextMenu;
|
||||
use muda::{AboutMetadata, MenuId};
|
||||
|
||||
/// Expected submenu id of the Window menu for macOS.
|
||||
pub const WINDOW_SUBMENU_ID: &str = "__tauri_window_menu__";
|
||||
/// Expected submenu id of the Help menu for macOS.
|
||||
pub const HELP_SUBMENU_ID: &str = "__tauri_help_menu__";
|
||||
|
||||
/// A type that is either a menu bar on the window
|
||||
/// on Windows and Linux or as a global menu in the menubar on macOS.
|
||||
pub struct Menu<R: Runtime> {
|
||||
pub(crate) id: MenuId,
|
||||
pub(crate) inner: muda::Menu,
|
||||
pub(crate) app_handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// We make sure it always runs on the main thread.
|
||||
unsafe impl<R: Runtime> Sync for Menu<R> {}
|
||||
unsafe impl<R: Runtime> Send for Menu<R> {}
|
||||
|
||||
impl<R: Runtime> Clone for Menu<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id.clone(),
|
||||
inner: self.inner.clone(),
|
||||
app_handle: self.app_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> super::ContextMenu for Menu<R> {
|
||||
fn popup<T: Runtime>(&self, window: Window<T>) -> crate::Result<()> {
|
||||
self.popup_inner(window, None::<Position>)
|
||||
}
|
||||
|
||||
fn popup_at<T: Runtime, P: Into<Position>>(
|
||||
&self,
|
||||
window: Window<T>,
|
||||
position: P,
|
||||
) -> crate::Result<()> {
|
||||
self.popup_inner(window, Some(position))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> ContextMenuBase for Menu<R> {
|
||||
fn popup_inner<T: Runtime, P: Into<crate::Position>>(
|
||||
&self,
|
||||
window: crate::Window<T>,
|
||||
position: Option<P>,
|
||||
) -> crate::Result<()> {
|
||||
let position = position.map(Into::into).map(super::into_position);
|
||||
run_main_thread!(self, move |self_: Self| {
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Ok(view) = window.ns_view() {
|
||||
self_
|
||||
.inner()
|
||||
.show_context_menu_for_nsview(view as _, position);
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
if let Ok(w) = window.gtk_window() {
|
||||
self_.inner().show_context_menu_for_gtk_window(&w, position);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if let Ok(hwnd) = window.hwnd() {
|
||||
self_.inner().show_context_menu_for_hwnd(hwnd.0, position)
|
||||
}
|
||||
})
|
||||
}
|
||||
fn inner(&self) -> &dyn muda::ContextMenu {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
fn inner_owned(&self) -> Box<dyn muda::ContextMenu> {
|
||||
Box::new(self.clone().inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Menu<R> {
|
||||
/// Creates a new menu.
|
||||
pub fn new<M: Manager<R>>(manager: &M) -> Self {
|
||||
let menu = muda::Menu::new();
|
||||
Self {
|
||||
id: menu.id().clone(),
|
||||
inner: menu,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new menu with the specified id.
|
||||
pub fn with_id<M: Manager<R>, I: Into<MenuId>>(manager: &M, id: I) -> Self {
|
||||
let menu = muda::Menu::with_id(id);
|
||||
Self {
|
||||
id: menu.id().clone(),
|
||||
inner: menu,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally.
|
||||
pub fn with_items<M: Manager<R>>(
|
||||
manager: &M,
|
||||
items: &[&dyn IsMenuItem<R>],
|
||||
) -> crate::Result<Self> {
|
||||
let menu = Self::new(manager);
|
||||
menu.append_items(items)?;
|
||||
Ok(menu)
|
||||
}
|
||||
|
||||
/// Creates a new menu with the specified id and given `items`.
|
||||
/// It calls [`Menu::new`] and [`Menu::append_items`] internally.
|
||||
pub fn with_id_and_items<M: Manager<R>, I: Into<MenuId>>(
|
||||
manager: &M,
|
||||
id: I,
|
||||
items: &[&dyn IsMenuItem<R>],
|
||||
) -> crate::Result<Self> {
|
||||
let menu = Self::with_id(manager, id);
|
||||
menu.append_items(items)?;
|
||||
Ok(menu)
|
||||
}
|
||||
|
||||
/// Creates a menu filled with default menu items and submenus.
|
||||
pub fn default(app_handle: &AppHandle<R>) -> crate::Result<Self> {
|
||||
let pkg_info = app_handle.package_info();
|
||||
let config = app_handle.config();
|
||||
let about_metadata = AboutMetadata {
|
||||
name: Some(pkg_info.name.clone()),
|
||||
version: Some(pkg_info.version.to_string()),
|
||||
copyright: config.tauri.bundle.copyright.clone(),
|
||||
authors: config.tauri.bundle.publisher.clone().map(|p| vec![p]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let window_menu = Submenu::with_id_and_items(
|
||||
app_handle,
|
||||
WINDOW_SUBMENU_ID,
|
||||
"Window",
|
||||
true,
|
||||
&[
|
||||
&PredefinedMenuItem::minimize(app_handle, None),
|
||||
&PredefinedMenuItem::maximize(app_handle, None),
|
||||
#[cfg(target_os = "macos")]
|
||||
&PredefinedMenuItem::separator(app_handle),
|
||||
&PredefinedMenuItem::close_window(app_handle, None),
|
||||
],
|
||||
)?;
|
||||
|
||||
let help_menu = Submenu::with_id_and_items(
|
||||
app_handle,
|
||||
HELP_SUBMENU_ID,
|
||||
"Help",
|
||||
true,
|
||||
&[
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
&PredefinedMenuItem::about(app_handle, None, Some(about_metadata)),
|
||||
],
|
||||
)?;
|
||||
|
||||
let menu = Menu::with_items(
|
||||
app_handle,
|
||||
&[
|
||||
#[cfg(target_os = "macos")]
|
||||
&Submenu::with_items(
|
||||
app_handle,
|
||||
pkg_info.name.clone(),
|
||||
true,
|
||||
&[
|
||||
&PredefinedMenuItem::about(app_handle, None, Some(about_metadata)),
|
||||
&PredefinedMenuItem::separator(app_handle),
|
||||
&PredefinedMenuItem::services(app_handle, None),
|
||||
&PredefinedMenuItem::separator(app_handle),
|
||||
&PredefinedMenuItem::hide(app_handle, None),
|
||||
&PredefinedMenuItem::hide_others(app_handle, None),
|
||||
&PredefinedMenuItem::separator(app_handle),
|
||||
&PredefinedMenuItem::quit(app_handle, None),
|
||||
],
|
||||
)?,
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
)))]
|
||||
&Submenu::with_items(
|
||||
app_handle,
|
||||
"File",
|
||||
true,
|
||||
&[
|
||||
&PredefinedMenuItem::close_window(app_handle, None),
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
&PredefinedMenuItem::quit(app_handle, None),
|
||||
],
|
||||
)?,
|
||||
&Submenu::with_items(
|
||||
app_handle,
|
||||
"Edit",
|
||||
true,
|
||||
&[
|
||||
&PredefinedMenuItem::undo(app_handle, None),
|
||||
&PredefinedMenuItem::redo(app_handle, None),
|
||||
&PredefinedMenuItem::separator(app_handle),
|
||||
&PredefinedMenuItem::cut(app_handle, None),
|
||||
&PredefinedMenuItem::copy(app_handle, None),
|
||||
&PredefinedMenuItem::paste(app_handle, None),
|
||||
&PredefinedMenuItem::select_all(app_handle, None),
|
||||
],
|
||||
)?,
|
||||
#[cfg(target_os = "macos")]
|
||||
&Submenu::with_items(
|
||||
app_handle,
|
||||
"View",
|
||||
true,
|
||||
&[&PredefinedMenuItem::fullscreen(app_handle, None)],
|
||||
)?,
|
||||
&window_menu,
|
||||
&help_menu,
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(menu)
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &muda::Menu {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// The application handle associated with this type.
|
||||
pub fn app_handle(&self) -> &AppHandle<R> {
|
||||
&self.app_handle
|
||||
}
|
||||
|
||||
/// Returns a unique identifier associated with this menu.
|
||||
pub fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Add a menu item to the end of this menu.
|
||||
///
|
||||
/// ## Platform-spcific:
|
||||
///
|
||||
/// - **macOS:** Only [`Submenu`] can be added to the menu.
|
||||
///
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
|
||||
let kind = item.kind();
|
||||
run_main_thread!(self, |self_: Self| self_.inner.append(kind.inner().inner()))?
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add menu items to the end of this menu. It calls [`Menu::append`] in a loop internally.
|
||||
///
|
||||
/// ## Platform-spcific:
|
||||
///
|
||||
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||
///
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub fn append_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {
|
||||
for item in items {
|
||||
self.append(*item)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a menu item to the beginning of this menu.
|
||||
///
|
||||
/// ## Platform-spcific:
|
||||
///
|
||||
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||
///
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub fn prepend(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
|
||||
let kind = item.kind();
|
||||
run_main_thread!(self, |self_: Self| self_
|
||||
.inner
|
||||
.prepend(kind.inner().inner()))?
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add menu items to the beginning of this menu. It calls [`Menu::insert_items`] with position of `0` internally.
|
||||
///
|
||||
/// ## Platform-spcific:
|
||||
///
|
||||
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||
///
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub fn prepend_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {
|
||||
self.insert_items(items, 0)
|
||||
}
|
||||
|
||||
/// Insert a menu item at the specified `postion` in the menu.
|
||||
///
|
||||
/// ## Platform-spcific:
|
||||
///
|
||||
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||
///
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub fn insert(&self, item: &dyn IsMenuItem<R>, position: usize) -> crate::Result<()> {
|
||||
let kind = item.kind();
|
||||
run_main_thread!(self, |self_: Self| self_
|
||||
.inner
|
||||
.insert(kind.inner().inner(), position))?
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Insert menu items at the specified `postion` in the menu.
|
||||
///
|
||||
/// ## Platform-spcific:
|
||||
///
|
||||
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||
///
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub fn insert_items(&self, items: &[&dyn IsMenuItem<R>], position: usize) -> crate::Result<()> {
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
self.insert(*item, position + i)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a menu item from this menu.
|
||||
pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
|
||||
let kind = item.kind();
|
||||
run_main_thread!(self, |self_: Self| self_.inner.remove(kind.inner().inner()))?
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Retrieves the menu item matching the given identifier.
|
||||
pub fn get<'a, I>(&self, id: &'a I) -> Option<MenuItemKind<R>>
|
||||
where
|
||||
I: ?Sized,
|
||||
MenuId: PartialEq<&'a I>,
|
||||
{
|
||||
self
|
||||
.items()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.find(|i| i.id() == &id)
|
||||
}
|
||||
|
||||
/// Returns a list of menu items that has been added to this menu.
|
||||
pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {
|
||||
let handle = self.app_handle.clone();
|
||||
run_main_thread!(self, |self_: Self| self_
|
||||
.inner
|
||||
.items()
|
||||
.into_iter()
|
||||
.map(|i| match i {
|
||||
muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
}),
|
||||
muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
}),
|
||||
muda::MenuItemKind::Predefined(i) => {
|
||||
super::MenuItemKind::Predefined(super::PredefinedMenuItem {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
})
|
||||
}
|
||||
muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
}),
|
||||
muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
}),
|
||||
})
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
}
|
251
core/tauri/src/menu/mod.rs
Normal file
251
core/tauri/src/menu/mod.rs
Normal file
@ -0,0 +1,251 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#![cfg(desktop)]
|
||||
|
||||
//! Menu types and utility functions
|
||||
|
||||
// TODO(muda-migration): figure out js events
|
||||
|
||||
mod builders;
|
||||
mod check;
|
||||
mod icon;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod menu;
|
||||
mod normal;
|
||||
mod predefined;
|
||||
mod submenu;
|
||||
pub use builders::*;
|
||||
pub use check::CheckMenuItem;
|
||||
pub use icon::IconMenuItem;
|
||||
pub use menu::{Menu, HELP_SUBMENU_ID, WINDOW_SUBMENU_ID};
|
||||
pub use normal::MenuItem;
|
||||
pub use predefined::PredefinedMenuItem;
|
||||
pub use submenu::Submenu;
|
||||
|
||||
use crate::Runtime;
|
||||
pub use muda::{AboutMetadata, MenuEvent, MenuId, NativeIcon};
|
||||
|
||||
/// An enumeration of all menu item kinds that could be added to
|
||||
/// a [`Menu`] or [`Submenu`]
|
||||
pub enum MenuItemKind<R: Runtime> {
|
||||
/// Normal menu item
|
||||
MenuItem(MenuItem<R>),
|
||||
/// Submenu menu item
|
||||
Submenu(Submenu<R>),
|
||||
/// Predefined menu item
|
||||
Predefined(PredefinedMenuItem<R>),
|
||||
/// Check menu item
|
||||
Check(CheckMenuItem<R>),
|
||||
/// Icon menu item
|
||||
Icon(IconMenuItem<R>),
|
||||
}
|
||||
|
||||
impl<R: Runtime> MenuItemKind<R> {
|
||||
/// Returns a unique identifier associated with this menu item.
|
||||
pub fn id(&self) -> &MenuId {
|
||||
match self {
|
||||
MenuItemKind::MenuItem(i) => i.id(),
|
||||
MenuItemKind::Submenu(i) => i.id(),
|
||||
MenuItemKind::Predefined(i) => i.id(),
|
||||
MenuItemKind::Check(i) => i.id(),
|
||||
MenuItemKind::Icon(i) => i.id(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &dyn IsMenuItem<R> {
|
||||
match self {
|
||||
MenuItemKind::MenuItem(i) => i,
|
||||
MenuItemKind::Submenu(i) => i,
|
||||
MenuItemKind::Predefined(i) => i,
|
||||
MenuItemKind::Check(i) => i,
|
||||
MenuItemKind::Icon(i) => i,
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`MenuItem`], and returns `None` if it wasn't.
|
||||
pub fn as_menuitem(&self) -> Option<&MenuItem<R>> {
|
||||
match self {
|
||||
MenuItemKind::MenuItem(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`MenuItem`], and panics if it wasn't.
|
||||
pub fn as_menuitem_unchecked(&self) -> &MenuItem<R> {
|
||||
match self {
|
||||
MenuItemKind::MenuItem(i) => i,
|
||||
_ => panic!("Not a MenuItem"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`Submenu`], and returns `None` if it wasn't.
|
||||
pub fn as_submenu(&self) -> Option<&Submenu<R>> {
|
||||
match self {
|
||||
MenuItemKind::Submenu(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`Submenu`], and panics if it wasn't.
|
||||
pub fn as_submenu_unchecked(&self) -> &Submenu<R> {
|
||||
match self {
|
||||
MenuItemKind::Submenu(i) => i,
|
||||
_ => panic!("Not a Submenu"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`PredefinedMenuItem`], and returns `None` if it wasn't.
|
||||
pub fn as_predefined_menuitem(&self) -> Option<&PredefinedMenuItem<R>> {
|
||||
match self {
|
||||
MenuItemKind::Predefined(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`PredefinedMenuItem`], and panics if it wasn't.
|
||||
pub fn as_predefined_menuitem_unchecked(&self) -> &PredefinedMenuItem<R> {
|
||||
match self {
|
||||
MenuItemKind::Predefined(i) => i,
|
||||
_ => panic!("Not a PredefinedMenuItem"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`CheckMenuItem`], and returns `None` if it wasn't.
|
||||
pub fn as_check_menuitem(&self) -> Option<&CheckMenuItem<R>> {
|
||||
match self {
|
||||
MenuItemKind::Check(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`CheckMenuItem`], and panics if it wasn't.
|
||||
pub fn as_check_menuitem_unchecked(&self) -> &CheckMenuItem<R> {
|
||||
match self {
|
||||
MenuItemKind::Check(i) => i,
|
||||
_ => panic!("Not a CheckMenuItem"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`IconMenuItem`], and returns `None` if it wasn't.
|
||||
pub fn as_icon_menuitem(&self) -> Option<&IconMenuItem<R>> {
|
||||
match self {
|
||||
MenuItemKind::Icon(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts this item to a [`IconMenuItem`], and panics if it wasn't.
|
||||
pub fn as_icon_menuitem_unchecked(&self) -> &IconMenuItem<R> {
|
||||
match self {
|
||||
MenuItemKind::Icon(i) => i,
|
||||
_ => panic!("Not an IconMenuItem"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for MenuItemKind<R> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::MenuItem(i) => Self::MenuItem(i.clone()),
|
||||
Self::Submenu(i) => Self::Submenu(i.clone()),
|
||||
Self::Predefined(i) => Self::Predefined(i.clone()),
|
||||
Self::Check(i) => Self::Check(i.clone()),
|
||||
Self::Icon(i) => Self::Icon(i.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> sealed::IsMenuItemBase for MenuItemKind<R> {
|
||||
fn inner(&self) -> &dyn muda::IsMenuItem {
|
||||
self.inner().inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> IsMenuItem<R> for MenuItemKind<R> {
|
||||
fn kind(&self) -> MenuItemKind<R> {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn id(&self) -> &MenuId {
|
||||
self.id()
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait that defines a generic item in a menu, which may be one of [`MenuItemKind`]
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This trait is ONLY meant to be implemented internally by the crate.
|
||||
pub trait IsMenuItem<R: Runtime>: sealed::IsMenuItemBase {
|
||||
/// Returns the kind of this menu item.
|
||||
fn kind(&self) -> MenuItemKind<R>;
|
||||
|
||||
/// Returns a unique identifier associated with this menu.
|
||||
fn id(&self) -> &MenuId;
|
||||
}
|
||||
|
||||
/// A helper trait with methods to help creating a context menu.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This trait is ONLY meant to be implemented internally by the crate.
|
||||
pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync {
|
||||
/// Popup this menu as a context menu on the specified window at the cursor position.
|
||||
fn popup<R: crate::Runtime>(&self, window: crate::Window<R>) -> crate::Result<()>;
|
||||
|
||||
/// Popup this menu as a context menu on the specified window at the specified position.
|
||||
///
|
||||
/// The position is relative to the window's top-left corner.
|
||||
fn popup_at<R: crate::Runtime, P: Into<crate::Position>>(
|
||||
&self,
|
||||
window: crate::Window<R>,
|
||||
position: P,
|
||||
) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
|
||||
pub trait IsMenuItemBase {
|
||||
fn inner(&self) -> &dyn muda::IsMenuItem;
|
||||
}
|
||||
|
||||
pub trait ContextMenuBase {
|
||||
fn inner(&self) -> &dyn muda::ContextMenu;
|
||||
fn inner_owned(&self) -> Box<dyn muda::ContextMenu>;
|
||||
fn popup_inner<R: crate::Runtime, P: Into<crate::Position>>(
|
||||
&self,
|
||||
window: crate::Window<R>,
|
||||
position: Option<P>,
|
||||
) -> crate::Result<()>;
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<crate::Icon> for muda::Icon {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(value: crate::Icon) -> Result<Self, Self::Error> {
|
||||
let value: crate::runtime::Icon = value.try_into()?;
|
||||
muda::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_logical_position<P: crate::Pixel>(
|
||||
p: crate::LogicalPosition<P>,
|
||||
) -> muda::LogicalPosition<P> {
|
||||
muda::LogicalPosition { x: p.x, y: p.y }
|
||||
}
|
||||
|
||||
pub(crate) fn into_physical_position<P: crate::Pixel>(
|
||||
p: crate::PhysicalPosition<P>,
|
||||
) -> muda::PhysicalPosition<P> {
|
||||
muda::PhysicalPosition { x: p.x, y: p.y }
|
||||
}
|
||||
|
||||
pub(crate) fn into_position(p: crate::Position) -> muda::Position {
|
||||
match p {
|
||||
crate::Position::Physical(p) => muda::Position::Physical(into_physical_position(p)),
|
||||
crate::Position::Logical(p) => muda::Position::Logical(into_logical_position(p)),
|
||||
}
|
||||
}
|
134
core/tauri/src/menu/normal.rs
Normal file
134
core/tauri/src/menu/normal.rs
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime};
|
||||
|
||||
/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
|
||||
///
|
||||
/// [`Menu`]: super::Menu
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub struct MenuItem<R: Runtime> {
|
||||
pub(crate) id: MenuId,
|
||||
pub(crate) inner: muda::MenuItem,
|
||||
pub(crate) app_handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for MenuItem<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id.clone(),
|
||||
inner: self.inner.clone(),
|
||||
app_handle: self.app_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// We make sure it always runs on the main thread.
|
||||
unsafe impl<R: Runtime> Sync for MenuItem<R> {}
|
||||
unsafe impl<R: Runtime> Send for MenuItem<R> {}
|
||||
|
||||
impl<R: Runtime> super::sealed::IsMenuItemBase for MenuItem<R> {
|
||||
fn inner(&self) -> &dyn muda::IsMenuItem {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> super::IsMenuItem<R> for MenuItem<R> {
|
||||
fn kind(&self) -> super::MenuItemKind<R> {
|
||||
super::MenuItemKind::MenuItem(self.clone())
|
||||
}
|
||||
|
||||
fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> MenuItem<R> {
|
||||
/// Create a new menu item.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn new<M: Manager<R>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
acccelerator: Option<S>,
|
||||
) -> Self {
|
||||
let item = muda::MenuItem::new(
|
||||
text,
|
||||
enabled,
|
||||
acccelerator.and_then(|s| s.as_ref().parse().ok()),
|
||||
);
|
||||
Self {
|
||||
id: item.id().clone(),
|
||||
inner: item,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new menu item with the specified id.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn with_id<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
id: I,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
acccelerator: Option<S>,
|
||||
) -> Self {
|
||||
let item = muda::MenuItem::with_id(
|
||||
id,
|
||||
text,
|
||||
enabled,
|
||||
acccelerator.and_then(|s| s.as_ref().parse().ok()),
|
||||
);
|
||||
Self {
|
||||
id: item.id().clone(),
|
||||
inner: item,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The application handle associated with this type.
|
||||
pub fn app_handle(&self) -> &AppHandle<R> {
|
||||
&self.app_handle
|
||||
}
|
||||
|
||||
/// Returns a unique identifier associated with this menu item.
|
||||
pub fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Get the text for this menu item.
|
||||
pub fn text(&self) -> crate::Result<String> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.text())
|
||||
}
|
||||
|
||||
/// Set the text for this menu item. `text` could optionally contain
|
||||
/// an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
|
||||
let text = text.as_ref().to_string();
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
|
||||
}
|
||||
|
||||
/// Get whether this menu item is enabled or not.
|
||||
pub fn is_enabled(&self) -> crate::Result<bool> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.is_enabled())
|
||||
}
|
||||
|
||||
/// Enable or disable this menu item.
|
||||
pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled))
|
||||
}
|
||||
|
||||
/// Set this menu item accelerator.
|
||||
pub fn set_accelerator<S: AsRef<str>>(&self, acccelerator: Option<S>) -> crate::Result<()> {
|
||||
let accel = acccelerator.and_then(|s| s.as_ref().parse().ok());
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into)
|
||||
}
|
||||
}
|
287
core/tauri/src/menu/predefined.rs
Normal file
287
core/tauri/src/menu/predefined.rs
Normal file
@ -0,0 +1,287 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::AboutMetadata;
|
||||
use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime};
|
||||
|
||||
/// A predefined (native) menu item which has a predfined behavior by the OS or by this crate.
|
||||
pub struct PredefinedMenuItem<R: Runtime> {
|
||||
pub(crate) id: MenuId,
|
||||
pub(crate) inner: muda::PredefinedMenuItem,
|
||||
pub(crate) app_handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for PredefinedMenuItem<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id.clone(),
|
||||
inner: self.inner.clone(),
|
||||
app_handle: self.app_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// We make sure it always runs on the main thread.
|
||||
unsafe impl<R: Runtime> Sync for PredefinedMenuItem<R> {}
|
||||
unsafe impl<R: Runtime> Send for PredefinedMenuItem<R> {}
|
||||
|
||||
impl<R: Runtime> super::sealed::IsMenuItemBase for PredefinedMenuItem<R> {
|
||||
fn inner(&self) -> &dyn muda::IsMenuItem {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> super::IsMenuItem<R> for PredefinedMenuItem<R> {
|
||||
fn kind(&self) -> super::MenuItemKind<R> {
|
||||
super::MenuItemKind::Predefined(self.clone())
|
||||
}
|
||||
|
||||
fn id(&self) -> &MenuId {
|
||||
self.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> PredefinedMenuItem<R> {
|
||||
/// Separator menu item
|
||||
pub fn separator<M: Manager<R>>(manager: &M) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::separator();
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy menu item
|
||||
pub fn copy<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::copy(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cut menu item
|
||||
pub fn cut<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::cut(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Paste menu item
|
||||
pub fn paste<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::paste(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// SelectAll menu item
|
||||
pub fn select_all<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::select_all(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Undo menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn undo<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::undo(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
/// Redo menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn redo<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::redo(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimize window menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn minimize<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::minimize(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximize window menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn maximize<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::maximize(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fullscreen menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn fullscreen<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::fullscreen(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Hide window menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn hide<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::hide(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Hide other windows menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn hide_others<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::hide_others(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Show all app windows menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn show_all<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::show_all(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Close window menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn close_window<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::show_all(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Quit app menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn quit<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::quit(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// About app menu item
|
||||
pub fn about<M: Manager<R>>(
|
||||
manager: &M,
|
||||
text: Option<&str>,
|
||||
metadata: Option<AboutMetadata>,
|
||||
) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::about(text, metadata);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Services menu item
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux:** Unsupported.
|
||||
pub fn services<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
|
||||
let inner = muda::PredefinedMenuItem::services(text);
|
||||
Self {
|
||||
id: inner.id().clone(),
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a unique identifier associated with this menu item.
|
||||
pub fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Get the text for this menu item.
|
||||
pub fn text(&self) -> crate::Result<String> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.text())
|
||||
}
|
||||
|
||||
/// Set the text for this menu item. `text` could optionally contain
|
||||
/// an `&` before a character to assign this character as the mnemonic
|
||||
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
|
||||
let text = text.as_ref().to_string();
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
|
||||
}
|
||||
|
||||
/// The application handle associated with this type.
|
||||
pub fn app_handle(&self) -> &AppHandle<R> {
|
||||
&self.app_handle
|
||||
}
|
||||
}
|
328
core/tauri/src/menu/submenu.rs
Normal file
328
core/tauri/src/menu/submenu.rs
Normal file
@ -0,0 +1,328 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::{sealed::ContextMenuBase, IsMenuItem, MenuItemKind};
|
||||
use crate::{run_main_thread, AppHandle, Manager, Position, Runtime, Window};
|
||||
use muda::{ContextMenu, MenuId};
|
||||
|
||||
/// A type that is a submenu inside a [`Menu`] or [`Submenu`]
|
||||
///
|
||||
/// [`Menu`]: super::Menu
|
||||
/// [`Submenu`]: super::Submenu
|
||||
pub struct Submenu<R: Runtime> {
|
||||
pub(crate) id: MenuId,
|
||||
pub(crate) inner: muda::Submenu,
|
||||
pub(crate) app_handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// We make sure it always runs on the main thread.
|
||||
unsafe impl<R: Runtime> Sync for Submenu<R> {}
|
||||
unsafe impl<R: Runtime> Send for Submenu<R> {}
|
||||
|
||||
impl<R: Runtime> Clone for Submenu<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id.clone(),
|
||||
inner: self.inner.clone(),
|
||||
app_handle: self.app_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> super::sealed::IsMenuItemBase for Submenu<R> {
|
||||
fn inner(&self) -> &dyn muda::IsMenuItem {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> super::IsMenuItem<R> for Submenu<R> {
|
||||
fn kind(&self) -> super::MenuItemKind<R> {
|
||||
super::MenuItemKind::Submenu(self.clone())
|
||||
}
|
||||
|
||||
fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> super::ContextMenu for Submenu<R> {
|
||||
fn popup<T: Runtime>(&self, window: Window<T>) -> crate::Result<()> {
|
||||
self.popup_inner(window, None::<Position>)
|
||||
}
|
||||
|
||||
fn popup_at<T: Runtime, P: Into<Position>>(
|
||||
&self,
|
||||
window: Window<T>,
|
||||
position: P,
|
||||
) -> crate::Result<()> {
|
||||
self.popup_inner(window, Some(position))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> ContextMenuBase for Submenu<R> {
|
||||
fn popup_inner<T: Runtime, P: Into<crate::Position>>(
|
||||
&self,
|
||||
window: crate::Window<T>,
|
||||
position: Option<P>,
|
||||
) -> crate::Result<()> {
|
||||
let position = position.map(Into::into).map(super::into_position);
|
||||
run_main_thread!(self, move |self_: Self| {
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Ok(view) = window.ns_view() {
|
||||
self_
|
||||
.inner()
|
||||
.show_context_menu_for_nsview(view as _, position);
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
if let Ok(w) = window.gtk_window() {
|
||||
self_.inner().show_context_menu_for_gtk_window(&w, position);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if let Ok(hwnd) = window.hwnd() {
|
||||
self_.inner().show_context_menu_for_hwnd(hwnd.0, position)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn inner(&self) -> &dyn muda::ContextMenu {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
fn inner_owned(&self) -> Box<dyn muda::ContextMenu> {
|
||||
Box::new(self.clone().inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Submenu<R> {
|
||||
/// Creates a new submenu.
|
||||
pub fn new<M: Manager<R>, S: AsRef<str>>(manager: &M, text: S, enabled: bool) -> Self {
|
||||
let submenu = muda::Submenu::new(text, enabled);
|
||||
Self {
|
||||
id: submenu.id().clone(),
|
||||
inner: submenu,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new submenu with the specified id.
|
||||
pub fn with_id<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
id: I,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
) -> Self {
|
||||
let menu = muda::Submenu::with_id(id, text, enabled);
|
||||
Self {
|
||||
id: menu.id().clone(),
|
||||
inner: menu,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new menu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally.
|
||||
pub fn with_items<M: Manager<R>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
items: &[&dyn IsMenuItem<R>],
|
||||
) -> crate::Result<Self> {
|
||||
let menu = Self::new(manager, text, enabled);
|
||||
menu.append_items(items)?;
|
||||
Ok(menu)
|
||||
}
|
||||
|
||||
/// Creates a new menu with the specified id and given `items`.
|
||||
/// It calls [`Submenu::new`] and [`Submenu::append_items`] internally.
|
||||
pub fn with_id_and_items<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
|
||||
manager: &M,
|
||||
id: I,
|
||||
text: S,
|
||||
enabled: bool,
|
||||
items: &[&dyn IsMenuItem<R>],
|
||||
) -> crate::Result<Self> {
|
||||
let menu = Self::with_id(manager, id, text, enabled);
|
||||
menu.append_items(items)?;
|
||||
Ok(menu)
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &muda::Submenu {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// The application handle associated with this type.
|
||||
pub fn app_handle(&self) -> &AppHandle<R> {
|
||||
&self.app_handle
|
||||
}
|
||||
|
||||
/// Returns a unique identifier associated with this submenu.
|
||||
pub fn id(&self) -> &MenuId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Add a menu item to the end of this submenu.
|
||||
pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
|
||||
let kind = item.kind();
|
||||
run_main_thread!(self, |self_: Self| self_.inner.append(kind.inner().inner()))?
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add menu items to the end of this submenu. It calls [`Submenu::append`] in a loop internally.
|
||||
pub fn append_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {
|
||||
for item in items {
|
||||
self.append(*item)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a menu item to the beginning of this submenu.
|
||||
pub fn prepend(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
|
||||
let kind = item.kind();
|
||||
run_main_thread!(self, |self_: Self| {
|
||||
self_.inner.prepend(kind.inner().inner())
|
||||
})?
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add menu items to the beginning of this submenu. It calls [`Submenu::insert_items`] with position of `0` internally.
|
||||
pub fn prepend_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {
|
||||
self.insert_items(items, 0)
|
||||
}
|
||||
|
||||
/// Insert a menu item at the specified `postion` in this submenu.
|
||||
pub fn insert(&self, item: &dyn IsMenuItem<R>, position: usize) -> crate::Result<()> {
|
||||
let kind = item.kind();
|
||||
run_main_thread!(self, |self_: Self| {
|
||||
self_.inner.insert(kind.inner().inner(), position)
|
||||
})?
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Insert menu items at the specified `postion` in this submenu.
|
||||
pub fn insert_items(&self, items: &[&dyn IsMenuItem<R>], position: usize) -> crate::Result<()> {
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
self.insert(*item, position + i)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a menu item from this submenu.
|
||||
pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
|
||||
let kind = item.kind();
|
||||
run_main_thread!(self, |self_: Self| self_.inner.remove(kind.inner().inner()))?
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns a list of menu items that has been added to this submenu.
|
||||
pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {
|
||||
let handle = self.app_handle.clone();
|
||||
run_main_thread!(self, |self_: Self| {
|
||||
self_
|
||||
.inner
|
||||
.items()
|
||||
.into_iter()
|
||||
.map(|i| match i {
|
||||
muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
}),
|
||||
muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
}),
|
||||
muda::MenuItemKind::Predefined(i) => {
|
||||
super::MenuItemKind::Predefined(super::PredefinedMenuItem {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
})
|
||||
}
|
||||
muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
}),
|
||||
muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem {
|
||||
id: i.id().clone(),
|
||||
inner: i,
|
||||
app_handle: handle.clone(),
|
||||
}),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the text for this submenu.
|
||||
pub fn text(&self) -> crate::Result<String> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.text())
|
||||
}
|
||||
|
||||
/// Set the text for this submenu. `text` could optionally contain
|
||||
/// an `&` before a character to assign this character as the mnemonic
|
||||
/// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`.
|
||||
pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
|
||||
let text = text.as_ref().to_string();
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
|
||||
}
|
||||
|
||||
/// Get whether this submenu is enabled or not.
|
||||
pub fn is_enabled(&self) -> crate::Result<bool> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.is_enabled())
|
||||
}
|
||||
|
||||
/// Enable or disable this submenu.
|
||||
pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled))
|
||||
}
|
||||
|
||||
/// Set this submenu as the Window menu for the application on macOS.
|
||||
///
|
||||
/// This will cause macOS to automatically add window-switching items and
|
||||
/// certain other items to the menu.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_as_windows_menu_for_nsapp(&self) -> crate::Result<()> {
|
||||
run_main_thread!(self, |self_: Self| self_
|
||||
.inner
|
||||
.set_as_windows_menu_for_nsapp())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set this submenu as the Help menu for the application on macOS.
|
||||
///
|
||||
/// This will cause macOS to automatically add a search box to the menu.
|
||||
///
|
||||
/// If no menu is set as the Help menu, macOS will automatically use any menu
|
||||
/// which has a title matching the localized word "Help".
|
||||
pub fn set_as_help_menu_for_nsapp(&self) -> crate::Result<()> {
|
||||
#[cfg(target_os = "macos")]
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_as_help_menu_for_nsapp())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieves the menu item matching the given identifier.
|
||||
pub fn get<'a, I>(&self, id: &'a I) -> Option<MenuItemKind<R>>
|
||||
where
|
||||
I: ?Sized,
|
||||
MenuId: PartialEq<&'a I>,
|
||||
{
|
||||
self
|
||||
.items()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.find(|i| i.id() == &id)
|
||||
}
|
||||
}
|
@ -194,7 +194,7 @@ impl<R: Runtime> PathResolver<R> {
|
||||
|
||||
/// Returns the path to the suggested directory for your app's config files.
|
||||
///
|
||||
/// Resolves to [`config_dir`]`/${bundle_identifier}`.
|
||||
/// Resolves to [`config_dir`](self.config_dir)`/${bundle_identifier}`.
|
||||
pub fn app_config_dir(&self) -> Result<PathBuf> {
|
||||
dirs_next::config_dir()
|
||||
.ok_or(Error::UnknownPath)
|
||||
@ -203,7 +203,7 @@ impl<R: Runtime> PathResolver<R> {
|
||||
|
||||
/// Returns the path to the suggested directory for your app's data files.
|
||||
///
|
||||
/// Resolves to [`data_dir`]`/${bundle_identifier}`.
|
||||
/// Resolves to [`data_dir`](self.data_dir)`/${bundle_identifier}`.
|
||||
pub fn app_data_dir(&self) -> Result<PathBuf> {
|
||||
dirs_next::data_dir()
|
||||
.ok_or(Error::UnknownPath)
|
||||
@ -212,7 +212,7 @@ impl<R: Runtime> PathResolver<R> {
|
||||
|
||||
/// Returns the path to the suggested directory for your app's local data files.
|
||||
///
|
||||
/// Resolves to [`local_data_dir`]`/${bundle_identifier}`.
|
||||
/// Resolves to [`local_data_dir`](self.local_data_dir)`/${bundle_identifier}`.
|
||||
pub fn app_local_data_dir(&self) -> Result<PathBuf> {
|
||||
dirs_next::data_local_dir()
|
||||
.ok_or(Error::UnknownPath)
|
||||
@ -221,7 +221,7 @@ impl<R: Runtime> PathResolver<R> {
|
||||
|
||||
/// Returns the path to the suggested directory for your app's cache files.
|
||||
///
|
||||
/// Resolves to [`cache_dir`]`/${bundle_identifier}`.
|
||||
/// Resolves to [`cache_dir`](self.cache_dir)`/${bundle_identifier}`.
|
||||
pub fn app_cache_dir(&self) -> Result<PathBuf> {
|
||||
dirs_next::cache_dir()
|
||||
.ok_or(Error::UnknownPath)
|
||||
@ -232,9 +232,9 @@ impl<R: Runtime> PathResolver<R> {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** Resolves to [`data_local_dir`]`/${bundle_identifier}/logs`.
|
||||
/// - **macOS:** Resolves to [`home_dir`]`/Library/Logs/${bundle_identifier}`
|
||||
/// - **Windows:** Resolves to [`data_local_dir`]`/${bundle_identifier}/logs`.
|
||||
/// - **Linux:** Resolves to [`data_local_dir`](self.data_local_dir)`/${bundle_identifier}/logs`.
|
||||
/// - **macOS:** Resolves to [`home_dir`](self.home_dir)`/Library/Logs/${bundle_identifier}`
|
||||
/// - **Windows:** Resolves to [`data_local_dir`](self.data_local_dir)`/${bundle_identifier}/logs`.
|
||||
pub fn app_log_dir(&self) -> Result<PathBuf> {
|
||||
#[cfg(target_os = "macos")]
|
||||
let path = dirs_next::home_dir().ok_or(Error::UnknownPath).map(|dir| {
|
||||
|
@ -65,7 +65,7 @@ impl<'de> Deserialize<'de> for SafePathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
/// A base directory to be used in [`resolve_directory`].
|
||||
/// A base directory for a path.
|
||||
///
|
||||
/// The base directory is the optional root of a file system operation.
|
||||
/// If informed by the API call, all paths will be relative to the path of the given directory.
|
||||
|
@ -60,29 +60,29 @@ pub(crate) fn register_channel(channel: Channel) {
|
||||
/// Glue between Rust and the Kotlin code that sends the plugin response back.
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn handle_android_plugin_response(
|
||||
env: jni::JNIEnv<'_>,
|
||||
env: &mut jni::JNIEnv<'_>,
|
||||
id: i32,
|
||||
success: jni::objects::JString<'_>,
|
||||
error: jni::objects::JString<'_>,
|
||||
) {
|
||||
let (payload, is_ok): (serde_json::Value, bool) = match (
|
||||
env
|
||||
.is_same_object(success, jni::objects::JObject::default())
|
||||
.is_same_object(&success, jni::objects::JObject::default())
|
||||
.unwrap_or_default(),
|
||||
env
|
||||
.is_same_object(error, jni::objects::JObject::default())
|
||||
.is_same_object(&error, jni::objects::JObject::default())
|
||||
.unwrap_or_default(),
|
||||
) {
|
||||
// both null
|
||||
(true, true) => (serde_json::Value::Null, true),
|
||||
// error null
|
||||
(false, true) => (
|
||||
serde_json::from_str(env.get_string(success).unwrap().to_str().unwrap()).unwrap(),
|
||||
serde_json::from_str(env.get_string(&success).unwrap().to_str().unwrap()).unwrap(),
|
||||
true,
|
||||
),
|
||||
// success null
|
||||
(true, false) => (
|
||||
serde_json::from_str(env.get_string(error).unwrap().to_str().unwrap()).unwrap(),
|
||||
serde_json::from_str(env.get_string(&error).unwrap().to_str().unwrap()).unwrap(),
|
||||
false,
|
||||
),
|
||||
// both are set - impossible in the Kotlin code
|
||||
@ -102,12 +102,12 @@ pub fn handle_android_plugin_response(
|
||||
/// Glue between Rust and the Kotlin code that sends the channel data.
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn send_channel_data(
|
||||
env: jni::JNIEnv<'_>,
|
||||
env: &mut jni::JNIEnv<'_>,
|
||||
channel_id: i64,
|
||||
data_str: jni::objects::JString<'_>,
|
||||
) {
|
||||
let data: serde_json::Value =
|
||||
serde_json::from_str(env.get_string(data_str).unwrap().to_str().unwrap()).unwrap();
|
||||
serde_json::from_str(env.get_string(&data_str).unwrap().to_str().unwrap()).unwrap();
|
||||
|
||||
if let Some(channel) = CHANNELS
|
||||
.get_or_init(Default::default)
|
||||
@ -196,24 +196,15 @@ impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
|
||||
) -> Result<PluginHandle<R>, PluginInvokeError> {
|
||||
use jni::{errors::Error as JniError, objects::JObject, JNIEnv};
|
||||
|
||||
fn initialize_plugin<'a, R: Runtime>(
|
||||
env: JNIEnv<'a>,
|
||||
activity: JObject<'a>,
|
||||
webview: JObject<'a>,
|
||||
fn initialize_plugin<R: Runtime>(
|
||||
env: &mut JNIEnv<'_>,
|
||||
activity: &JObject<'_>,
|
||||
webview: &JObject<'_>,
|
||||
runtime_handle: &R::Handle,
|
||||
plugin_name: &'static str,
|
||||
plugin_class: String,
|
||||
plugin_config: &serde_json::Value,
|
||||
) -> Result<(), JniError> {
|
||||
let plugin_manager = env
|
||||
.call_method(
|
||||
activity,
|
||||
"getPluginManager",
|
||||
"()Lapp/tauri/plugin/PluginManager;",
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
|
||||
// instantiate plugin
|
||||
let plugin_class = runtime_handle.find_class(env, activity, plugin_class)?;
|
||||
let plugin = env.new_object(
|
||||
@ -223,15 +214,28 @@ impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
|
||||
)?;
|
||||
|
||||
// load plugin
|
||||
|
||||
let plugin_manager = env
|
||||
.call_method(
|
||||
activity,
|
||||
"getPluginManager",
|
||||
"()Lapp/tauri/plugin/PluginManager;",
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
|
||||
let plugin_name = env.new_string(plugin_name)?;
|
||||
let config =
|
||||
crate::jni_helpers::to_jsobject::<R>(env, activity, &runtime_handle, plugin_config)?;
|
||||
env.call_method(
|
||||
plugin_manager,
|
||||
"load",
|
||||
"(Landroid/webkit/WebView;Ljava/lang/String;Lapp/tauri/plugin/Plugin;Lapp/tauri/plugin/JSObject;)V",
|
||||
&[
|
||||
webview.into(),
|
||||
env.new_string(plugin_name)?.into(),
|
||||
plugin.into(),
|
||||
crate::jni_helpers::to_jsobject::<R>(env, activity, runtime_handle, plugin_config)?
|
||||
(&plugin_name).into(),
|
||||
(&plugin).into(),
|
||||
config.borrow()
|
||||
],
|
||||
)?;
|
||||
|
||||
@ -393,11 +397,13 @@ pub(crate) fn run_command<
|
||||
plugin: &str,
|
||||
command: String,
|
||||
payload: &serde_json::Value,
|
||||
runtime_handle: &R::Handle,
|
||||
env: JNIEnv<'_>,
|
||||
activity: JObject<'_>,
|
||||
runtime_handle: R::Handle,
|
||||
env: &mut JNIEnv<'_>,
|
||||
activity: &JObject<'_>,
|
||||
) -> Result<(), JniError> {
|
||||
let data = crate::jni_helpers::to_jsobject::<R>(env, activity, runtime_handle, payload)?;
|
||||
let plugin = env.new_string(plugin)?;
|
||||
let command = env.new_string(&command)?;
|
||||
let data = crate::jni_helpers::to_jsobject::<R>(env, activity, &runtime_handle, payload)?;
|
||||
let plugin_manager = env
|
||||
.call_method(
|
||||
activity,
|
||||
@ -413,9 +419,9 @@ pub(crate) fn run_command<
|
||||
"(ILjava/lang/String;Ljava/lang/String;Lapp/tauri/plugin/JSObject;)V",
|
||||
&[
|
||||
id.into(),
|
||||
env.new_string(plugin)?.into(),
|
||||
env.new_string(&command)?.into(),
|
||||
data,
|
||||
(&plugin).into(),
|
||||
(&command).into(),
|
||||
data.borrow(),
|
||||
],
|
||||
)?;
|
||||
|
||||
@ -440,7 +446,7 @@ pub(crate) fn run_command<
|
||||
.insert(id, Box::new(handler.clone()));
|
||||
|
||||
handle.run_on_android_context(move |env, activity, _webview| {
|
||||
if let Err(e) = run::<R>(id, &plugin_name, command, &payload, &handle_, env, activity) {
|
||||
if let Err(e) = run::<R>(id, &plugin_name, command, &payload, handle_, env, activity) {
|
||||
handler(Err(e.to_string().into()));
|
||||
}
|
||||
});
|
||||
|
@ -6,21 +6,16 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use tauri_runtime::{
|
||||
menu::{Menu, MenuUpdate},
|
||||
monitor::Monitor,
|
||||
webview::{WindowBuilder, WindowBuilderBase},
|
||||
window::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
CursorIcon, DetachedWindow, MenuEvent, PendingWindow, WindowEvent,
|
||||
CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent,
|
||||
},
|
||||
DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result,
|
||||
RunEvent, Runtime, RuntimeHandle, UserAttentionType, UserEvent,
|
||||
};
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
use tauri_runtime::{
|
||||
menu::{SystemTrayMenu, TrayHandle},
|
||||
SystemTray, SystemTrayEvent, TrayId,
|
||||
RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri_utils::TitleBarStyle;
|
||||
use tauri_utils::{config::WindowConfig, Theme};
|
||||
@ -105,9 +100,10 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
|
||||
}
|
||||
|
||||
/// Create a new webview window.
|
||||
fn create_window(
|
||||
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
|
||||
&self,
|
||||
pending: PendingWindow<T, Self::Runtime>,
|
||||
_before_webview_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self::Runtime>> {
|
||||
let id = rand::random();
|
||||
self.context.windows.borrow_mut().insert(id, Window);
|
||||
@ -119,7 +115,6 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
|
||||
last_evaluated_script: Default::default(),
|
||||
url: Arc::new(Mutex::new(pending.url)),
|
||||
},
|
||||
menu_ids: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -128,17 +123,6 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
|
||||
self.context.send_message(Message::Task(Box::new(f)))
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "system-tray"))))]
|
||||
fn system_tray(
|
||||
&self,
|
||||
system_tray: SystemTray,
|
||||
) -> Result<<Self::Runtime as Runtime<T>>::TrayHandler> {
|
||||
Ok(MockTrayHandler {
|
||||
context: self.context.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
|
||||
#[cfg(target_os = "linux")]
|
||||
return raw_window_handle::RawDisplayHandle::Xlib(raw_window_handle::XlibDisplayHandle::empty());
|
||||
@ -177,8 +161,8 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
|
||||
#[cfg(target_os = "android")]
|
||||
fn find_class<'a>(
|
||||
&'a self,
|
||||
env: jni::JNIEnv<'a>,
|
||||
activity: jni::objects::JObject<'a>,
|
||||
env: &'a mut jni::JNIEnv<'a>,
|
||||
activity: &'a jni::objects::JObject<'a>,
|
||||
name: impl Into<String>,
|
||||
) -> std::result::Result<jni::objects::JClass<'a>, jni::errors::Error> {
|
||||
todo!()
|
||||
@ -187,9 +171,7 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
|
||||
#[cfg(target_os = "android")]
|
||||
fn run_on_android_context<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce(jni::JNIEnv<'_>, jni::objects::JObject<'_>, jni::objects::JObject<'_>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
@ -223,10 +205,6 @@ impl WindowBuilder for MockWindowBuilder {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn menu(self, menu: Menu) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
fn center(self) -> Self {
|
||||
self
|
||||
}
|
||||
@ -357,10 +335,6 @@ impl WindowBuilder for MockWindowBuilder {
|
||||
fn has_icon(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn get_menu(&self) -> Option<&Menu> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
@ -376,10 +350,6 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
Uuid::new_v4()
|
||||
}
|
||||
|
||||
fn on_menu_event<F: Fn(&MenuEvent) + Send + 'static>(&self, f: F) -> Uuid {
|
||||
Uuid::new_v4()
|
||||
}
|
||||
|
||||
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@ -474,10 +444,6 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
Ok(String::new())
|
||||
}
|
||||
|
||||
fn is_menu_visible(&self) -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn current_monitor(&self) -> Result<Option<Monitor>> {
|
||||
Ok(None)
|
||||
}
|
||||
@ -505,6 +471,17 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
fn default_vbox(&self) -> Result<gtk::Box> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn raw_window_handle(&self) -> Result<raw_window_handle::RawWindowHandle> {
|
||||
#[cfg(target_os = "linux")]
|
||||
return Ok(raw_window_handle::RawWindowHandle::Xlib(
|
||||
@ -534,9 +511,10 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_window(
|
||||
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
|
||||
&mut self,
|
||||
pending: PendingWindow<T, Self::Runtime>,
|
||||
_before_webview_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self::Runtime>> {
|
||||
let id = rand::random();
|
||||
self.context.windows.borrow_mut().insert(id, Window);
|
||||
@ -548,7 +526,6 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
last_evaluated_script: Default::default(),
|
||||
url: Arc::new(Mutex::new(pending.url)),
|
||||
},
|
||||
menu_ids: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -593,14 +570,6 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_menu(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hide_menu(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@ -698,46 +667,6 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
.replace(script.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_menu_item(&self, id: u16, update: MenuUpdate) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MockTrayHandler {
|
||||
context: RuntimeContext,
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
impl TrayHandle for MockTrayHandler {
|
||||
fn set_icon(&self, icon: Icon) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn set_menu(&self, menu: SystemTrayMenu) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_icon_as_template(&self, is_template: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_title(&self, title: &str) -> tauri_runtime::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tooltip(&self, tooltip: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -753,8 +682,6 @@ impl<T: UserEvent> EventLoopProxy<T> for EventProxy {
|
||||
pub struct MockRuntime {
|
||||
is_running: Arc<AtomicBool>,
|
||||
pub context: RuntimeContext,
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
tray_handler: MockTrayHandler,
|
||||
run_rx: Receiver<Message>,
|
||||
}
|
||||
|
||||
@ -770,10 +697,6 @@ impl MockRuntime {
|
||||
};
|
||||
Self {
|
||||
is_running,
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
tray_handler: MockTrayHandler {
|
||||
context: context.clone(),
|
||||
},
|
||||
context,
|
||||
run_rx: rx,
|
||||
}
|
||||
@ -783,16 +706,14 @@ impl MockRuntime {
|
||||
impl<T: UserEvent> Runtime<T> for MockRuntime {
|
||||
type Dispatcher = MockDispatcher;
|
||||
type Handle = MockRuntimeHandle;
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
type TrayHandler = MockTrayHandler;
|
||||
type EventLoopProxy = EventProxy;
|
||||
|
||||
fn new() -> Result<Self> {
|
||||
fn new(_args: RuntimeInitArgs) -> Result<Self> {
|
||||
Ok(Self::init())
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
fn new_any_thread() -> Result<Self> {
|
||||
fn new_any_thread(_args: RuntimeInitArgs) -> Result<Self> {
|
||||
Ok(Self::init())
|
||||
}
|
||||
|
||||
@ -806,7 +727,11 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_window(&self, pending: PendingWindow<T, Self>) -> Result<DetachedWindow<T, Self>> {
|
||||
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
|
||||
&self,
|
||||
pending: PendingWindow<T, Self>,
|
||||
_before_webview_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self>> {
|
||||
let id = rand::random();
|
||||
self.context.windows.borrow_mut().insert(id, Window);
|
||||
Ok(DetachedWindow {
|
||||
@ -817,20 +742,9 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
|
||||
last_evaluated_script: Default::default(),
|
||||
url: Arc::new(Mutex::new(pending.url)),
|
||||
},
|
||||
menu_ids: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
|
||||
fn system_tray(&self, system_tray: SystemTray) -> Result<Self::TrayHandler> {
|
||||
Ok(self.tray_handler.clone())
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
|
||||
fn on_system_tray_event<F: Fn(TrayId, &SystemTrayEvent) + Send + 'static>(&mut self, f: F) {}
|
||||
|
||||
fn primary_monitor(&self) -> Option<Monitor> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
|
||||
windows: Vec::new(),
|
||||
bundle: Default::default(),
|
||||
security: Default::default(),
|
||||
system_tray: None,
|
||||
tray_icon: None,
|
||||
macos_private_api: false,
|
||||
},
|
||||
build: Default::default(),
|
||||
@ -133,8 +133,8 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
|
||||
assets: Arc::new(assets),
|
||||
default_window_icon: None,
|
||||
app_icon: None,
|
||||
#[cfg(desktop)]
|
||||
system_tray_icon: None,
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
tray_icon: None,
|
||||
package_info: crate::PackageInfo {
|
||||
name: "test".into(),
|
||||
version: "0.1.0".parse().unwrap(),
|
||||
@ -163,7 +163,7 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn mock_builder() -> Builder<MockRuntime> {
|
||||
Builder::<MockRuntime>::new()
|
||||
Builder::<MockRuntime>::new().enable_macos_default_menu(false)
|
||||
}
|
||||
|
||||
/// Creates a new [`App`] for testing using the [`mock_context`] with a [`noop_assets`].
|
||||
|
351
core/tauri/src/tray.rs
Normal file
351
core/tauri/src/tray.rs
Normal file
@ -0,0 +1,351 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#![cfg(all(desktop, feature = "tray-icon"))]
|
||||
|
||||
//! Tray icon types and utility functions
|
||||
|
||||
use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener};
|
||||
use crate::menu::ContextMenu;
|
||||
use crate::menu::MenuEvent;
|
||||
use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime};
|
||||
use std::path::Path;
|
||||
pub use tray_icon::{ClickType, Rectangle, TrayIconEvent, TrayIconId};
|
||||
|
||||
// TODO(muda-migration): figure out js events
|
||||
|
||||
/// [`TrayIcon`] builder struct and associated methods.
|
||||
#[derive(Default)]
|
||||
pub struct TrayIconBuilder<R: Runtime> {
|
||||
on_menu_event: Option<GlobalMenuEventListener<AppHandle<R>>>,
|
||||
on_tray_event: Option<GlobalTrayIconEventListener<TrayIcon<R>>>,
|
||||
inner: tray_icon::TrayIconBuilder,
|
||||
}
|
||||
|
||||
impl<R: Runtime> TrayIconBuilder<R> {
|
||||
/// Creates a new tray icon builder.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
|
||||
/// Setting an empty [`Menu`](crate::menu::Menu) is enough.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: tray_icon::TrayIconBuilder::new(),
|
||||
on_menu_event: None,
|
||||
on_tray_event: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new tray icon builder with the specified id.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
|
||||
/// Setting an empty [`Menu`](crate::menu::Menu) is enough.
|
||||
pub fn with_id<I: Into<TrayIconId>>(id: I) -> Self {
|
||||
let mut builder = Self::new();
|
||||
builder.inner = builder.inner.with_id(id);
|
||||
builder
|
||||
}
|
||||
|
||||
/// Set the a menu for this tray icon.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content.
|
||||
pub fn menu<M: ContextMenu>(mut self, menu: &M) -> Self {
|
||||
self.inner = self.inner.with_menu(menu.inner_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set an icon for this tray icon.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
|
||||
/// Setting an empty [`Menu`](crate::menu::Menu) is enough.
|
||||
pub fn icon(mut self, icon: Icon) -> Self {
|
||||
let icon = icon.try_into().ok();
|
||||
if let Some(icon) = icon {
|
||||
self.inner = self.inner.with_icon(icon);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a tooltip for this tray icon.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn tooltip<S: AsRef<str>>(mut self, s: S) -> Self {
|
||||
self.inner = self.inner.with_tooltip(s);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the tray icon title.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** The title will not be shown unless there is an icon
|
||||
/// as well. The title is useful for numerical and other frequently
|
||||
/// updated information. In general, it shouldn't be shown unless a
|
||||
/// user requests it as it can take up a significant amount of space
|
||||
/// on the user's panel. This may not be shown in all visualizations.
|
||||
/// - **Windows:** Unsupported.
|
||||
pub fn title<S: AsRef<str>>(mut self, title: S) -> Self {
|
||||
self.inner = self.inner.with_title(title);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set tray icon temp dir path. **Linux only**.
|
||||
///
|
||||
/// On Linux, we need to write the icon to the disk and usually it will
|
||||
/// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
|
||||
pub fn temp_dir_path<P: AsRef<Path>>(mut self, s: P) -> Self {
|
||||
self.inner = self.inner.with_temp_dir_path(s);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
|
||||
pub fn icon_as_template(mut self, is_template: bool) -> Self {
|
||||
self.inner = self.inner.with_icon_as_template(is_template);
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to show the tray menu on left click or not, default is `true`. **macOS only**.
|
||||
pub fn menu_on_left_click(mut self, enable: bool) -> Self {
|
||||
self.inner = self.inner.with_menu_on_left_click(enable);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a handler for menu events.
|
||||
///
|
||||
/// Note that this handler is called for any menu event,
|
||||
/// whether it is coming from this window, another window or from the tray icon menu.
|
||||
pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Sync + Send + 'static>(
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.on_menu_event.replace(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a handler for this tray icon events.
|
||||
pub fn on_tray_event<F: Fn(&TrayIcon<R>, TrayIconEvent) + Sync + Send + 'static>(
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.on_tray_event.replace(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Access the unique id that will be assigned to the tray icon
|
||||
/// this builder will create.
|
||||
pub fn id(&self) -> &TrayIconId {
|
||||
self.inner.id()
|
||||
}
|
||||
|
||||
/// Builds and adds a new [`TrayIcon`] to the system tray.
|
||||
pub fn build<M: Manager<R>>(self, manager: &M) -> crate::Result<TrayIcon<R>> {
|
||||
let id = self.id().clone();
|
||||
let inner = self.inner.build()?;
|
||||
let icon = TrayIcon {
|
||||
id,
|
||||
inner,
|
||||
app_handle: manager.app_handle().clone(),
|
||||
};
|
||||
|
||||
icon.register(&icon.app_handle, self.on_menu_event, self.on_tray_event);
|
||||
|
||||
Ok(icon)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tray icon struct and associated methods.
|
||||
///
|
||||
/// This type is reference-counted and the icon is removed when the last instance is dropped.
|
||||
///
|
||||
/// See [TrayIconBuilder] to construct this type.
|
||||
pub struct TrayIcon<R: Runtime> {
|
||||
id: TrayIconId,
|
||||
inner: tray_icon::TrayIcon,
|
||||
app_handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for TrayIcon<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id.clone(),
|
||||
inner: self.inner.clone(),
|
||||
app_handle: self.app_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// We make sure it always runs on the main thread.
|
||||
unsafe impl<R: Runtime> Sync for TrayIcon<R> {}
|
||||
unsafe impl<R: Runtime> Send for TrayIcon<R> {}
|
||||
|
||||
impl<R: Runtime> TrayIcon<R> {
|
||||
fn register(
|
||||
&self,
|
||||
app_handle: &AppHandle<R>,
|
||||
on_menu_event: Option<GlobalMenuEventListener<AppHandle<R>>>,
|
||||
on_tray_event: Option<GlobalTrayIconEventListener<TrayIcon<R>>>,
|
||||
) {
|
||||
if let Some(handler) = on_menu_event {
|
||||
app_handle
|
||||
.manager
|
||||
.inner
|
||||
.menu_event_listeners
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(handler);
|
||||
}
|
||||
|
||||
if let Some(handler) = on_tray_event {
|
||||
app_handle
|
||||
.manager
|
||||
.inner
|
||||
.tray_event_listeners
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.id.clone(), handler);
|
||||
}
|
||||
|
||||
app_handle
|
||||
.manager
|
||||
.inner
|
||||
.tray_icons
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(self.clone());
|
||||
}
|
||||
|
||||
/// The application handle associated with this type.
|
||||
pub fn app_handle(&self) -> &AppHandle<R> {
|
||||
&self.app_handle
|
||||
}
|
||||
|
||||
/// Register a handler for menu events.
|
||||
///
|
||||
/// Note that this handler is called for any menu event,
|
||||
/// whether it is coming from this window, another window or from the tray icon menu.
|
||||
pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Sync + Send + 'static>(&self, f: F) {
|
||||
self
|
||||
.app_handle
|
||||
.manager
|
||||
.inner
|
||||
.menu_event_listeners
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(Box::new(f));
|
||||
}
|
||||
|
||||
/// Register a handler for this tray icon events.
|
||||
pub fn on_tray_event<F: Fn(&TrayIcon<R>, TrayIconEvent) + Sync + Send + 'static>(&self, f: F) {
|
||||
self
|
||||
.app_handle
|
||||
.manager
|
||||
.inner
|
||||
.tray_event_listeners
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.id.clone(), Box::new(f));
|
||||
}
|
||||
|
||||
/// Returns the id associated with this tray icon.
|
||||
pub fn id(&self) -> &TrayIconId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Set new tray icon. If `None` is provided, it will remove the icon.
|
||||
pub fn set_icon(&self, icon: Option<Icon>) -> crate::Result<()> {
|
||||
let icon = icon.and_then(|i| i.try_into().ok());
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_icon(icon))?.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Set new tray menu.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux**: once a menu is set it cannot be removed so `None` has no effect
|
||||
pub fn set_menu<M: ContextMenu + 'static>(&self, menu: Option<M>) -> crate::Result<()> {
|
||||
run_main_thread!(self, |self_: Self| self_
|
||||
.inner
|
||||
.set_menu(menu.map(|m| m.inner_owned())))
|
||||
}
|
||||
|
||||
/// Sets the tooltip for this tray icon.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** Unsupported
|
||||
pub fn set_tooltip<S: AsRef<str>>(&self, tooltip: Option<S>) -> crate::Result<()> {
|
||||
let s = tooltip.map(|s| s.as_ref().to_string());
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_tooltip(s))?.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Sets the tooltip for this tray icon.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Linux:** The title will not be shown unless there is an icon
|
||||
/// as well. The title is useful for numerical and other frequently
|
||||
/// updated information. In general, it shouldn't be shown unless a
|
||||
/// user requests it as it can take up a significant amount of space
|
||||
/// on the user's panel. This may not be shown in all visualizations.
|
||||
/// - **Windows:** Unsupported
|
||||
pub fn set_title<S: AsRef<str>>(&self, title: Option<S>) -> crate::Result<()> {
|
||||
let s = title.map(|s| s.as_ref().to_string());
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_title(s))
|
||||
}
|
||||
|
||||
/// Show or hide this tray icon
|
||||
pub fn set_visible(&self, visible: bool) -> crate::Result<()> {
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_visible(visible))?.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Sets the tray icon temp dir path. **Linux only**.
|
||||
///
|
||||
/// On Linux, we need to write the icon to the disk and usually it will
|
||||
/// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
|
||||
pub fn set_temp_dir_path<P: AsRef<Path>>(&self, path: Option<P>) -> crate::Result<()> {
|
||||
#[allow(unused)]
|
||||
let p = path.map(|p| p.as_ref().to_path_buf());
|
||||
#[cfg(target_os = "linux")]
|
||||
run_main_thread!(self, |self_: Self| self_.inner.set_temp_dir_path(p))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
|
||||
pub fn set_icon_as_template(&self, #[allow(unused)] is_template: bool) -> crate::Result<()> {
|
||||
#[cfg(target_os = "macos")]
|
||||
run_main_thread!(self, |self_: Self| self_
|
||||
.inner
|
||||
.set_icon_as_template(is_template))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Disable or enable showing the tray menu on left click. **macOS only**.
|
||||
pub fn set_show_menu_on_left_click(&self, #[allow(unused)] enable: bool) -> crate::Result<()> {
|
||||
#[cfg(target_os = "macos")]
|
||||
run_main_thread!(self, |self_: Self| self_
|
||||
.inner
|
||||
.set_show_menu_on_left_click(enable))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Icon> for tray_icon::Icon {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(value: Icon) -> Result<Self, Self::Error> {
|
||||
let value: crate::runtime::Icon = value.try_into()?;
|
||||
tray_icon::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into)
|
||||
}
|
||||
}
|
@ -4,10 +4,7 @@
|
||||
|
||||
//! The Tauri window types and functions.
|
||||
|
||||
pub(crate) mod menu;
|
||||
|
||||
use http::HeaderMap;
|
||||
pub use menu::{MenuEvent, MenuHandle};
|
||||
pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState};
|
||||
use url::Url;
|
||||
|
||||
@ -38,8 +35,8 @@ use crate::{
|
||||
};
|
||||
#[cfg(desktop)]
|
||||
use crate::{
|
||||
menu::{ContextMenu, Menu, MenuId},
|
||||
runtime::{
|
||||
menu::Menu,
|
||||
window::dpi::{Position, Size},
|
||||
UserAttentionType,
|
||||
},
|
||||
@ -125,9 +122,13 @@ pub struct WindowBuilder<'a, R: Runtime> {
|
||||
app_handle: AppHandle<R>,
|
||||
label: String,
|
||||
pub(crate) window_builder: <R::Dispatcher as Dispatch<EventLoopMessage>>::WindowBuilder,
|
||||
#[cfg(desktop)]
|
||||
pub(crate) menu: Option<Menu<R>>,
|
||||
pub(crate) webview_attributes: WebviewAttributes,
|
||||
web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
|
||||
navigation_handler: Option<Box<NavigationHandler>>,
|
||||
#[cfg(desktop)]
|
||||
on_menu_event: Option<crate::app::GlobalMenuEventListener<Window<R>>>,
|
||||
}
|
||||
|
||||
impl<'a, R: Runtime> fmt::Debug for WindowBuilder<'a, R> {
|
||||
@ -168,7 +169,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
|
||||
/// ```
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let handle = app.handle();
|
||||
/// let handle = app.handle().clone();
|
||||
/// std::thread::spawn(move || {
|
||||
/// let window = tauri::WindowBuilder::new(&handle, "label", tauri::WindowUrl::App("index.html".into()))
|
||||
/// .build()
|
||||
@ -192,16 +193,20 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
|
||||
/// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583
|
||||
pub fn new<M: Manager<R>, L: Into<String>>(manager: &'a M, label: L, url: WindowUrl) -> Self {
|
||||
let runtime = manager.runtime();
|
||||
let app_handle = manager.app_handle();
|
||||
let app_handle = manager.app_handle().clone();
|
||||
Self {
|
||||
manager: manager.manager().clone(),
|
||||
runtime,
|
||||
app_handle,
|
||||
label: label.into(),
|
||||
window_builder: <R::Dispatcher as Dispatch<EventLoopMessage>>::WindowBuilder::new(),
|
||||
#[cfg(desktop)]
|
||||
menu: None,
|
||||
webview_attributes: WebviewAttributes::new(url),
|
||||
web_resource_request_handler: None,
|
||||
navigation_handler: None,
|
||||
#[cfg(desktop)]
|
||||
on_menu_event: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,14 +237,18 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
|
||||
let builder = Self {
|
||||
manager: manager.manager().clone(),
|
||||
runtime: manager.runtime(),
|
||||
app_handle: manager.app_handle(),
|
||||
app_handle: manager.app_handle().clone(),
|
||||
label: config.label.clone(),
|
||||
webview_attributes: WebviewAttributes::from(&config),
|
||||
window_builder: <R::Dispatcher as Dispatch<EventLoopMessage>>::WindowBuilder::with_config(
|
||||
config,
|
||||
),
|
||||
web_resource_request_handler: None,
|
||||
#[cfg(desktop)]
|
||||
menu: None,
|
||||
navigation_handler: None,
|
||||
#[cfg(desktop)]
|
||||
on_menu_event: None,
|
||||
};
|
||||
|
||||
builder
|
||||
@ -317,6 +326,48 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Registers a global menu event listener.
|
||||
///
|
||||
/// Note that this handler is called for any menu event,
|
||||
/// whether it is coming from this window, another window or from the tray icon menu.
|
||||
///
|
||||
/// Also note that this handler will not be called if
|
||||
/// the window used to register it was closed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use tauri::menu::{Menu, Submenu, MenuItem};
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let handle = app.handle();
|
||||
/// let save_menu_item = MenuItem::new(handle, "Save", true, None);
|
||||
/// let menu = Menu::with_items(handle, &[
|
||||
/// &Submenu::with_items(handle, "File", true, &[
|
||||
/// &save_menu_item,
|
||||
/// ])?,
|
||||
/// ])?;
|
||||
/// let window = tauri::WindowBuilder::new(app, "editor", tauri::WindowUrl::default())
|
||||
/// .menu(menu)
|
||||
/// .on_menu_event(move |window, event| {
|
||||
/// if event.id == save_menu_item.id() {
|
||||
/// // save menu item
|
||||
/// }
|
||||
/// })
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[cfg(desktop)]
|
||||
pub fn on_menu_event<F: Fn(&Window<R>, crate::menu::MenuEvent) + Send + Sync + 'static>(
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.on_menu_event.replace(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new webview window.
|
||||
pub fn build(mut self) -> crate::Result<Window<R>> {
|
||||
let mut pending = PendingWindow::new(
|
||||
@ -331,13 +382,43 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
|
||||
let pending = self
|
||||
.manager
|
||||
.prepare_window(self.app_handle.clone(), pending, &labels)?;
|
||||
|
||||
#[cfg(desktop)]
|
||||
let window_menu = {
|
||||
let is_app_wide = self.menu.is_none();
|
||||
self
|
||||
.menu
|
||||
.or_else(|| self.app_handle.menu())
|
||||
.map(|menu| WindowMenu { is_app_wide, menu })
|
||||
};
|
||||
|
||||
#[cfg(desktop)]
|
||||
let handler = self
|
||||
.manager
|
||||
.prepare_window_menu_creation_handler(window_menu.as_ref());
|
||||
#[cfg(not(desktop))]
|
||||
#[allow(clippy::type_complexity)]
|
||||
let handler: Option<Box<dyn Fn(tauri_runtime::window::RawWindow<'_>) + Send>> = None;
|
||||
|
||||
let window_effects = pending.webview_attributes.window_effects.clone();
|
||||
let window = match &mut self.runtime {
|
||||
RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending),
|
||||
RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending),
|
||||
RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_window(pending),
|
||||
RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending, handler),
|
||||
RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending, handler),
|
||||
RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_window(pending, handler),
|
||||
}
|
||||
.map(|window| {
|
||||
self.manager.attach_window(
|
||||
self.app_handle.clone(),
|
||||
window,
|
||||
#[cfg(desktop)]
|
||||
window_menu,
|
||||
)
|
||||
})?;
|
||||
|
||||
#[cfg(desktop)]
|
||||
if let Some(handler) = self.on_menu_event {
|
||||
window.on_menu_event(handler);
|
||||
}
|
||||
.map(|window| self.manager.attach_window(self.app_handle.clone(), window))?;
|
||||
|
||||
if let Some(effects) = window_effects {
|
||||
crate::vibrancy::set_window_effects(&window, Some(effects))?;
|
||||
@ -365,8 +446,8 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
|
||||
impl<'a, R: Runtime> WindowBuilder<'a, R> {
|
||||
/// Sets the menu for the window.
|
||||
#[must_use]
|
||||
pub fn menu(mut self, menu: Menu) -> Self {
|
||||
self.window_builder = self.window_builder.menu(menu);
|
||||
pub fn menu(mut self, menu: Menu<R>) -> Self {
|
||||
self.menu.replace(menu);
|
||||
self
|
||||
}
|
||||
|
||||
@ -659,7 +740,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows**: If using decorations or shadows, you may want to try this workaround https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891
|
||||
/// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>
|
||||
/// - **Linux**: Unsupported
|
||||
pub fn effects(mut self, effects: WindowEffectsConfig) -> Self {
|
||||
self.webview_attributes = self.webview_attributes.window_effects(effects);
|
||||
@ -793,13 +874,20 @@ pub struct InvokeRequest {
|
||||
pub headers: HeaderMap,
|
||||
}
|
||||
|
||||
/// A wrapper struct to hold the window menu state
|
||||
/// and whether it is global per-app or specific to this window.
|
||||
#[cfg(desktop)]
|
||||
pub(crate) struct WindowMenu<R: Runtime> {
|
||||
pub(crate) is_app_wide: bool,
|
||||
pub(crate) menu: Menu<R>,
|
||||
}
|
||||
|
||||
// TODO: expand these docs since this is a pretty important type
|
||||
/// A webview window managed by Tauri.
|
||||
///
|
||||
/// This type also implements [`Manager`] which allows you to manage other windows attached to
|
||||
/// the same application.
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
#[derive(Debug)]
|
||||
pub struct Window<R: Runtime> {
|
||||
/// The webview window created by the runtime.
|
||||
pub(crate) window: DetachedWindow<EventLoopMessage, R>,
|
||||
@ -807,6 +895,20 @@ pub struct Window<R: Runtime> {
|
||||
manager: WindowManager<R>,
|
||||
pub(crate) app_handle: AppHandle<R>,
|
||||
js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<usize>>>>,
|
||||
// The menu set for this window
|
||||
#[cfg(desktop)]
|
||||
pub(crate) menu: Arc<Mutex<Option<WindowMenu<R>>>>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> std::fmt::Debug for Window<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Window")
|
||||
.field("window", &self.window)
|
||||
.field("manager", &self.manager)
|
||||
.field("app_handle", &self.app_handle)
|
||||
.field("js_event_listeners", &self.js_event_listeners)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<R: Runtime> raw_window_handle::HasRawWindowHandle for Window<R> {
|
||||
@ -822,6 +924,8 @@ impl<R: Runtime> Clone for Window<R> {
|
||||
manager: self.manager.clone(),
|
||||
app_handle: self.app_handle.clone(),
|
||||
js_event_listeners: self.js_event_listeners.clone(),
|
||||
#[cfg(desktop)]
|
||||
menu: self.menu.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -868,8 +972,8 @@ impl<R: Runtime> ManagerBase<R> for Window<R> {
|
||||
RuntimeOrDispatch::Dispatch(self.dispatcher())
|
||||
}
|
||||
|
||||
fn managed_app_handle(&self) -> AppHandle<R> {
|
||||
self.app_handle.clone()
|
||||
fn managed_app_handle(&self) -> &AppHandle<R> {
|
||||
&self.app_handle
|
||||
}
|
||||
}
|
||||
|
||||
@ -968,12 +1072,15 @@ impl<R: Runtime> Window<R> {
|
||||
manager: WindowManager<R>,
|
||||
window: DetachedWindow<EventLoopMessage, R>,
|
||||
app_handle: AppHandle<R>,
|
||||
#[cfg(desktop)] menu: Option<WindowMenu<R>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
manager,
|
||||
app_handle,
|
||||
js_event_listeners: Default::default(),
|
||||
#[cfg(desktop)]
|
||||
menu: Arc::new(Mutex::new(menu)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1015,20 +1122,6 @@ impl<R: Runtime> Window<R> {
|
||||
.on_window_event(move |event| f(&event.clone().into()));
|
||||
}
|
||||
|
||||
/// Registers a menu event listener.
|
||||
pub fn on_menu_event<F: Fn(MenuEvent) + Send + 'static>(&self, f: F) -> uuid::Uuid {
|
||||
let menu_ids = self.window.menu_ids.clone();
|
||||
self.window.dispatcher.on_menu_event(move |event| {
|
||||
let id = menu_ids
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&event.menu_item_id)
|
||||
.unwrap()
|
||||
.clone();
|
||||
f(MenuEvent { menu_item_id: id })
|
||||
})
|
||||
}
|
||||
|
||||
/// Executes a closure, providing it with the webview handle that is specific to the current platform.
|
||||
///
|
||||
/// The closure is executed on the main thread.
|
||||
@ -1094,16 +1187,270 @@ impl<R: Runtime> Window<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Window getters.
|
||||
/// Menu APIs
|
||||
#[cfg(desktop)]
|
||||
impl<R: Runtime> Window<R> {
|
||||
/// Gets a handle to the window menu.
|
||||
pub fn menu_handle(&self) -> MenuHandle<R> {
|
||||
MenuHandle {
|
||||
ids: self.window.menu_ids.clone(),
|
||||
dispatcher: self.dispatcher(),
|
||||
}
|
||||
/// Registers a global menu event listener.
|
||||
///
|
||||
/// Note that this handler is called for any menu event,
|
||||
/// whether it is coming from this window, another window or from the tray icon menu.
|
||||
///
|
||||
/// Also note that this handler will not be called if
|
||||
/// the window used to register it was closed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use tauri::menu::{Menu, Submenu, MenuItem};
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let handle = app.handle();
|
||||
/// let save_menu_item = MenuItem::new(handle, "Save", true, None);
|
||||
/// let menu = Menu::with_items(handle, &[
|
||||
/// &Submenu::with_items(handle, "File", true, &[
|
||||
/// &save_menu_item,
|
||||
/// ])?,
|
||||
/// ])?;
|
||||
/// let window = tauri::WindowBuilder::new(app, "editor", tauri::WindowUrl::default())
|
||||
/// .menu(menu)
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// window.on_menu_event(move |window, event| {
|
||||
/// if event.id == save_menu_item.id() {
|
||||
/// // save menu item
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn on_menu_event<F: Fn(&Window<R>, crate::menu::MenuEvent) + Send + Sync + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) {
|
||||
self
|
||||
.manager
|
||||
.inner
|
||||
.window_menu_event_listeners
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.label().to_string(), Box::new(f));
|
||||
}
|
||||
|
||||
pub(crate) fn menu_lock(&self) -> std::sync::MutexGuard<'_, Option<WindowMenu<R>>> {
|
||||
self.menu.lock().expect("poisoned window")
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "macos", allow(dead_code))]
|
||||
pub(crate) fn has_app_wide_menu(&self) -> bool {
|
||||
self
|
||||
.menu_lock()
|
||||
.as_ref()
|
||||
.map(|m| m.is_app_wide)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "macos", allow(dead_code))]
|
||||
pub(crate) fn is_menu_in_use<I: PartialEq<MenuId>>(&self, id: &I) -> bool {
|
||||
self
|
||||
.menu_lock()
|
||||
.as_ref()
|
||||
.map(|m| id.eq(m.menu.id()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns this window menu .
|
||||
pub fn menu(&self) -> Option<Menu<R>> {
|
||||
self.menu_lock().as_ref().map(|m| m.menu.clone())
|
||||
}
|
||||
|
||||
/// Sets the window menu and returns the previous one.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one
|
||||
/// window, if you need to set it, use [`AppHandle::set_menu`] instead.
|
||||
#[cfg_attr(target_os = "macos", allow(unused_variables))]
|
||||
pub fn set_menu(&self, menu: Menu<R>) -> crate::Result<Option<Menu<R>>> {
|
||||
let prev_menu = self.remove_menu()?;
|
||||
|
||||
self.manager.insert_menu_into_stash(&menu);
|
||||
|
||||
let window = self.clone();
|
||||
let menu_ = menu.clone();
|
||||
self.run_on_main_thread(move || {
|
||||
#[cfg(windows)]
|
||||
if let Ok(hwnd) = window.hwnd() {
|
||||
let _ = menu_.inner().init_for_hwnd(hwnd.0);
|
||||
}
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
if let (Ok(gtk_window), Ok(gtk_box)) = (window.gtk_window(), window.default_vbox()) {
|
||||
let _ = menu_
|
||||
.inner()
|
||||
.init_for_gtk_window(>k_window, Some(>k_box));
|
||||
}
|
||||
})?;
|
||||
|
||||
self.menu_lock().replace(WindowMenu {
|
||||
is_app_wide: false,
|
||||
menu,
|
||||
});
|
||||
|
||||
Ok(prev_menu)
|
||||
}
|
||||
|
||||
/// Removes the window menu and returns it.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one
|
||||
/// window, if you need to remove it, use [`AppHandle::remove_menu`] instead.
|
||||
pub fn remove_menu(&self) -> crate::Result<Option<Menu<R>>> {
|
||||
let current_menu = self.menu_lock().as_ref().map(|m| m.menu.clone());
|
||||
|
||||
// remove from the window
|
||||
#[cfg_attr(target_os = "macos", allow(unused_variables))]
|
||||
if let Some(menu) = current_menu {
|
||||
let window = self.clone();
|
||||
self.run_on_main_thread(move || {
|
||||
#[cfg(windows)]
|
||||
if let Ok(hwnd) = window.hwnd() {
|
||||
let _ = menu.inner().remove_for_hwnd(hwnd.0);
|
||||
}
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
if let Ok(gtk_window) = window.gtk_window() {
|
||||
let _ = menu.inner().remove_for_gtk_window(>k_window);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
let prev_menu = self.menu_lock().take().map(|m| m.menu);
|
||||
|
||||
self
|
||||
.manager
|
||||
.remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id()));
|
||||
|
||||
Ok(prev_menu)
|
||||
}
|
||||
|
||||
/// Hides the window menu.
|
||||
pub fn hide_menu(&self) -> crate::Result<()> {
|
||||
// remove from the window
|
||||
#[cfg_attr(target_os = "macos", allow(unused_variables))]
|
||||
if let Some(window_menu) = &*self.menu_lock() {
|
||||
let window = self.clone();
|
||||
let menu_ = window_menu.menu.clone();
|
||||
self.run_on_main_thread(move || {
|
||||
#[cfg(windows)]
|
||||
if let Ok(hwnd) = window.hwnd() {
|
||||
let _ = menu_.inner().hide_for_hwnd(hwnd.0);
|
||||
}
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
if let Ok(gtk_window) = window.gtk_window() {
|
||||
let _ = menu_.inner().hide_for_gtk_window(>k_window);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Shows the window menu.
|
||||
pub fn show_menu(&self) -> crate::Result<()> {
|
||||
// remove from the window
|
||||
#[cfg_attr(target_os = "macos", allow(unused_variables))]
|
||||
if let Some(window_menu) = &*self.menu_lock() {
|
||||
let window = self.clone();
|
||||
let menu_ = window_menu.menu.clone();
|
||||
self.run_on_main_thread(move || {
|
||||
#[cfg(windows)]
|
||||
if let Ok(hwnd) = window.hwnd() {
|
||||
let _ = menu_.inner().show_for_hwnd(hwnd.0);
|
||||
}
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
if let Ok(gtk_window) = window.gtk_window() {
|
||||
let _ = menu_.inner().show_for_gtk_window(>k_window);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Shows the window menu.
|
||||
pub fn is_menu_visible(&self) -> crate::Result<bool> {
|
||||
// remove from the window
|
||||
#[cfg_attr(target_os = "macos", allow(unused_variables))]
|
||||
if let Some(window_menu) = &*self.menu_lock() {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let window = self.clone();
|
||||
let menu_ = window_menu.menu.clone();
|
||||
self.run_on_main_thread(move || {
|
||||
#[cfg(windows)]
|
||||
if let Ok(hwnd) = window.hwnd() {
|
||||
let _ = tx.send(menu_.inner().is_visible_on_hwnd(hwnd.0));
|
||||
}
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
if let Ok(gtk_window) = window.gtk_window() {
|
||||
let _ = tx.send(menu_.inner().is_visible_on_gtk_window(>k_window));
|
||||
}
|
||||
})?;
|
||||
|
||||
return Ok(rx.recv().unwrap_or(false));
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Shows the specified menu as a context menu at the cursor position.
|
||||
pub fn popup_menu<M: ContextMenu>(&self, menu: &M) -> crate::Result<()> {
|
||||
menu.popup(self.clone())
|
||||
}
|
||||
|
||||
/// Shows the specified menu as a context menu at the specified position.
|
||||
///
|
||||
/// The position is relative to the window's top-left corner.
|
||||
pub fn popup_menu_at<M: ContextMenu, P: Into<Position>>(
|
||||
&self,
|
||||
menu: &M,
|
||||
position: P,
|
||||
) -> crate::Result<()> {
|
||||
menu.popup_at(self.clone(), position)
|
||||
}
|
||||
}
|
||||
|
||||
/// Window getters.
|
||||
impl<R: Runtime> Window<R> {
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
pub fn scale_factor(&self) -> crate::Result<f64> {
|
||||
self.window.dispatcher.scale_factor().map_err(Into::into)
|
||||
@ -1251,6 +1598,23 @@ impl<R: Runtime> Window<R> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the pointer to the content view of this window.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn ns_view(&self) -> crate::Result<*mut std::ffi::c_void> {
|
||||
self
|
||||
.window
|
||||
.dispatcher
|
||||
.raw_window_handle()
|
||||
.map_err(Into::into)
|
||||
.and_then(|handle| {
|
||||
if let raw_window_handle::RawWindowHandle::AppKit(h) = handle {
|
||||
Ok(h.ns_view)
|
||||
} else {
|
||||
Err(crate::Error::InvalidWindowHandle)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the native handle that is used by this window.
|
||||
#[cfg(windows)]
|
||||
pub fn hwnd(&self) -> crate::Result<HWND> {
|
||||
@ -1270,7 +1634,7 @@ impl<R: Runtime> Window<R> {
|
||||
|
||||
/// Returns the `ApplicationWindow` from gtk crate that is used by this window.
|
||||
///
|
||||
/// Note that this can only be used on the main thread.
|
||||
/// Note that this type can only be used on the main thread.
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
@ -1282,6 +1646,20 @@ impl<R: Runtime> Window<R> {
|
||||
self.window.dispatcher.gtk_window().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window.
|
||||
///
|
||||
/// Note that this type can only be used on the main thread.
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
pub fn default_vbox(&self) -> crate::Result<gtk::Box> {
|
||||
self.window.dispatcher.default_vbox().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the current window theme.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
@ -1486,7 +1864,7 @@ impl<R: Runtime> Window<R> {
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Windows**: If using decorations or shadows, you may want to try this workaround https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891
|
||||
/// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>
|
||||
/// - **Linux**: Unsupported
|
||||
pub fn set_effects<E: Into<Option<WindowEffectsConfig>>>(&self, effects: E) -> crate::Result<()> {
|
||||
let effects = effects.into();
|
||||
|
@ -1,155 +0,0 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{
|
||||
runtime::{
|
||||
menu::{MenuHash, MenuId, MenuIdRef, MenuUpdate},
|
||||
Dispatch,
|
||||
},
|
||||
Runtime,
|
||||
};
|
||||
|
||||
use tauri_macros::default_runtime;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
/// The window menu event.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MenuEvent {
|
||||
pub(crate) menu_item_id: MenuId,
|
||||
}
|
||||
|
||||
impl MenuEvent {
|
||||
/// The menu item id.
|
||||
pub fn menu_item_id(&self) -> MenuIdRef<'_> {
|
||||
&self.menu_item_id
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a system tray. Allows updating the context menu items.
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
#[derive(Debug)]
|
||||
pub struct MenuHandle<R: Runtime> {
|
||||
pub(crate) ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
|
||||
pub(crate) dispatcher: R::Dispatcher,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for MenuHandle<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
ids: self.ids.clone(),
|
||||
dispatcher: self.dispatcher.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a system tray menu item.
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
#[derive(Debug)]
|
||||
pub struct MenuItemHandle<R: Runtime> {
|
||||
id: u16,
|
||||
dispatcher: R::Dispatcher,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for MenuItemHandle<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
dispatcher: self.dispatcher.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> MenuHandle<R> {
|
||||
/// Gets a handle to the menu item that has the specified `id`.
|
||||
pub fn get_item(&self, id: MenuIdRef<'_>) -> MenuItemHandle<R> {
|
||||
let ids = self.ids.lock().unwrap();
|
||||
let iter = ids.iter();
|
||||
for (raw, item_id) in iter {
|
||||
if item_id == id {
|
||||
return MenuItemHandle {
|
||||
id: *raw,
|
||||
dispatcher: self.dispatcher.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
panic!("item id not found")
|
||||
}
|
||||
|
||||
/// Attempts to get a handle to the menu item that has the specified `id`, return an error if `id` is not found.
|
||||
pub fn try_get_item(&self, id: MenuIdRef<'_>) -> Option<MenuItemHandle<R>> {
|
||||
self
|
||||
.ids
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|i| i.1 == id)
|
||||
.map(|i| MenuItemHandle {
|
||||
id: *i.0,
|
||||
dispatcher: self.dispatcher.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Shows the menu.
|
||||
pub fn show(&self) -> crate::Result<()> {
|
||||
self.dispatcher.show_menu().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Hides the menu.
|
||||
pub fn hide(&self) -> crate::Result<()> {
|
||||
self.dispatcher.hide_menu().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Whether the menu is visible or not.
|
||||
pub fn is_visible(&self) -> crate::Result<bool> {
|
||||
self.dispatcher.is_menu_visible().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Toggles the menu visibility.
|
||||
pub fn toggle(&self) -> crate::Result<()> {
|
||||
if self.is_visible()? {
|
||||
self.hide()
|
||||
} else {
|
||||
self.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> MenuItemHandle<R> {
|
||||
/// Modifies the enabled state of the menu item.
|
||||
pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
|
||||
self
|
||||
.dispatcher
|
||||
.update_menu_item(self.id, MenuUpdate::SetEnabled(enabled))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Modifies the title (label) of the menu item.
|
||||
pub fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()> {
|
||||
self
|
||||
.dispatcher
|
||||
.update_menu_item(self.id, MenuUpdate::SetTitle(title.into()))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Modifies the selected state of the menu item.
|
||||
pub fn set_selected(&self, selected: bool) -> crate::Result<()> {
|
||||
self
|
||||
.dispatcher
|
||||
.update_menu_item(self.id, MenuUpdate::SetSelected(selected))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
|
||||
pub fn set_native_image(&self, image: crate::NativeImage) -> crate::Result<()> {
|
||||
self
|
||||
.dispatcher
|
||||
.update_menu_item(self.id, MenuUpdate::SetNativeImage(image))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
2
examples/api/dist/assets/index.css
vendored
2
examples/api/dist/assets/index.css
vendored
File diff suppressed because one or more lines are too long
18
examples/api/dist/assets/index.js
vendored
18
examples/api/dist/assets/index.js
vendored
File diff suppressed because one or more lines are too long
918
examples/api/src-tauri/Cargo.lock
generated
918
examples/api/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -40,7 +40,7 @@ features = [
|
||||
"icon-png",
|
||||
"isolation",
|
||||
"macos-private-api",
|
||||
"system-tray"
|
||||
"tray-icon"
|
||||
]
|
||||
|
||||
[dev-dependencies.tauri]
|
||||
|
@ -29,3 +29,37 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
|
||||
message: "message response".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, not(target_os = "macos")))]
|
||||
#[command]
|
||||
pub fn toggle_menu<R: tauri::Runtime>(window: tauri::Window<R>) {
|
||||
if window.is_menu_visible().unwrap_or_default() {
|
||||
let _ = window.hide_menu();
|
||||
} else {
|
||||
let _ = window.show_menu();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[command]
|
||||
pub fn toggle_menu<R: tauri::Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
app_menu: tauri::State<'_, crate::AppMenu<R>>,
|
||||
) {
|
||||
if let Some(menu) = app.remove_menu().unwrap() {
|
||||
app_menu.0.lock().unwrap().replace(menu);
|
||||
} else {
|
||||
app
|
||||
.set_menu(app_menu.0.lock().unwrap().clone().expect("no app menu"))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[command]
|
||||
pub fn popup_context_menu<R: tauri::Runtime>(
|
||||
window: tauri::Window<R>,
|
||||
popup_menu: tauri::State<'_, crate::PopupMenu<R>>,
|
||||
) {
|
||||
window.popup_menu(&popup_menu.0).unwrap();
|
||||
}
|
||||
|
@ -15,13 +15,22 @@ use serde::Serialize;
|
||||
use tauri::{ipc::Channel, window::WindowBuilder, App, AppHandle, RunEvent, Runtime, WindowUrl};
|
||||
use tauri_plugin_sample::{PingRequest, SampleExt};
|
||||
|
||||
#[cfg(desktop)]
|
||||
use tauri::Manager;
|
||||
|
||||
pub type SetupHook = Box<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>;
|
||||
pub type OnEvent = Box<dyn FnMut(&AppHandle, RunEvent)>;
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
struct Reply {
|
||||
data: String,
|
||||
}
|
||||
|
||||
pub type SetupHook = Box<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>;
|
||||
pub type OnEvent = Box<dyn FnMut(&AppHandle, RunEvent)>;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub struct AppMenu<R: Runtime>(pub std::sync::Mutex<Option<tauri::menu::Menu<R>>>);
|
||||
|
||||
#[cfg(desktop)]
|
||||
pub struct PopupMenu<R: Runtime>(tauri::menu::Menu<R>);
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
@ -43,11 +52,23 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
.setup(move |app| {
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
tray::create_tray(app)?;
|
||||
|
||||
app.handle().plugin(tauri_plugin_cli::init())?;
|
||||
let handle = app.handle();
|
||||
tray::create_tray(&handle)?;
|
||||
handle.plugin(tauri_plugin_cli::init())?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
app.manage(AppMenu::<R>(Default::default()));
|
||||
|
||||
#[cfg(desktop)]
|
||||
app.manage(PopupMenu(
|
||||
tauri::menu::MenuBuilder::new(app)
|
||||
.check("Tauri is awesome!")
|
||||
.text("Do something")
|
||||
.copy()
|
||||
.build()?,
|
||||
));
|
||||
|
||||
let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default());
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
@ -55,7 +76,8 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
.title("Tauri API Validation")
|
||||
.inner_size(1000., 800.)
|
||||
.min_inner_size(600., 400.)
|
||||
.content_protected(true);
|
||||
.content_protected(true)
|
||||
.menu(tauri::menu::Menu::default(&app.handle())?);
|
||||
}
|
||||
|
||||
let window = window_builder.build().unwrap();
|
||||
@ -119,16 +141,15 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
});
|
||||
});
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
builder = builder.menu(tauri::Menu::os_default("Tauri API Validation"));
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut app = builder
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
cmd::log_operation,
|
||||
cmd::perform_request,
|
||||
#[cfg(desktop)]
|
||||
cmd::toggle_menu,
|
||||
#[cfg(desktop)]
|
||||
cmd::popup_context_menu
|
||||
])
|
||||
.build(tauri::tauri_build_context!())
|
||||
.expect("error while building tauri application");
|
||||
@ -140,7 +161,7 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
#[cfg(all(desktop, not(test)))]
|
||||
if let RunEvent::ExitRequested { api, .. } = &_event {
|
||||
// Keep the event loop running even if all windows are closed
|
||||
// This allow us to catch system tray events when there is no window
|
||||
// This allow us to catch tray icon events when there is no window
|
||||
api.prevent_exit();
|
||||
}
|
||||
})
|
||||
|
@ -4,121 +4,113 @@
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tauri::{
|
||||
CustomMenuItem, Manager, Runtime, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder,
|
||||
WindowUrl,
|
||||
menu::{Menu, MenuItem},
|
||||
tray::{ClickType, TrayIconBuilder},
|
||||
Manager, Runtime, WindowBuilder, WindowUrl,
|
||||
};
|
||||
|
||||
pub fn create_tray<R: Runtime>(app: &tauri::App<R>) -> tauri::Result<()> {
|
||||
let mut tray_menu1 = SystemTrayMenu::new()
|
||||
.add_item(CustomMenuItem::new("toggle", "Toggle"))
|
||||
.add_item(CustomMenuItem::new("new", "New window"))
|
||||
.add_item(CustomMenuItem::new("icon_1", "Tray Icon 1"))
|
||||
.add_item(CustomMenuItem::new("icon_2", "Tray Icon 2"));
|
||||
|
||||
pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
|
||||
let toggle_i = MenuItem::with_id(app, "toggle", "Toggle", true, None);
|
||||
let new_window_i = MenuItem::with_id(app, "new-window", "New window", true, None);
|
||||
let icon_i_1 = MenuItem::with_id(app, "icon-1", "Icon 1", true, None);
|
||||
let icon_i_2 = MenuItem::with_id(app, "icon-2", "Icon 2", true, None);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
tray_menu1 = tray_menu1.add_item(CustomMenuItem::new("set_title", "Set Title"));
|
||||
}
|
||||
let set_title_i = MenuItem::with_id(app, "set-title", "Set Title", true, None);
|
||||
let switch_i = MenuItem::with_id(app, "switch-menu", "Switch Menu", true, None);
|
||||
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None);
|
||||
let remove_tray_i = MenuItem::with_id(app, "remove-tray", "Remove Tray icon", true, None);
|
||||
let menu1 = Menu::with_items(
|
||||
app,
|
||||
&[
|
||||
&toggle_i,
|
||||
&new_window_i,
|
||||
&icon_i_1,
|
||||
&icon_i_2,
|
||||
#[cfg(target_os = "macos")]
|
||||
&set_title_i,
|
||||
&switch_i,
|
||||
&quit_i,
|
||||
&remove_tray_i,
|
||||
],
|
||||
)?;
|
||||
let menu2 = Menu::with_items(
|
||||
app,
|
||||
&[&toggle_i, &new_window_i, &switch_i, &quit_i, &remove_tray_i],
|
||||
)?;
|
||||
|
||||
tray_menu1 = tray_menu1
|
||||
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
|
||||
.add_item(CustomMenuItem::new("exit_app", "Quit"))
|
||||
.add_item(CustomMenuItem::new("destroy", "Destroy"));
|
||||
|
||||
let tray_menu2 = SystemTrayMenu::new()
|
||||
.add_item(CustomMenuItem::new("toggle", "Toggle"))
|
||||
.add_item(CustomMenuItem::new("new", "New window"))
|
||||
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
|
||||
.add_item(CustomMenuItem::new("exit_app", "Quit"))
|
||||
.add_item(CustomMenuItem::new("destroy", "Destroy"));
|
||||
let is_menu1 = AtomicBool::new(true);
|
||||
|
||||
let handle = app.handle();
|
||||
let tray_id = "my-tray".to_string();
|
||||
SystemTray::new()
|
||||
.with_id(&tray_id)
|
||||
.with_menu(tray_menu1.clone())
|
||||
.with_tooltip("Tauri")
|
||||
.on_event(move |event| {
|
||||
let tray_handle = handle.tray_handle_by_id(&tray_id).unwrap();
|
||||
match event {
|
||||
SystemTrayEvent::LeftClick {
|
||||
position: _,
|
||||
size: _,
|
||||
..
|
||||
} => {
|
||||
let window = handle.get_window("main").unwrap();
|
||||
window.show().unwrap();
|
||||
window.set_focus().unwrap();
|
||||
let _ = TrayIconBuilder::with_id("tray-1")
|
||||
.tooltip("Tauri")
|
||||
.icon(app.default_window_icon().unwrap().clone())
|
||||
.menu(&menu1)
|
||||
.menu_on_left_click(false)
|
||||
.on_menu_event(move |app, event| match event.id.as_ref() {
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
"remove-tray" => {
|
||||
app.remove_tray_by_id("tray-1");
|
||||
}
|
||||
"toggle" => {
|
||||
if let Some(window) = app.get_window("main") {
|
||||
let new_title = if window.is_visible().unwrap_or_default() {
|
||||
let _ = window.hide();
|
||||
"Show"
|
||||
} else {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
"Hide"
|
||||
};
|
||||
toggle_i.set_text(new_title).unwrap();
|
||||
}
|
||||
SystemTrayEvent::MenuItemClick { id, .. } => {
|
||||
let item_handle = tray_handle.get_item(&id);
|
||||
match id.as_str() {
|
||||
"exit_app" => {
|
||||
// exit the app
|
||||
handle.exit(0);
|
||||
}
|
||||
"destroy" => {
|
||||
tray_handle.destroy().unwrap();
|
||||
}
|
||||
"toggle" => {
|
||||
let window = handle.get_window("main").unwrap();
|
||||
let new_title = if window.is_visible().unwrap() {
|
||||
window.hide().unwrap();
|
||||
"Show"
|
||||
} else {
|
||||
window.show().unwrap();
|
||||
"Hide"
|
||||
};
|
||||
item_handle.set_title(new_title).unwrap();
|
||||
}
|
||||
"new" => {
|
||||
WindowBuilder::new(&handle, "new", WindowUrl::App("index.html".into()))
|
||||
.title("Tauri")
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
"set_title" => {
|
||||
#[cfg(target_os = "macos")]
|
||||
tray_handle.set_title("Tauri").unwrap();
|
||||
}
|
||||
"icon_1" => {
|
||||
#[cfg(target_os = "macos")]
|
||||
tray_handle.set_icon_as_template(true).unwrap();
|
||||
|
||||
tray_handle
|
||||
.set_icon(tauri::Icon::Raw(
|
||||
include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
"icon_2" => {
|
||||
#[cfg(target_os = "macos")]
|
||||
tray_handle.set_icon_as_template(true).unwrap();
|
||||
|
||||
tray_handle
|
||||
.set_icon(tauri::Icon::Raw(
|
||||
include_bytes!("../../../.icons/icon.ico").to_vec(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
"switch_menu" => {
|
||||
let flag = is_menu1.load(Ordering::Relaxed);
|
||||
let (menu, tooltip) = if flag {
|
||||
(tray_menu2.clone(), "Menu 2")
|
||||
} else {
|
||||
(tray_menu1.clone(), "Tauri")
|
||||
};
|
||||
tray_handle.set_menu(menu).unwrap();
|
||||
tray_handle.set_tooltip(tooltip).unwrap();
|
||||
is_menu1.store(!flag, Ordering::Relaxed);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
"new-window" => {
|
||||
let _ = WindowBuilder::new(app, "new", WindowUrl::App("index.html".into()))
|
||||
.title("Tauri")
|
||||
.build();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
"set-title" => {
|
||||
if let Some(tray) = app.tray_by_id("tray-1") {
|
||||
let _ = tray.set_title(Some("Tauri"));
|
||||
}
|
||||
}
|
||||
i @ "icon-1" | i @ "icon-2" => {
|
||||
if let Some(tray) = app.tray_by_id("tray-1") {
|
||||
let _ = tray.set_icon(Some(tauri::Icon::Raw(if i == "icon-1" {
|
||||
include_bytes!("../../../.icons/icon.ico").to_vec()
|
||||
} else {
|
||||
include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec()
|
||||
})));
|
||||
}
|
||||
}
|
||||
"switch-menu" => {
|
||||
let flag = is_menu1.load(Ordering::Relaxed);
|
||||
let (menu, tooltip) = if flag {
|
||||
(menu2.clone(), "Menu 2")
|
||||
} else {
|
||||
(menu1.clone(), "Tauri")
|
||||
};
|
||||
if let Some(tray) = app.tray_by_id("tray-1") {
|
||||
let _ = tray.set_menu(Some(menu));
|
||||
let _ = tray.set_tooltip(Some(tooltip));
|
||||
}
|
||||
is_menu1.store(!flag, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_event(|tray, event| {
|
||||
if event.click_type == ClickType::Left {
|
||||
let app = tray.app_handle();
|
||||
if let Some(window) = app.get_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.build(app)
|
||||
.map(|_| ())
|
||||
.build(app);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -112,11 +112,6 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"systemTray": {
|
||||
"iconPath": "../../.icons/tray_icon_with_transparency.png",
|
||||
"iconAsTemplate": true,
|
||||
"menuOnLeftClick": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
import Welcome from './views/Welcome.svelte'
|
||||
import Communication from './views/Communication.svelte'
|
||||
import WebRTC from './views/WebRTC.svelte'
|
||||
|
||||
const userAgent = navigator.userAgent.toLowerCase()
|
||||
const isMobile = userAgent.includes('android') || userAgent.includes('iphone')
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.ctrlKey && event.key === 'b') {
|
||||
invoke('toggle_menu')
|
||||
}
|
||||
})
|
||||
|
||||
const views = [
|
||||
{
|
||||
|
@ -1,7 +1,19 @@
|
||||
<p>
|
||||
This is a demo of Tauri's API capabilities using the <code
|
||||
>@tauri-apps/api</code
|
||||
> package. It's used as the main validation app, serving as the test bed of our
|
||||
development process. In the future, this app will be used on Tauri's integration
|
||||
tests.
|
||||
</p>
|
||||
<script>
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
function contextMenu() {
|
||||
invoke('popup_context_menu')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
This is a demo of Tauri's API capabilities using the <code
|
||||
>@tauri-apps/api</code
|
||||
> package. It's used as the main validation app, serving as the test bed of our
|
||||
development process. In the future, this app will be used on Tauri's integration
|
||||
tests.
|
||||
</p>
|
||||
|
||||
<button class="btn" on:click={contextMenu}>Context menu</button>
|
||||
</div>
|
||||
|
@ -61,11 +61,7 @@ mod ui {
|
||||
pub fn main() {
|
||||
let context = super::context();
|
||||
tauri::Builder::default()
|
||||
.menu(if cfg!(target_os = "macos") {
|
||||
tauri::Menu::os_default(&context.package_info().name)
|
||||
} else {
|
||||
tauri::Menu::default()
|
||||
})
|
||||
.menu(tauri::menu::Menu::default)
|
||||
.setup(|app| {
|
||||
// set the splashscreen and main windows to be globally available with the tauri state API
|
||||
app.manage(SplashscreenWindow(Arc::new(Mutex::new(
|
||||
|
72
tooling/cli/Cargo.lock
generated
72
tooling/cli/Cargo.lock
generated
@ -299,9 +299,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.3.1"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84"
|
||||
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
||||
|
||||
[[package]]
|
||||
name = "bitness"
|
||||
@ -1105,12 +1105,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
@ -1674,9 +1671,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.6"
|
||||
version = "0.24.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
|
||||
checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
@ -1759,15 +1756,6 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
@ -1793,7 +1781,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"rustix 0.37.19",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -2085,9 +2073,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.144"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
@ -2138,6 +2126,12 @@ version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
||||
|
||||
[[package]]
|
||||
name = "local-ip-address"
|
||||
version = "0.4.9"
|
||||
@ -2314,7 +2308,7 @@ version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ede2d12cd6fce44da537a4be1f5510c73be2506c2e32dfaaafd1f36968f3a0e"
|
||||
dependencies = [
|
||||
"bitflags 2.3.1",
|
||||
"bitflags 2.3.3",
|
||||
"ctor 0.2.0",
|
||||
"napi-derive",
|
||||
"napi-sys",
|
||||
@ -3261,7 +3255,20 @@ dependencies = [
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"linux-raw-sys 0.3.8",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.5",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -3907,9 +3914,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.39"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96"
|
||||
checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
@ -4142,15 +4149,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.6.0"
|
||||
version = "3.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
|
||||
checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix",
|
||||
"rustix 0.38.7",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -4232,9 +4238,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471"
|
||||
checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"jpeg-decoder",
|
||||
@ -5099,9 +5105,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "0.2.3"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
|
||||
checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Config",
|
||||
"description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler and define a system tray.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"bundle\": {}, \"security\": { \"csp\": null }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
|
||||
"description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler and define a tray icon.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"bundle\": {}, \"security\": { \"csp\": null }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
@ -223,11 +223,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"systemTray": {
|
||||
"description": "Configuration for app system tray.",
|
||||
"trayIcon": {
|
||||
"description": "Configuration for app tray icon.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SystemTrayConfig"
|
||||
"$ref": "#/definitions/TrayIconConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@ -2065,15 +2065,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SystemTrayConfig": {
|
||||
"description": "Configuration for application system tray icon.\n\nSee more: https://tauri.app/v1/api/config#systemtrayconfig",
|
||||
"TrayIconConfig": {
|
||||
"description": "Configuration for application tray icon.\n\nSee more: https://tauri.app/v1/api/config#trayiconconfig",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"iconPath"
|
||||
],
|
||||
"properties": {
|
||||
"iconPath": {
|
||||
"description": "Path to the default icon to use on the system tray.",
|
||||
"description": "Path to the default icon to use for the tray icon.",
|
||||
"type": "string"
|
||||
},
|
||||
"iconAsTemplate": {
|
||||
@ -2092,6 +2092,13 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"description": "Tray icon tooltip on Windows and macOS",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
@ -143,31 +143,6 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
|
||||
// set env vars used by the bundler
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if config_.tauri.system_tray.is_some() {
|
||||
if let Ok(tray) = std::env::var("TAURI_TRAY") {
|
||||
std::env::set_var(
|
||||
"TRAY_LIBRARY_PATH",
|
||||
if tray == "ayatana" {
|
||||
format!(
|
||||
"{}/libayatana-appindicator3.so.1",
|
||||
pkgconfig_utils::get_library_path("ayatana-appindicator3-0.1")
|
||||
.expect("failed to get ayatana-appindicator library path using pkg-config.")
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}/libappindicator3.so.1",
|
||||
pkgconfig_utils::get_library_path("appindicator3-0.1")
|
||||
.expect("failed to get libappindicator-gtk library path using pkg-config.")
|
||||
)
|
||||
},
|
||||
);
|
||||
} else {
|
||||
std::env::set_var(
|
||||
"TRAY_LIBRARY_PATH",
|
||||
pkgconfig_utils::get_appindicator_library_path(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if config_.tauri.bundle.appimage.bundle_media_framework {
|
||||
std::env::set_var("APPIMAGE_BUNDLE_GSTREAMER", "1");
|
||||
}
|
||||
@ -394,37 +369,3 @@ fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod pkgconfig_utils {
|
||||
use std::{path::PathBuf, process::Command};
|
||||
|
||||
pub fn get_appindicator_library_path() -> PathBuf {
|
||||
match get_library_path("ayatana-appindicator3-0.1") {
|
||||
Some(p) => format!("{p}/libayatana-appindicator3.so.1").into(),
|
||||
None => match get_library_path("appindicator3-0.1") {
|
||||
Some(p) => format!("{p}/libappindicator3.so.1").into(),
|
||||
None => panic!("Can't detect any appindicator library"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the folder in which a library is located using `pkg-config`.
|
||||
pub fn get_library_path(name: &str) -> Option<String> {
|
||||
let mut cmd = Command::new("pkg-config");
|
||||
cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1");
|
||||
cmd.arg("--libs-only-L");
|
||||
cmd.arg(name);
|
||||
if let Ok(output) = cmd.output() {
|
||||
if !output.stdout.is_empty() {
|
||||
// output would be "-L/path/to/library\n"
|
||||
let word = output.stdout[2..].to_vec();
|
||||
return Some(String::from_utf8_lossy(&word).trim().to_string());
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -697,12 +697,7 @@ impl AppSettings for RustAppSettings {
|
||||
config: &Config,
|
||||
features: &[String],
|
||||
) -> crate::Result<BundleSettings> {
|
||||
tauri_config_to_bundle_settings(
|
||||
&self.manifest,
|
||||
features,
|
||||
config.tauri.bundle.clone(),
|
||||
config.tauri.system_tray.clone(),
|
||||
)
|
||||
tauri_config_to_bundle_settings(&self.manifest, features, config.tauri.bundle.clone())
|
||||
}
|
||||
|
||||
fn app_binary_path(&self, options: &Options) -> crate::Result<PathBuf> {
|
||||
@ -1045,7 +1040,6 @@ fn tauri_config_to_bundle_settings(
|
||||
manifest: &Manifest,
|
||||
features: &[String],
|
||||
config: crate::helpers::config::BundleConfig,
|
||||
system_tray_config: Option<crate::helpers::config::SystemTrayConfig>,
|
||||
) -> crate::Result<BundleSettings> {
|
||||
let enabled_features = manifest.all_enabled_features(features);
|
||||
|
||||
@ -1066,15 +1060,45 @@ fn tauri_config_to_bundle_settings(
|
||||
#[allow(unused_mut)]
|
||||
let mut depends = config.deb.depends.unwrap_or_default();
|
||||
|
||||
// set env vars used by the bundler and inject dependencies
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Some(system_tray_config) = &system_tray_config {
|
||||
let tray = std::env::var("TAURI_TRAY").unwrap_or_else(|_| "ayatana".to_string());
|
||||
if tray == "ayatana" {
|
||||
depends.push("libayatana-appindicator3-1".into());
|
||||
} else {
|
||||
depends.push("libappindicator3-1".into());
|
||||
if enabled_features.contains(&"tray-icon".into())
|
||||
|| enabled_features.contains(&"tauri/tray-icon".into())
|
||||
{
|
||||
let (tray_kind, path) = std::env::var("TAURI_TRAY")
|
||||
.map(|kind| {
|
||||
if kind == "ayatana" {
|
||||
(
|
||||
pkgconfig_utils::TrayKind::Ayatana,
|
||||
format!(
|
||||
"{}/libayatana-appindicator3.so.1",
|
||||
pkgconfig_utils::get_library_path("ayatana-appindicator3-0.1")
|
||||
.expect("failed to get ayatana-appindicator library path using pkg-config.")
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
pkgconfig_utils::TrayKind::Libappindicator,
|
||||
format!(
|
||||
"{}/libappindicator3.so.1",
|
||||
pkgconfig_utils::get_library_path("appindicator3-0.1")
|
||||
.expect("failed to get libappindicator-gtk library path using pkg-config.")
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| pkgconfig_utils::get_appindicator_library_path());
|
||||
match tray_kind {
|
||||
pkgconfig_utils::TrayKind::Ayatana => {
|
||||
depends.push("libayatana-appindicator3-1".into());
|
||||
}
|
||||
pkgconfig_utils::TrayKind::Libappindicator => {
|
||||
depends.push("libappindicator3-1".into());
|
||||
}
|
||||
}
|
||||
|
||||
std::env::set_var("TRAY_LIBRARY_PATH", path);
|
||||
}
|
||||
|
||||
// provides `libwebkit2gtk-4.1.so.37` and all `4.0` versions have the -37 package name
|
||||
@ -1184,3 +1208,48 @@ fn tauri_config_to_bundle_settings(
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod pkgconfig_utils {
|
||||
use std::process::Command;
|
||||
|
||||
pub enum TrayKind {
|
||||
Ayatana,
|
||||
Libappindicator,
|
||||
}
|
||||
|
||||
pub fn get_appindicator_library_path() -> (TrayKind, String) {
|
||||
match get_library_path("ayatana-appindicator3-0.1") {
|
||||
Some(p) => (
|
||||
TrayKind::Ayatana,
|
||||
format!("{p}/libayatana-appindicator3.so.1"),
|
||||
),
|
||||
None => match get_library_path("appindicator3-0.1") {
|
||||
Some(p) => (
|
||||
TrayKind::Libappindicator,
|
||||
format!("{p}/libappindicator3.so.1"),
|
||||
),
|
||||
None => panic!("Can't detect any appindicator library"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the folder in which a library is located using `pkg-config`.
|
||||
pub fn get_library_path(name: &str) -> Option<String> {
|
||||
let mut cmd = Command::new("pkg-config");
|
||||
cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1");
|
||||
cmd.arg("--libs-only-L");
|
||||
cmd.arg(name);
|
||||
if let Ok(output) = cmd.output() {
|
||||
if !output.stdout.is_empty() {
|
||||
// output would be "-L/path/to/library\n"
|
||||
let word = output.stdout[2..].to_vec();
|
||||
return Some(String::from_utf8_lossy(&word).trim().to_string());
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,10 @@ fn migrate_config(config: &mut Value) -> Result<()> {
|
||||
process_security(security)?;
|
||||
}
|
||||
|
||||
if let Some(tray) = tauri_config.remove("systemTray") {
|
||||
tauri_config.insert("trayIcon".into(), tray);
|
||||
}
|
||||
|
||||
// cli
|
||||
if let Some(cli) = tauri_config.remove("cli") {
|
||||
process_cli(&mut plugins, cli)?;
|
||||
|
@ -89,6 +89,7 @@ fn features_to_remove() -> Vec<&'static str> {
|
||||
features_to_remove.push("shell-open-api");
|
||||
features_to_remove.push("windows7-compat");
|
||||
features_to_remove.push("updater");
|
||||
features_to_remove.push("system-tray");
|
||||
|
||||
// this allowlist feature was not removed
|
||||
let index = features_to_remove
|
||||
@ -152,6 +153,8 @@ fn migrate_dependency_table<D: TableLike>(dep: &mut D, version: String, remove:
|
||||
features_array.remove(index);
|
||||
if f == "reqwest-native-tls-vendored" {
|
||||
add_features.push("native-tls-vendored");
|
||||
} else if f == "system-tray" {
|
||||
add_features.push("tray-icon");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user