feat: refactor sgclaw around zeroclaw compat runtime
This commit is contained in:
25
third_party/zeroclaw/apps/tauri/src/tray/events.rs
vendored
Normal file
25
third_party/zeroclaw/apps/tauri/src/tray/events.rs
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
//! Tray menu event handling.
|
||||
|
||||
use tauri::{menu::MenuEvent, AppHandle, Manager, Runtime};
|
||||
|
||||
pub fn handle_menu_event<R: Runtime>(app: &AppHandle<R>, event: MenuEvent) {
|
||||
match event.id().as_ref() {
|
||||
"show" => show_main_window(app, None),
|
||||
"chat" => show_main_window(app, Some("/agent")),
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_main_window<R: Runtime>(app: &AppHandle<R>, navigate_to: Option<&str>) {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
if let Some(path) = navigate_to {
|
||||
let script = format!("window.location.hash = '{path}'");
|
||||
let _ = window.eval(&script);
|
||||
}
|
||||
}
|
||||
}
|
||||
105
third_party/zeroclaw/apps/tauri/src/tray/icon.rs
vendored
Normal file
105
third_party/zeroclaw/apps/tauri/src/tray/icon.rs
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
//! Tray icon management — swap icon based on connection/agent status.
|
||||
|
||||
use crate::state::AgentStatus;
|
||||
use tauri::image::Image;
|
||||
|
||||
/// Embedded tray icon PNGs (22x22, RGBA).
|
||||
const ICON_IDLE: &[u8] = include_bytes!("../../icons/tray-idle.png");
|
||||
const ICON_WORKING: &[u8] = include_bytes!("../../icons/tray-working.png");
|
||||
const ICON_ERROR: &[u8] = include_bytes!("../../icons/tray-error.png");
|
||||
const ICON_DISCONNECTED: &[u8] = include_bytes!("../../icons/tray-disconnected.png");
|
||||
|
||||
/// Select the appropriate tray icon for the current state.
|
||||
pub fn icon_for_state(connected: bool, status: AgentStatus) -> Image<'static> {
|
||||
let bytes: &[u8] = if !connected {
|
||||
ICON_DISCONNECTED
|
||||
} else {
|
||||
match status {
|
||||
AgentStatus::Idle => ICON_IDLE,
|
||||
AgentStatus::Working => ICON_WORKING,
|
||||
AgentStatus::Error => ICON_ERROR,
|
||||
}
|
||||
};
|
||||
Image::from_bytes(bytes).expect("embedded tray icon is a valid PNG")
|
||||
}
|
||||
|
||||
/// Tooltip text for the current state.
|
||||
pub fn tooltip_for_state(connected: bool, status: AgentStatus) -> &'static str {
|
||||
if !connected {
|
||||
return "ZeroClaw — Disconnected";
|
||||
}
|
||||
match status {
|
||||
AgentStatus::Idle => "ZeroClaw — Idle",
|
||||
AgentStatus::Working => "ZeroClaw — Working",
|
||||
AgentStatus::Error => "ZeroClaw — Error",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn icon_disconnected_when_not_connected() {
|
||||
// Should not panic — icon bytes are valid PNGs.
|
||||
let _img = icon_for_state(false, AgentStatus::Idle);
|
||||
let _img = icon_for_state(false, AgentStatus::Working);
|
||||
let _img = icon_for_state(false, AgentStatus::Error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn icon_connected_variants() {
|
||||
let _idle = icon_for_state(true, AgentStatus::Idle);
|
||||
let _working = icon_for_state(true, AgentStatus::Working);
|
||||
let _error = icon_for_state(true, AgentStatus::Error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tooltip_disconnected() {
|
||||
assert_eq!(
|
||||
tooltip_for_state(false, AgentStatus::Idle),
|
||||
"ZeroClaw — Disconnected"
|
||||
);
|
||||
// Agent status is irrelevant when disconnected.
|
||||
assert_eq!(
|
||||
tooltip_for_state(false, AgentStatus::Working),
|
||||
"ZeroClaw — Disconnected"
|
||||
);
|
||||
assert_eq!(
|
||||
tooltip_for_state(false, AgentStatus::Error),
|
||||
"ZeroClaw — Disconnected"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tooltip_connected_variants() {
|
||||
assert_eq!(
|
||||
tooltip_for_state(true, AgentStatus::Idle),
|
||||
"ZeroClaw — Idle"
|
||||
);
|
||||
assert_eq!(
|
||||
tooltip_for_state(true, AgentStatus::Working),
|
||||
"ZeroClaw — Working"
|
||||
);
|
||||
assert_eq!(
|
||||
tooltip_for_state(true, AgentStatus::Error),
|
||||
"ZeroClaw — Error"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedded_icons_are_valid_png() {
|
||||
// Verify the PNG signature (first 8 bytes) of each embedded icon.
|
||||
let png_sig: &[u8] = &[0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];
|
||||
assert!(ICON_IDLE.starts_with(png_sig), "idle icon not valid PNG");
|
||||
assert!(
|
||||
ICON_WORKING.starts_with(png_sig),
|
||||
"working icon not valid PNG"
|
||||
);
|
||||
assert!(ICON_ERROR.starts_with(png_sig), "error icon not valid PNG");
|
||||
assert!(
|
||||
ICON_DISCONNECTED.starts_with(png_sig),
|
||||
"disconnected icon not valid PNG"
|
||||
);
|
||||
}
|
||||
}
|
||||
19
third_party/zeroclaw/apps/tauri/src/tray/menu.rs
vendored
Normal file
19
third_party/zeroclaw/apps/tauri/src/tray/menu.rs
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
//! Tray menu construction.
|
||||
|
||||
use tauri::{
|
||||
menu::{Menu, MenuItemBuilder, PredefinedMenuItem},
|
||||
App, Runtime,
|
||||
};
|
||||
|
||||
pub fn create_tray_menu<R: Runtime>(app: &App<R>) -> Result<Menu<R>, tauri::Error> {
|
||||
let show = MenuItemBuilder::with_id("show", "Show Dashboard").build(app)?;
|
||||
let chat = MenuItemBuilder::with_id("chat", "Agent Chat").build(app)?;
|
||||
let sep1 = PredefinedMenuItem::separator(app)?;
|
||||
let status = MenuItemBuilder::with_id("status", "Status: Checking...")
|
||||
.enabled(false)
|
||||
.build(app)?;
|
||||
let sep2 = PredefinedMenuItem::separator(app)?;
|
||||
let quit = MenuItemBuilder::with_id("quit", "Quit ZeroClaw").build(app)?;
|
||||
|
||||
Menu::with_items(app, &[&show, &chat, &sep1, &status, &sep2, &quit])
|
||||
}
|
||||
34
third_party/zeroclaw/apps/tauri/src/tray/mod.rs
vendored
Normal file
34
third_party/zeroclaw/apps/tauri/src/tray/mod.rs
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
//! System tray integration for ZeroClaw Desktop.
|
||||
|
||||
pub mod events;
|
||||
pub mod icon;
|
||||
pub mod menu;
|
||||
|
||||
use tauri::{
|
||||
tray::{TrayIcon, TrayIconBuilder, TrayIconEvent},
|
||||
App, Manager, Runtime,
|
||||
};
|
||||
|
||||
/// Set up the system tray icon and menu.
|
||||
pub fn setup_tray<R: Runtime>(app: &App<R>) -> Result<TrayIcon<R>, tauri::Error> {
|
||||
let menu = menu::create_tray_menu(app)?;
|
||||
|
||||
TrayIconBuilder::with_id("main")
|
||||
.tooltip("ZeroClaw — Disconnected")
|
||||
.icon(icon::icon_for_state(false, crate::state::AgentStatus::Idle))
|
||||
.menu(&menu)
|
||||
.show_menu_on_left_click(false)
|
||||
.on_menu_event(events::handle_menu_event)
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let TrayIconEvent::Click { button, .. } = event {
|
||||
if button == tauri::tray::MouseButton::Left {
|
||||
let app = tray.app_handle();
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)
|
||||
}
|
||||
Reference in New Issue
Block a user