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

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

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

相关推荐
水银嘻嘻2 分钟前
web 自动化之 Selenium 元素定位和浏览器操作
前端·selenium·自动化
GanGuaGua27 分钟前
CSS:元素显示模式与背景
前端·javascript·css·html
一个会的不多的人29 分钟前
C# NX二次开发:判断两个体是否干涉和获取系统日志的UFUN函数
前端·javascript·html
小离a_a34 分钟前
uniapp tabBar 中设置“custom“: true 在H5和app中无效解决办法
前端·uni-app
travel_wsy1 小时前
webrtc 视频直播
前端·vue.js·音视频·webrtc
zybsjn1 小时前
开发 Chrome 扩展中的侧边栏图标设置实录(Manifest V3)
前端·chrome
weixin_428498492 小时前
在Star-CCM+中实现UDF并引用场数据和网格数据
java·前端
小猫猫改bug2 小时前
threejs 添加css3d标签 vue3
前端·javascript·css3
前端小巷子2 小时前
CSS3 过渡与动画
前端·css·css3
m0_zj3 小时前
58.[前端开发-前端工程化]Day05-webpack-Git安装-配置-Git命令
前端·webpack·node.js