背景
刚安装的应用,用户可能对快捷键启动不熟悉,记不住快捷键,导致只能反复通过托盘菜单启动。而且,应用失去焦点后会自动隐藏,这就需要频繁去点击托盘菜单,造成糟糕的用户体验。
那么,如何帮助用户快速记住快捷键呢?一个有效的方法是,在托盘菜单中直接显示快捷键。用户频繁看到后,自然就能记住了。
开造
在 Tauri 中为托盘菜单添加快捷键可以通过 菜单项加速器 (Accelerator) 或 全局快捷键 (Global Shortcut) 实现。
JS 实现
src/hooks/useTray.ts
ts
import { useTranslation } from "react-i18next";
import { TrayIcon, type TrayIconOptions } from "@tauri-apps/api/tray";
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
import { resolveResource } from "@tauri-apps/api/path";
import { useUpdateEffect } from "ahooks";
import { exit } from "@tauri-apps/plugin-process";
import { isMac } from "@/utils/platform";
import { useAppStore } from "@/stores/appStore";
import { show_coco, show_settings } from "@/commands";
// 定义系统托盘的唯一标识符
const TRAY_ID = "COCO_TRAY";
/**
* 系统托盘钩子函数
* 用于创建和管理应用程序的系统托盘图标及其菜单
*/
export const useTray = () => {
// 获取国际化翻译功能和当前语言
const { t, i18n } = useTranslation();
// 从应用状态中获取显示Coco的快捷键
const showCocoShortcuts = useAppStore((state) => state.showCocoShortcuts);
// 当语言或快捷键发生变化时,更新托盘菜单
useUpdateEffect(() => {
if (showCocoShortcuts.length === 0) return;
updateTrayMenu();
}, [i18n.language, showCocoShortcuts]);
/**
* 根据ID获取托盘图标实例
*/
const getTrayById = () => {
return TrayIcon.getById(TRAY_ID);
};
/**
* 创建系统托盘图标
* 如果托盘图标已存在,则不会重复创建
*/
const createTrayIcon = async () => {
const tray = await getTrayById();
// 如果托盘已存在,直接返回
if (tray) return;
// 获取托盘菜单
const menu = await getTrayMenu();
// 根据操作系统选择不同的图标
const iconPath = isMac ? "assets/tray-mac.ico" : "assets/tray.ico";
const icon = await resolveResource(iconPath);
// 配置托盘选项
const options: TrayIconOptions = {
menu,
icon,
id: TRAY_ID,
iconAsTemplate: true, // 在macOS上允许系统根据主题调整图标颜色
};
// 创建新的托盘图标
return TrayIcon.new(options);
};
/**
* 获取托盘菜单配置
* 包含显示Coco、设置和退出应用等选项
*/
const getTrayMenu = async () => {
const items = await Promise.all([
// 显示Coco菜单项
MenuItem.new({
text: t("tray.showCoco"),
accelerator: showCocoShortcuts.join("+"), // 显示快捷键
action: () => {
show_coco() // 调用显示Coco的命令
},
}),
// 分隔线
PredefinedMenuItem.new({ item: "Separator" }),
// 设置菜单项
MenuItem.new({
text: t("tray.settings"),
// accelerator: "CommandOrControl+,", // 设置的快捷键(已注释)
action: () => {
show_settings() // 调用显示设置的命令
},
}),
// 分隔线
PredefinedMenuItem.new({ item: "Separator" }),
// 退出Coco菜单项
MenuItem.new({
text: t("tray.quitCoco"),
accelerator: "CommandOrControl+Q", // 退出的快捷键
action: () => {
exit(0); // 调用退出应用的命令
},
}),
]);
// 创建并返回菜单
return Menu.new({ items });
};
/**
* 更新托盘菜单
* 如果托盘不存在,则创建新的托盘
*/
const updateTrayMenu = async () => {
const tray = await getTrayById();
// 如果托盘不存在,创建新的托盘
if (!tray) {
return createTrayIcon();
}
// 获取最新的菜单配置
const menu = await getTrayMenu();
// 更新托盘菜单
tray.setMenu(menu);
};
};
Rust 实现
/src-tauri/src/tray.rs
rs
use std::sync::Mutex;
use tauri::{
AppHandle, CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu,
SystemTrayMenuItem, SystemTraySubmenu,
};
use once_cell::sync::Lazy;
// 使用静态变量存储快捷键
static SHOW_COCO_SHORTCUTS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
pub fn create_tray(app: &AppHandle) -> SystemTray {
let tray_menu = get_tray_menu(app);
// 根据平台选择不同的图标
let icon_path = if cfg!(target_os = "macos") {
"icons/tray-mac.ico"
} else {
"icons/tray.ico"
};
SystemTray::new().with_menu(tray_menu).with_icon(icon_path.into())
}
fn get_tray_menu(app: &AppHandle) -> SystemTrayMenu {
// 获取翻译函数 (在实际应用中,你需要实现国际化)
let t = |key: &str| -> String {
match key {
"tray.showCoco" => "显示 Coco".to_string(),
"tray.settings" => "设置".to_string(),
"tray.quitCoco" => "退出 Coco".to_string(),
_ => key.to_string(),
}
};
// 创建菜单项
let show_coco = CustomMenuItem::new("show_coco".to_string(), t("tray.showCoco"));
let settings = CustomMenuItem::new("settings".to_string(), t("tray.settings"));
let quit = CustomMenuItem::new("quit".to_string(), t("tray.quitCoco"));
// 创建菜单
SystemTrayMenu::new()
.add_item(show_coco)
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(settings)
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(quit)
}
pub fn handle_tray_event(app: &AppHandle, event: SystemTrayEvent) {
match event {
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
"show_coco" => {
// 显示主窗口
if let Some(window) = app.get_window("main") {
window.show().unwrap();
window.set_focus().unwrap();
}
}
"settings" => {
// 显示设置窗口
if let Some(window) = app.get_window("settings") {
window.show().unwrap();
window.set_focus().unwrap();
} else {
// 如果设置窗口不存在,创建一个新的
tauri::WindowBuilder::new(
app,
"settings",
tauri::WindowUrl::App("settings".into()),
)
.title("Coco 设置")
.build()
.unwrap();
}
}
"quit" => {
// 退出应用
app.exit(0);
}
_ => {}
},
_ => {}
}
}
// 更新托盘菜单
pub fn update_tray_menu(app: &AppHandle) {
let tray_handle = app.tray_handle();
let menu = get_tray_menu(app);
tray_handle.set_menu(menu).unwrap();
}
// 设置显示Coco的快捷键
pub fn set_show_coco_shortcuts(shortcuts: Vec<String>) {
let mut shortcuts_guard = SHOW_COCO_SHORTCUTS.lock().unwrap();
*shortcuts_guard = shortcuts;
}
效果
注意事项
- 快捷键冲突
- 全局快捷键可能与其他应用冲突,建议使用复杂组合(如
CmdOrCtrl+Shift+Q
)。
- 全局快捷键可能与其他应用冲突,建议使用复杂组合(如
- 跨平台修饰键
- 使用
CmdOrCtrl
自动适配系统(macOS 用Command
,Windows/Linux 用Control
)。
- 使用
- 权限问题
- 部分系统需用户授权快捷键(如 macOS 需在
Info.plist
添加权限描述)。
- 部分系统需用户授权快捷键(如 macOS 需在
- 组合键格式
- 支持
Ctrl
、Alt
、Shift
、Cmd
(macOS)或Super
(Windows 徽标键)。
- 支持
开源
最近,我正在基于 Tauri 开发一款项目,名为 Coco。目前已开源 ,项目仍在不断完善中,欢迎大家前往支持并为项目点亮免费的 star 🌟!
作为个人的第一个 Tauri 项目,开发过程中也是边探索边学习。希望能与志同道合的朋友一起交流经验、共同成长!
代码中如有问题或不足之处,期待小伙伴们的宝贵建议和指导!
- 官网: coco.rs/
- 前端仓库: github.com/infinilabs/...
- 服务端仓库: github.com/infinilabs/...
非常感谢您的支持与关注!