Tauri(十六)——为托盘菜单添加快捷键提示

背景

刚安装的应用,用户可能对快捷键启动不熟悉,记不住快捷键,导致只能反复通过托盘菜单启动。而且,应用失去焦点后会自动隐藏,这就需要频繁去点击托盘菜单,造成糟糕的用户体验。

那么,如何帮助用户快速记住快捷键呢?一个有效的方法是,在托盘菜单中直接显示快捷键。用户频繁看到后,自然就能记住了。

开造

在 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;
}

效果

注意事项

  1. 快捷键冲突
    • 全局快捷键可能与其他应用冲突,建议使用复杂组合(如 CmdOrCtrl+Shift+Q)。
  2. 跨平台修饰键
    • 使用 CmdOrCtrl 自动适配系统(macOS 用 Command,Windows/Linux 用 Control)。
  3. 权限问题
    • 部分系统需用户授权快捷键(如 macOS 需在 Info.plist 添加权限描述)。
  4. 组合键格式
    • 支持 CtrlAltShiftCmd(macOS)或 Super(Windows 徽标键)。

开源

最近,我正在基于 Tauri 开发一款项目,名为 Coco。目前已开源 ,项目仍在不断完善中,欢迎大家前往支持并为项目点亮免费的 star 🌟!

作为个人的第一个 Tauri 项目,开发过程中也是边探索边学习。希望能与志同道合的朋友一起交流经验、共同成长!

代码中如有问题或不足之处,期待小伙伴们的宝贵建议和指导!

非常感谢您的支持与关注!

相关推荐
崔庆才丨静觅15 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606115 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了15 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅15 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅16 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅16 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment16 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅17 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊17 小时前
jwt介绍
前端
爱敲代码的小鱼17 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax