feat(native): Linux parity + autostart + tray DND (P6-1; no macOS)
Rounds out the native app on Linux (Windows features kept; macOS stays no-op): - power.rs: no-sleep during calls on Linux via a zbus org.freedesktop.ScreenSaver Inhibit/UnInhibit (cookie held in ScreenSaverInhibit managed state). - set_badge_count: Linux launcher badge via the Unity com.canonical.Unity.LauncherEntry.Update D-Bus signal (best-effort; app_uri = cinny.desktop per Tauri's mainBinaryName naming). - tauri-plugin-autostart registered (+ autostart:allow-enable/disable/is-enabled capabilities); web toggles it from Settings. - Tray "Do Not Disturb" CheckMenuItem → emits lotus-dnd-changed to the web, which ORs it into the notification quiet-gate. zbus 5 (Linux target dep; blocking-api default). CI-compile-verified (windows+linux); reviewer confirmed no build-breakers. Runtime to check on Linux: DND toggle polarity, badge .desktop id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+73
-3
@@ -4,7 +4,7 @@
|
||||
)]
|
||||
|
||||
use tauri::{
|
||||
menu::{Menu, MenuItem, PredefinedMenuItem},
|
||||
menu::{CheckMenuItem, Menu, MenuItem, PredefinedMenuItem},
|
||||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||
webview::{NewWindowResponse, PageLoadEvent, WebviewWindowBuilder},
|
||||
Manager, WebviewUrl,
|
||||
@@ -145,6 +145,11 @@ async fn install_update(app: tauri::AppHandle) -> Result<(), String> {
|
||||
|
||||
#[tauri::command]
|
||||
fn set_badge_count(count: u32, window: tauri::Window) -> Result<(), String> {
|
||||
// `window` is only consulted on Windows (needs the HWND for the taskbar
|
||||
// overlay). Bind it elsewhere so cross-platform builds don't warn.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let _ = &window;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use windows::{
|
||||
@@ -313,6 +318,44 @@ fn set_badge_count(count: u32, window: tauri::Window) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linux (P6-1): emit the Unity `LauncherEntry.Update` broadcast signal so
|
||||
// launchers/docks that speak the com.canonical.Unity.LauncherEntry protocol
|
||||
// (GNOME "Dash to Dock", KDE task manager, Unity, etc.) render a count
|
||||
// badge on the app's launcher icon. Best-effort: any D-Bus failure is
|
||||
// logged and swallowed so a headless/unsupported environment never breaks
|
||||
// the badge call.
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::collections::HashMap;
|
||||
use zbus::zvariant::Value;
|
||||
|
||||
// application://<desktop-file-id>.desktop — the installed .desktop
|
||||
// basename. Tauri v2's Linux bundler names it after mainBinaryName
|
||||
// ("cinny"), NOT the identifier, so the file is `cinny.desktop`. If the
|
||||
// badge doesn't attach at runtime, verify against
|
||||
// /usr/share/applications/ and adjust.
|
||||
let app_uri = "application://cinny.desktop";
|
||||
let mut props: HashMap<&str, Value> = HashMap::new();
|
||||
props.insert("count", Value::from(count as i64));
|
||||
props.insert("count-visible", Value::from(count > 0));
|
||||
|
||||
match zbus::blocking::Connection::session() {
|
||||
Ok(conn) => {
|
||||
if let Err(e) = conn.emit_signal(
|
||||
None::<&str>,
|
||||
"/com/canonical/unity/launcherentry/lotuschat",
|
||||
"com.canonical.Unity.LauncherEntry",
|
||||
"Update",
|
||||
&(app_uri, props),
|
||||
) {
|
||||
eprintln!("badge: Unity LauncherEntry emit failed: {e}");
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("badge: D-Bus session connection failed: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -441,6 +484,14 @@ pub fn run() {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
|
||||
// P6-1 launch-on-login. The web side drives it via the plugin's own
|
||||
// `plugin:autostart|enable`/`disable`/`is-enabled` commands (no wrapper
|
||||
// command). The MacosLauncher arg is mandatory by the plugin API even
|
||||
// though macOS is out of scope; `None` = no extra launch args.
|
||||
builder = builder.plugin(tauri_plugin_autostart::init(
|
||||
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
builder
|
||||
@@ -451,17 +502,36 @@ pub fn run() {
|
||||
// panicking at startup (the tray simply isn't created).
|
||||
if let Some(base_icon) = app.default_window_icon().cloned() {
|
||||
let open_item = MenuItem::with_id(app, "open", "Open Lotus Chat", true, None::<&str>)?;
|
||||
// P6-1: Do Not Disturb toggle. The CheckMenuItem auto-flips its
|
||||
// own checkstate on click; we read the new state and push it to
|
||||
// the web client, which owns the actual notification-muting.
|
||||
let dnd_item =
|
||||
CheckMenuItem::with_id(app, "dnd", "Do Not Disturb", true, false, None::<&str>)?;
|
||||
let quit_item = MenuItem::with_id(app, "quit", "Quit Lotus Chat", true, None::<&str>)?;
|
||||
let separator = PredefinedMenuItem::separator(app)?;
|
||||
let tray_menu = Menu::with_items(app, &[&open_item, &separator, &quit_item])?;
|
||||
let tray_menu =
|
||||
Menu::with_items(app, &[&open_item, &dnd_item, &separator, &quit_item])?;
|
||||
// Clone the handle into the menu-event closure so we can query
|
||||
// is_checked() after the auto-toggle. CheckMenuItem is a cheap
|
||||
// clonable handle to the same underlying menu item.
|
||||
let dnd_for_event = dnd_item.clone();
|
||||
let tray = TrayIconBuilder::with_id("main-tray")
|
||||
.icon(base_icon.clone())
|
||||
.tooltip("Lotus Chat")
|
||||
.menu(&tray_menu)
|
||||
.show_menu_on_left_click(false)
|
||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||
.on_menu_event(move |app, event| match event.id.as_ref() {
|
||||
"open" => show_main(app),
|
||||
"quit" => app.exit(0),
|
||||
"dnd" => {
|
||||
let checked = dnd_for_event.is_checked().unwrap_or(false);
|
||||
native::emit_to_web(
|
||||
app,
|
||||
"lotus-dnd-changed",
|
||||
&serde_json::to_string(&serde_json::json!({ "active": checked }))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
|
||||
Reference in New Issue
Block a user