第 49 章 高级话题:Sidecar / 嵌入 Python / 原生菜单 / Webview 注入
本章目标
- Sidecar:把第三方可执行文件打进 App。
- Embedded Python:在 Tauri 里嵌入 Python 解释器(PyO3)。
- 原生菜单栏深入:动态菜单、快捷键、响应式。
- 向 WebView 注入 JS:做调试工具 / 注入脚本。
一、Sidecar
场景:需要调用某个 CLI(ffmpeg、yt-dlp、whisper.cpp)。
tauri.conf.json:
"bundle": {
"externalBin": ["bin/ffmpeg"]
}
Tauri CLI 会在构建时按平台复制 bin/ffmpeg-x86_64-apple-darwin 等变体。
运行时:
use tauri_plugin_shell::ShellExt;
let output = app.shell()
.sidecar("ffmpeg")?
.args(["-i", input, output_path])
.output()
.await?;
Capability 里要允许:
{ "identifier": "shell:allow-execute",
"allow": [{ "name": "ffmpeg", "sidecar": true, "args": true }] }
流式输出
let (mut rx, _child) = app.shell().sidecar("ffmpeg")?.args(args).spawn()?;
while let Some(ev) = rx.recv().await {
match ev {
CommandEvent::Stdout(line) => app.emit("ffmpeg:line", line)?,
CommandEvent::Terminated(t) => app.emit("ffmpeg:exit", t.code)?,
_ => {}
}
}
二、嵌入 Python
偶尔需要调 Python 生态(比如 librosa 分析波形)。有两条路:
2.1 sidecar python 子进程
简单直接,跨平台成本低。把一个 PyInstaller 打包后的可执行文件作为 sidecar。
2.2 PyO3 嵌入
pyo3 = { version = "0.22", features = ["auto-initialize"] }
use pyo3::prelude::*;
pub fn analyze_bpm(path: &str) -> anyhow::Result<f64> {
Python::with_gil(|py| {
let librosa = py.import_bound("librosa")?;
let (y, sr): (PyObject, f64) = librosa.call_method1("load", (path,))?.extract()?;
let bpm: f64 = librosa.call_method1("beat.beat_track", (y,))?.get_item(0)?.extract()?;
Ok(bpm)
})
}
注意:
- 发布时你得分发 Python 解释器(用
python-build-standalone),成本显著。 - macOS 的 Hardened Runtime + Python dylib 的签名头疼。
一般建议优先 sidecar,除非真的需要高频调用。
三、原生菜单进阶
动态菜单项
let recent = state.recent_files.lock().await.clone();
let mut builder = Submenu::builder(app, "文件");
for path in recent {
builder = builder.item(&MenuItem::with_id(app, format!("recent:{}", path), path, true, None::<&str>)?);
}
let menu = builder.build()?;
app.set_menu(menu)?;
菜单事件统一处理:
app.on_menu_event(|app, ev| {
let id = ev.id().as_ref();
if id.starts_with("recent:") {
let path = id.trim_start_matches("recent:");
let _ = app.emit("open_file", path);
}
});
响应当前状态(Checkable)
播放模式菜单打勾:
let shuffle = CheckMenuItem::with_id(app, "shuffle", "随机", true, mode == Shuffle, None::<&str>)?;
四、向 WebView 注入 JS
if let Some(w) = app.get_webview_window("main") {
w.eval("window.__APP_BUILD__ = 'nightly';")?;
}
高级:注入一个调试面板:
w.eval(include_str!("../assets/devtools.js"))?;
你甚至可以 hook console.log 上报到 Rust。
五、开发时打开 DevTools
#[cfg(debug_assertions)]
if let Some(w) = app.get_webview_window("main") { w.open_devtools(); }
六、自定义 WebView 设置
initialization_script:在所有页面加载前跑。user_agent:设定 UA。transparent:透明窗口。accept_first_mouse:macOS 首次点击不被吃掉。
WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into()))
.initialization_script("window.__TAURI_BRIDGE__ = true;")
.user_agent("CloudTone/1.0")
.build()?;
七、与 OS 深度集成
- macOS Dock 菜单:
TrayIconBuilder+MenuBuilder+set_dock_menu。 - Windows Jump List:通过
webview2/windows-rs原生 API。 - Linux 通知:
tauri-plugin-notification,MPRIS(第 37 章)。
八、性能剖析
- macOS Instruments:附加到
cloudtone进程,profile CPU、内存。 - Windows Performance Analyzer:ETW 跟踪。
- 前端:Chrome DevTools(
--remote-debugging-port=9222附加)。
本章小结
- Sidecar 比嵌入解释器简单得多。
- 原生菜单是桌面感的关键,动态 + Checkable 提升体验。
- 注入脚本是强力工具,慎用但必备。
下一章:求职与职业规划。