Skip to content

Commit c090927

Browse files
authored
feat(core): system tray, closes #157 (#1749)
1 parent 41d5d6a commit c090927

File tree

20 files changed

+756
-383
lines changed

20 files changed

+756
-383
lines changed

.changes/tray.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Adds system tray support.

core/tauri-codegen/src/context.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,47 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
6262
quote!(None)
6363
};
6464

65+
#[cfg(target_os = "linux")]
66+
let system_tray_icon = if let Some(tray) = &config.tauri.system_tray {
67+
let mut system_tray_icon_path = tray.icon_path.clone();
68+
system_tray_icon_path.set_extension("png");
69+
if dev {
70+
let system_tray_icon_file_name = system_tray_icon_path
71+
.file_name()
72+
.expect("failed to get tray path file_name")
73+
.to_string_lossy()
74+
.to_string();
75+
quote!(Some(
76+
::tauri::platform::resource_dir()
77+
.expect("failed to read resource dir")
78+
.join(
79+
#system_tray_icon_file_name
80+
)
81+
))
82+
} else {
83+
let system_tray_icon_path = config_parent
84+
.join(system_tray_icon_path)
85+
.display()
86+
.to_string();
87+
quote!(Some(::std::path::PathBuf::from(#system_tray_icon_path)))
88+
}
89+
} else {
90+
quote!(None)
91+
};
92+
93+
#[cfg(not(target_os = "linux"))]
94+
let system_tray_icon = if let Some(tray) = &config.tauri.system_tray {
95+
let mut system_tray_icon_path = tray.icon_path.clone();
96+
system_tray_icon_path.set_extension(if cfg!(windows) { "ico" } else { "png" });
97+
let system_tray_icon_path = config_parent
98+
.join(system_tray_icon_path)
99+
.display()
100+
.to_string();
101+
quote!(Some(include_bytes!(#system_tray_icon_path).to_vec()))
102+
} else {
103+
quote!(None)
104+
};
105+
65106
let package_name = if let Some(product_name) = &config.package.product_name {
66107
quote!(#product_name.to_string())
67108
} else {
@@ -78,6 +119,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
78119
config: #config,
79120
assets: ::std::sync::Arc::new(#assets),
80121
default_window_icon: #default_window_icon,
122+
system_tray_icon: #system_tray_icon,
81123
package_info: #root::api::PackageInfo {
82124
name: #package_name,
83125
version: #package_version,

core/tauri-utils/src/config.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ impl Default for WindowConfig {
134134

135135
/// The Updater configuration object.
136136
#[derive(PartialEq, Deserialize, Debug, Clone)]
137-
#[serde(tag = "updater", rename_all = "camelCase")]
137+
#[serde(rename_all = "camelCase")]
138138
pub struct UpdaterConfig {
139139
/// Whether the updater is active or not.
140140
#[serde(default)]
@@ -167,12 +167,21 @@ impl Default for UpdaterConfig {
167167

168168
/// Security configuration.
169169
#[derive(PartialEq, Deserialize, Debug, Clone, Default)]
170-
#[serde(tag = "updater", rename_all = "camelCase")]
170+
#[serde(rename_all = "camelCase")]
171171
pub struct SecurityConfig {
172172
/// Content security policy to inject to HTML files with the custom protocol.
173173
pub csp: Option<String>,
174174
}
175175

176+
/// Configuration for application system tray icon.
177+
#[derive(PartialEq, Deserialize, Debug, Clone, Default)]
178+
#[serde(rename_all = "camelCase")]
179+
pub struct SystemTrayConfig {
180+
/// Path to the icon to use on the system tray.
181+
/// Automatically set to be an `.png` on macOS and Linux, and `.ico` on Windows.
182+
pub icon_path: PathBuf,
183+
}
184+
176185
/// A CLI argument definition
177186
#[derive(PartialEq, Deserialize, Debug, Default, Clone)]
178187
#[serde(rename_all = "camelCase")]
@@ -262,7 +271,7 @@ pub struct CliArg {
262271

263272
/// The CLI root command definition.
264273
#[derive(PartialEq, Deserialize, Debug, Clone)]
265-
#[serde(tag = "cli", rename_all = "camelCase")]
274+
#[serde(rename_all = "camelCase")]
266275
#[allow(missing_docs)] // TODO
267276
pub struct CliConfig {
268277
pub description: Option<String>,
@@ -311,7 +320,7 @@ impl CliConfig {
311320

312321
/// The bundler configuration object.
313322
#[derive(PartialEq, Deserialize, Debug)]
314-
#[serde(tag = "bundle", rename_all = "camelCase")]
323+
#[serde(rename_all = "camelCase")]
315324
pub struct BundleConfig {
316325
/// The bundle identifier.
317326
pub identifier: String,
@@ -335,7 +344,7 @@ fn default_window_config() -> Vec<WindowConfig> {
335344

336345
/// The Tauri configuration object.
337346
#[derive(PartialEq, Deserialize, Debug)]
338-
#[serde(tag = "tauri", rename_all = "camelCase")]
347+
#[serde(rename_all = "camelCase")]
339348
pub struct TauriConfig {
340349
/// The window configuration.
341350
#[serde(default = "default_window_config")]
@@ -352,6 +361,8 @@ pub struct TauriConfig {
352361
/// The security configuration.
353362
#[serde(default)]
354363
pub security: SecurityConfig,
364+
/// System tray configuration.
365+
pub system_tray: Option<SystemTrayConfig>,
355366
}
356367

357368
impl Default for TauriConfig {
@@ -362,13 +373,14 @@ impl Default for TauriConfig {
362373
bundle: BundleConfig::default(),
363374
updater: UpdaterConfig::default(),
364375
security: SecurityConfig::default(),
376+
system_tray: None,
365377
}
366378
}
367379
}
368380

369381
/// The Build configuration object.
370382
#[derive(PartialEq, Deserialize, Debug)]
371-
#[serde(tag = "build", rename_all = "camelCase")]
383+
#[serde(rename_all = "camelCase")]
372384
pub struct BuildConfig {
373385
/// the devPath config.
374386
#[serde(default = "default_dev_path")]
@@ -777,15 +789,33 @@ mod build {
777789
}
778790
}
779791

792+
impl ToTokens for SystemTrayConfig {
793+
fn to_tokens(&self, tokens: &mut TokenStream) {
794+
let icon_path = self.icon_path.to_string_lossy().to_string();
795+
let icon_path = quote! { ::std::path::PathBuf::from(#icon_path) };
796+
literal_struct!(tokens, SystemTrayConfig, icon_path);
797+
}
798+
}
799+
780800
impl ToTokens for TauriConfig {
781801
fn to_tokens(&self, tokens: &mut TokenStream) {
782802
let windows = vec_lit(&self.windows, identity);
783803
let cli = opt_lit(self.cli.as_ref());
784804
let bundle = &self.bundle;
785805
let updater = &self.updater;
786806
let security = &self.security;
807+
let system_tray = opt_lit(self.system_tray.as_ref());
787808

788-
literal_struct!(tokens, TauriConfig, windows, cli, bundle, updater, security);
809+
literal_struct!(
810+
tokens,
811+
TauriConfig,
812+
windows,
813+
cli,
814+
bundle,
815+
updater,
816+
security,
817+
system_tray
818+
);
789819
}
790820
}
791821

@@ -880,6 +910,7 @@ mod test {
880910
endpoints: None,
881911
},
882912
security: SecurityConfig { csp: None },
913+
system_tray: None,
883914
};
884915

885916
// create a build config

core/tauri/src/api/process.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ impl Command {
365365
// tests for the commands functions.
366366
#[cfg(test)]
367367
mod test {
368+
#[cfg(not(windows))]
368369
use super::*;
369370

370371
#[cfg(not(windows))]

core/tauri/src/endpoints/window.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
// SPDX-License-Identifier: MIT
44

55
#[cfg(window_create)]
6-
use crate::sealed::ManagerBase;
7-
6+
use crate::runtime::{webview::WindowBuilder, Dispatch, Runtime};
87
use crate::{
98
api::config::WindowConfig,
109
endpoints::InvokeResponse,
@@ -106,17 +105,21 @@ impl Cmd {
106105
});
107106

108107
let url = options.url.clone();
109-
let pending = crate::runtime::window::PendingWindow::with_config(
110-
options,
111-
crate::runtime::webview::WebviewAttributes::new(url),
112-
label.clone(),
113-
);
114-
window.create_new_window(pending)?.emit_others(
115-
&crate::runtime::manager::tauri_event::<P::Event>("tauri://window-created"),
116-
Some(WindowCreatedEvent {
117-
label: label.to_string(),
118-
}),
119-
)?;
108+
window
109+
.create_window(label.clone(), url, |_, webview_attributes| {
110+
(
111+
<<<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder>::with_config(
112+
options,
113+
),
114+
webview_attributes,
115+
)
116+
})?
117+
.emit_others(
118+
&crate::runtime::manager::tauri_event::<P::Event>("tauri://window-created"),
119+
Some(WindowCreatedEvent {
120+
label: label.to_string(),
121+
}),
122+
)?;
120123
}
121124
// Getters
122125
Self::ScaleFactor => return Ok(window.scale_factor()?.into()),

core/tauri/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ pub enum Error {
6666
/// `default_path` provided to dialog API doesn't exist.
6767
#[error("failed to setup dialog: provided default path `{0}` doesn't exist")]
6868
DialogDefaultPathNotExists(PathBuf),
69+
/// Encountered an error creating the app system tray,
70+
#[error("error encountered during tray setup: {0}")]
71+
SystemTray(Box<dyn std::error::Error + Send>),
6972
}
7073

7174
impl From<serde_json::Error> for Error {

core/tauri/src/lib.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,11 @@ pub use {
5959
Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad,
6060
PageLoadPayload, SetupHook,
6161
},
62-
self::runtime::app::{App, Builder, WindowMenuEvent},
62+
self::runtime::app::{App, Builder, SystemTrayEvent, WindowMenuEvent},
6363
self::runtime::flavors::wry::Wry,
64+
self::runtime::menu::{CustomMenuItem, Menu, MenuId, MenuItem, SystemTrayMenuItem},
6465
self::runtime::monitor::Monitor,
65-
self::runtime::webview::{
66-
CustomMenuItem, Menu, MenuItem, MenuItemId, WebviewAttributes, WindowBuilder,
67-
},
66+
self::runtime::webview::{WebviewAttributes, WindowBuilder},
6867
self::runtime::window::{
6968
export::{
7069
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
@@ -73,6 +72,7 @@ pub use {
7372
WindowEvent,
7473
},
7574
self::state::{State, StateManager},
75+
tauri_utils::platform,
7676
};
7777

7878
/// Reads the config file at compile time and generates a [`Context`] based on its content.
@@ -130,6 +130,14 @@ pub struct Context<A: Assets> {
130130
/// The default window icon Tauri should use when creating windows.
131131
pub default_window_icon: Option<Vec<u8>>,
132132

133+
/// The icon to use use on the system tray UI.
134+
#[cfg(target_os = "linux")]
135+
pub system_tray_icon: Option<PathBuf>,
136+
137+
/// The icon to use use on the system tray UI.
138+
#[cfg(not(target_os = "linux"))]
139+
pub system_tray_icon: Option<Vec<u8>>,
140+
133141
/// Package information.
134142
pub package_info: crate::api::PackageInfo,
135143
}
@@ -142,6 +150,12 @@ pub trait Params: sealed::ParamsBase {
142150
/// The type used to determine the name of windows.
143151
type Label: Tag;
144152

153+
/// The type used to determine window menu ids.
154+
type MenuId: MenuId;
155+
156+
/// The type used to determine system tray menu ids.
157+
type SystemTrayMenuId: MenuId;
158+
145159
/// Assets that Tauri should serve from itself.
146160
type Assets: Assets;
147161

@@ -258,8 +272,8 @@ pub(crate) mod sealed {
258272

259273
/// A running [`Runtime`] or a dispatcher to it.
260274
pub enum RuntimeOrDispatch<'r, P: Params> {
261-
/// Mutable reference to the running [`Runtime`].
262-
Runtime(&'r mut P::Runtime),
275+
/// Reference to the running [`Runtime`].
276+
Runtime(&'r P::Runtime),
263277

264278
/// A dispatcher to the running [`Runtime`].
265279
Dispatch(<P::Runtime as Runtime>::Dispatcher),
@@ -270,18 +284,16 @@ pub(crate) mod sealed {
270284
/// The manager behind the [`Managed`] item.
271285
fn manager(&self) -> &WindowManager<P>;
272286

273-
/// The runtime or runtime dispatcher of the [`Managed`] item.
274-
fn runtime(&mut self) -> RuntimeOrDispatch<'_, P>;
275-
276287
/// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
277288
fn create_new_window(
278-
&mut self,
289+
&self,
290+
runtime: RuntimeOrDispatch<'_, P>,
279291
pending: crate::PendingWindow<P>,
280292
) -> crate::Result<crate::Window<P>> {
281293
use crate::runtime::Dispatch;
282294
let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
283295
let pending = self.manager().prepare_window(pending, &labels)?;
284-
match self.runtime() {
296+
match runtime {
285297
RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending),
286298
RuntimeOrDispatch::Dispatch(mut dispatcher) => dispatcher.create_window(pending),
287299
}

0 commit comments

Comments
 (0)