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 项目,开发过程中也是边探索边学习。希望能与志同道合的朋友一起交流经验、共同成长!

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

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

相关推荐
大土豆的bug记录5 小时前
鸿蒙进行视频上传,使用 request.uploadFile方法
开发语言·前端·华为·arkts·鸿蒙·arkui
maybe02095 小时前
前端表格数据导出Excel文件方法,列自适应宽度、增加合计、自定义文件名称
前端·javascript·excel·js·大前端
HBR666_5 小时前
菜单(路由)权限&按钮权限&路由进度条
前端·vue
A-Kamen5 小时前
深入理解 HTML5 Web Workers:提升网页性能的关键技术解析
前端·html·html5
锋小张7 小时前
a-date-picker 格式化日期格式 YYYY-MM-DD HH:mm:ss
前端·javascript·vue.js
鱼樱前端7 小时前
前端模块化开发标准全面解析--ESM获得绝杀
前端·javascript
yanlele7 小时前
前端面试第 75 期 - 前端质量问题专题(11 道题)
前端·javascript·面试
前端小白۞9 小时前
el-date-picker时间范围 编辑回显后不能修改问题
前端·vue.js·elementui
拉不动的猪9 小时前
刷刷题44(uniapp-中级)
前端·javascript·面试
Spider Cat 蜘蛛猫9 小时前
chrome插件开发之API解析-chrome.scripting.executeScript()
前端·chrome