Tauri插件开发:与系统API交互

Tauri 的插件系统能让你用 Rust 封装系统级能力,然后在前端通过 JS 调用。这篇介绍怎么从零写一个 Tauri 插件,实现系统通知、托盘、窗口管理等功能。

Tauri 插件架构

Tauri 插件本质上是一个实现了 tauri::plugin::Plugin trait 的 Rust struct。一个插件可以:

  • 注册 Tauri commands(供前端 JS 调用)
  • 监听应用生命周期事件(setup、on_event 等)
  • 管理自己的状态

插件的基本骨架如下:

use tauri::{
    plugin::{Builder, TauriPlugin},
    Runtime, Manager,
};

pub fn init<R: Runtime>() -> TauriPlugin<R> {
    Builder::new("my-plugin")
        .invoke_handler(tauri::generate_handler![
            notify_send,
            window_set_title,
        ])
        .setup(|app| {
            // 插件初始化逻辑
            println!("my-plugin initialized");
            Ok(())
        })
        .build()
}

在主应用中注册:

fn main() {
    tauri::Builder::default()
        .plugin(my_plugin::init())
        .run(tauri::generate_context!())
        .expect("error running app");
}

调用系统通知 API

notify-rust crate 可以在桌面发系统通知。先在插件的 Cargo.toml 加依赖:

[dependencies]
notify-rust = "4"

然后写 command:

use notify_rust::Notification;

#[tauri::command]
fn notify_send(title: String, body: String) -> Result<(), String> {
    Notification::new()
        .summary(&title)
        .body(&body)
        .icon("dialog-information")
        .timeout(5000)
        .show()
        .map_err(|e| e.to_string())?;
    Ok(())
}

前端调用:

import { invoke } from '@tauri-apps/api/tauri';

async function sendNotification(title: string, body: string) {
  await invoke('notify_send', { title, body });
}

系统托盘

Tauri 内建了系统托盘支持,但通过插件可以做更灵活的控制:

use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu, SystemTrayEvent};

pub fn create_tray() -> SystemTray {
    let show = CustomMenuItem::new("show".to_string(), "显示窗口");
    let hide = CustomMenuItem::new("hide".to_string(), "隐藏窗口");
    let quit = CustomMenuItem::new("quit".to_string(), "退出");

    let menu = SystemTrayMenu::new()
        .add_item(show)
        .add_item(hide)
        .add_native_item(tauri::SystemTrayMenuItem::Separator)
        .add_item(quit);

    SystemTray::new().with_menu(menu)
}

pub fn handle_tray_event<R: Runtime>(app: &tauri::AppHandle<R>, event: SystemTrayEvent) {
    match event {
        SystemTrayEvent::MenuItemClick { id, .. } => {
            match id.as_str() {
                "show" => {
                    let window = app.get_window("main").unwrap();
                    window.show().unwrap();
                    window.set_focus().unwrap();
                }
                "hide" => {
                    let window = app.get_window("main").unwrap();
                    window.hide().unwrap();
                }
                "quit" => std::process::exit(0),
                _ => {}
            }
        }
        SystemTrayEvent::DoubleClick { .. } => {
            let window = app.get_window("main").unwrap();
            window.show().unwrap();
            window.set_focus().unwrap();
        }
        _ => {}
    }
}

窗口管理

Tauri 的 WindowBuilder 可以在运行时动态创建窗口。封装成插件 command 后,前端就能按需开新窗口:

#[tauri::command]
async fn window_create<R: Runtime>(
    app: tauri::AppHandle<R>,
    label: String,
    title: String,
    url: String,
    width: f64,
    height: f64,
) -> Result<(), String> {
    tauri::WindowBuilder::new(&app, &label, tauri::WindowUrl::App(url.into()))
        .title(&title)
        .inner_size(width, height)
        .center()
        .build()
        .map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
async fn window_set_title<R: Runtime>(
    app: tauri::AppHandle<R>,
    label: String,
    title: String,
) -> Result<(), String> {
    let win = app.get_window(&label).ok_or("Window not found")?;
    win.set_title(&title).map_err(|e| e.to_string())?;
    Ok(())
}

把这些能力包装成插件之后,前端可以非常方便地调用系统级功能,而不需要关心底层平台差异。Tauri 的插件机制设计得很干净,Rust 端处理系统交互、前端只管调用,职责分离很清晰。