tauri 提供了两种系统托盘的创建方式,托盘 API 在 JavaScript 和 Rust 中均可用。
通用基本配置(不管是 JavaScript
还是 Rust
):
我们需要在 src-tauri/Cargo.toml
中写入包含系统托盘的必要功能 tray-icon
,image-png
。
[package]
name = "tauri-app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
[lib]
name = "tauri_app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
# 为 tauri 的 features 数组里加入 "tray-icon",保存时自动更新依赖。
# "image-png" 是为了支持 Image api 读取文件存在的,详细请参考源码:
# import { Image } from '@tauri-apps/api/image'; static fromPath 函数的注释
# 如果没有添加 "image-png",则在读取 png 时将会出现该错误:"Uncaught (in promise) expected RGBA image data, found a file path"
tauri = { version = "2", features = ["tray-icon", "image-png"] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
一、使用 JavaScript
创建系统托盘
一. 使用 JavaScript
创建系统托盘:
// 导入系统托盘,
import { TrayIcon } from '@tauri-apps/api/tray';
/**
* 在这里你可以添加一个托盘菜单,标题,工具提示,事件处理程序等
*/
const options = {
// icon 的相对路径基于:项目根目录/src-tauri/
icon : "icons/32x32.png",
// 托盘提示,悬浮在托盘图标上可以显示 tauri-app
tooltip: 'tauri-app',
// 是否在左键点击时显示托盘菜单,默认为 true。当然不能为 true 啦,程序进入后台不得左键点击图标显示窗口啊。
menuOnLeftClick: false,
// 托盘菜单,后续创建.....
menu: undefined,
// 托盘图标上事件的处理程序。这个下面需要详细说一下
action: (event) => {}
}
/**
* 创建系统托盘
*/
export async function createTray() {
const tray = await TrayIcon.new(options);
}
二. 设置 JavaScript
系统托盘事件
在上面的代码中,我们通过运行 createTray
函数就可以得到一个系统托盘图标。但是光有一个图标还是不够的,所以我们还得有相对应的事件,通过 Ctrl+左键
进入源码部分,我们可以看到 options.action
函数传入了一个 TrayIconEvent
联合类型事件:
export type TrayIconEvent = (TrayIconEventBase<'Click'> & TrayIconClickEvent) | (TrayIconEventBase<'DoubleClick'> & Omit<TrayIconClickEvent, 'buttonState'>) | TrayIconEventBase<'Enter'> | TrayIconEventBase<'Move'> | TrayIconEventBase<'Leave'>;
在这里,它提供了 Click
、DoubleClick
、Enter
、Move
、Leave
(单击,双击,鼠标移入,鼠标拖动,鼠标移出)这五个托盘事件。
action
属性函数的 event
参数我们需要使用 switch
或者是 if
把它的 type
筛选出来,在 case
选项中执行我们的事件:
action: (event) => {
switch(event.type) {
case 'Click':
console.log('单击事件');
console.log('Click event:', event);
console.log('Mouse button:', event.button);
console.log('Button state:', event.buttonState);
break;
case 'DoubleClick':
console.log('双击事件');
console.log('DoubleClick event:', event);
break;
case 'Enter':
console.log('鼠标进入托盘图标');
console.log('Mouse entered tray icon area:', event);
break;
case 'Move':
console.log('鼠标移动托盘图标');
console.log('Mouse moved over tray icon:', event);
break;
case 'Leave':
console.log('鼠标离开托盘图标');
console.log('Mouse left tray icon area:', event);
break;
default:
console.log(`未定义事件类型: ${event.type}`);
}
}
对于我们来说,常用的也就只有第一个 Click
事件,其他的用的不多,所以别的我也就不说了,大家自行选择就好。
那么接下来,我们来试一试单击托盘图标会怎么样:
看,它单击事件触发了两次,一次是鼠标按下(Button state: Down
),一次是鼠标松开(Button state: Up
)。 对于我们来说,我们只需要一次就可以,通过简单的 if 判断可以过滤掉一次事件:
if(event.buttonState === 'Down')
或者
if(event.buttonState === 'Up')
当然还有一件事,我们多次修改了文件,
vite
在多次的重载中难免会造成多个系统托盘图标,但是没关系,重启应用后,我还是一个 o( ̄▽ ̄)o
三. JavaScript
设置窗口关闭后,应用进入后台运行:
其实吧,我们只需要隐藏掉窗口就好,至于怎么隐藏呢,我在这里说一下步骤:
tauri 提供了 import { getCurrentWindow } from '@tauri-apps/api/window';
api,执行 getCurrentWindow 函数可以获取到一个 Window
当前窗体实例,我们可以通过这个实例的 hide()
函数隐藏窗口。
-
关闭系统自带的拖拽栏:设置
tauri.conf.json
属性:app.windows.decorations = false
-
使用
HTML+CSS+JS
替代系统拖拽栏,重点是这个关闭按钮得换成hide()
-
下一步就是参考我的上一篇文档,看看这两步是怎么做的:tauri 2.0创建项目 https://blog.csdn.net/xiaoyuanbaobao/article/details/143751093
在干完上面的操作后,我们就可以关闭窗口,在创建托盘的页面引入 getCurrentWindow
函数在单击事件中调用它的 show()
方法把进入后台运行的程序窗口打开。
只是把窗口显示出来就够了?
当然不是,我们还得在它窗口没隐藏的情况下把窗口置顶聚焦,这才符合我们的程序使用习惯。
-
通过
isVisible
方法可以检查窗口是否是可视状态 -
通过
show
方法可以把窗口显示出来 -
通过
setFocus
方法可以聚焦窗口,让窗口置顶显示 -
通过
unminimize
方法可以解除最小化,怎么好像混进去什么奇怪的东西?
这 TM 是个大坑!!!!!!
在进入最小化的情况下,聚焦无法将页面置顶,只有解除最小化之后聚焦才是有效的。
这个在rust
端api
中并没有这个问题,但是在Javascript
端api
是这样的。!!!需要特别注意!!!
tauri
遵循最小权限原则,在运行中可能会碰到如下权限不足的问题:
在src-tauri/capabilities/default.json
文件的permissions
属性数组中加入这几个权限:
"core:window:allow-set-focus"
、"core:window:allow-close
、
"core:window:allow-show"
、"core:window:allow-is-visible"
、
"core:window:allow-unminimize"
、"core:window:allow-is-minimized"
写到现在给大家再看看现在的 Tray
代码吧,目前它任然是不完整的,我还没挂托盘菜单 Menu
呢:
// 导入系统托盘
import { TrayIcon } from '@tauri-apps/api/tray';
import { getCurrentWindow } from '@tauri-apps/api/window';
/**
* 在这里你可以添加一个托盘菜单,标题,工具提示,事件处理程序等
*/
const options = {
// icon 的相对路径基于:项目根目录/src-tauri/
icon : "icons/32x32.png",
// 托盘提示,悬浮在托盘图标上可以显示 tauri-app
tooltip: 'tauri-app',
// 是否在左键点击时显示托盘菜单,默认为 true。当然不能为 true 啦,程序进入后台不得左键点击图标显示窗口啊。
menuOnLeftClick: false,
// 托盘图标上事件的处理程序。
action: (event) => {
// 左键点击事件
if(event.type === 'Click' && event.button === "Left" && event.buttonState === 'Down') {
console.log('单击事件');
// 显示窗口
winShowFocus();
}
}
}
/**
* 窗口置顶显示
*/
async function winShowFocus() {
// 获取窗体实例
const win = getCurrentWindow();
// 检查窗口是否见,如果不可见则显示出来
if(!(await win.isVisible())) {
win.show();
}else {
// 检查是否处于最小化状态,如果处于最小化状态则解除最小化
if(await win.isMinimized()) {
await win.unminimize();
}
// 窗口置顶
await win.setFocus();
}
}
/**
* 创建系统托盘
*/
export async function createTray() {
const tray = await TrayIcon.new(options);
console.log(tray)
}
四. Javascript 前端添加托盘菜单 Menu
创建系统托盘菜单:
import { Menu } from '@tauri-apps/api/menu';
/**
* 创建托盘菜单
*/
async function createMenu() {
return await Menu.new({
// items 的显示顺序是倒过来的
items: [
{
id: 'show',
text: '显示窗口',
action: () => {
winShowFocus();
}
},
{
// 菜单 id
id: 'quit',
// 菜单文本
text: '退出',
// 菜单事件处理程序
action: () => {
// 退出应用还需要引入别的依赖
console.log('退出应用');
}
}
]
})
}
将托盘菜单添加进系统托盘,修改创建系统托盘的 createTray
函数:
/**
* 创建系统托盘
*/
export async function createTray() {
// 获取 menu
options.menu = await createMenu();
const tray = await TrayIcon.new(options);
console.log(tray)
}
五. JavaScript 退出应用
在窗体化应用中,通常关闭最后一个窗口,就可以退出整个应用:
getCurrentWindow().close();
但是这样可能并不一定保险,我在 getCurrentWindow
并没有发现类似于 app.exit
或者 app.quit
的函数。所以我们又得引入新的东西了:进程 process https://tauri.app/zh-cn/plugin/process/
- 首先使用项目的包管理器来添加依赖:
npm run tauri add process
- 引入
api
:import { exit, relaunch } from ‘@tauri-apps/plugin-process’;
在进程管理中,只有两个东西,一个是 exit
退出应用函数,一个是 relaunch
重启应用函数,没了 ಠ_ಠ
。
用法也没啥好说的,直接调用就好:
// 退出应用
await exit(0);
// 重启应用
await relaunch();
六. JavaScript 创建系统托盘最终代码:
createTray
函数是对外导出的,外部调用跑就行,不会真的有人没调这个方法吧?(⓿_⓿)
// 导入系统托盘
import { TrayIcon } from '@tauri-apps/api/tray';
// 获取当前窗口
import { getCurrentWindow } from '@tauri-apps/api/window';
// 托盘菜单
import { Menu } from '@tauri-apps/api/menu';
// 进程管理
import { exit } from '@tauri-apps/plugin-process';
/**
* 在这里你可以添加一个托盘菜单,标题,工具提示,事件处理程序等
*/
const options = {
// icon 的相对路径基于:项目根目录/src-tauri/,其他 tauri api 相对路径大抵都是这个套路
icon : "icons/32x32.png",
// 托盘提示,悬浮在托盘图标上可以显示 tauri-app
tooltip: 'tauri-app',
// 是否在左键点击时显示托盘菜单,默认为 true。当然不能为 true 啦,程序进入后台不得左键点击图标显示窗口啊。
menuOnLeftClick: false,
// 托盘图标上事件的处理程序。
action: (event) => {
// 左键点击事件
if(event.type === 'Click' && event.button === "Left" && event.buttonState === 'Down') {
console.log('单击事件');
// 显示窗口
winShowFocus();
}
}
}
/**
* 窗口置顶显示
*/
async function winShowFocus() {
// 获取窗体实例
const win = getCurrentWindow();
// 检查窗口是否见,如果不可见则显示出来
if(!(await win.isVisible())) {
win.show();
}else {
// 检查是否处于最小化状态,如果处于最小化状态则解除最小化
if(await win.isMinimized()) {
await win.unminimize();
}
// 窗口置顶
await win.setFocus();
}
}
/**
* 创建托盘菜单
*/
async function createMenu() {
return await Menu.new({
// items 的显示顺序是倒过来的
items: [
{
id: 'show',
text: '显示窗口',
action: () => {
winShowFocus();
}
},
{
// 菜单 id
id: 'quit',
// 菜单文本
text: '退出',
// 菜单项点击事件
action: () => {
// 退出应用
exit(0);
}
}
]
})
}
/**
* 创建系统托盘
*/
export async function createTray() {
// 获取 menu
options.menu = await createMenu();
await TrayIcon.new(options);
}
二、在 Rust
创建系统托盘菜单:
其实吧,我觉得 JavaScript api
创建托盘菜单已经足够了,但是由于我 rust
学的一坨屎 (啥也不会!),所以在这里我不会说太多,直接将操作甩这就完了。
1. 在 src-tauri/Cargo.toml 中写入包含系统托盘的必要功能 tray-icon
2. 使用 html+css+Javascript
替换掉系统默认的拖拽栏,详情请参考:tauri 2.0创建项目 https://blog.csdn.net/xiaoyuanbaobao/article/details/143751093
3.Rust
创建系统托盘:
修改 src-tauri/lib.rs
文件,在文件中添加如下内容:
// 导入系统托盘所需的依赖, 导入的全都是
use tauri::{
tray::{
TrayIconBuilder,
MouseButtonState,
MouseButton,
TrayIconEvent
},
menu::{
Menu,
MenuItem
},
Manager
};
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![greet])
// setup 部分是系统托盘
.setup(|app| {
let show_i = MenuItem::with_id(app, "show", "显示", true, None::<&str>)?;
let quit_i = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&show_i, &quit_i])?;
// 创建系统托盘
let _tray = TrayIconBuilder::new()
// 添加托盘图标
.icon(app.default_window_icon().unwrap().clone())
// 添加菜单
.menu(&menu)
// 禁用鼠标左键点击图标显示托盘菜单
.menu_on_left_click(false)
// 监听托盘图标发出的鼠标事件
.on_tray_icon_event(|tray, event| match event {
// 左键点击托盘图标显示窗口
TrayIconEvent::Click {
id: _,
position: _,
rect: _,
button: MouseButton::Left,
button_state: MouseButtonState::Up,
} => {
let win = tray
.app_handle()
.get_webview_window("main")
.expect("REASON");
match win.is_visible() {
Ok(visible) if !visible => {
win.show().unwrap();
}
Err(e) => eprintln!("{}", e),
_ => (),
};
// 获取窗口焦点
win.set_focus().unwrap();
}
_ => {}
})
// 监听菜单事件
.on_menu_event(|app, event| match event.id.as_ref() {
"show" => {
let win = app.get_webview_window("main").unwrap();
match win.is_visible() {
Ok(visible) if !visible => {
win.show().unwrap();
}
Err(e) => eprintln!("{}", e),
_ => (),
};
// 获取窗口焦点
win.set_focus().unwrap();
}
"quit" => {
app.exit(0);
}
_ => {}
})
.build(app)?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
在这一大串代码中,.setup
部分是系统托盘部分的代码,注意别忘了粘贴的位置,以及导入依赖。
三、结束
tauri 2.0 创建系统托盘的教程到这里就结束了,让我想想接下来要在官方文档里薅哪一步分下来写 ヽ( ̄ω ̄( ̄ω ̄〃)ゝ