Add system tray icon with menu for agent

- Added tray-icon and muda crates for tray functionality
- Tray icon shows green circle when connected
- Menu displays: session code, machine name, End Session option
- End Session menu item cleanly terminates the agent
- Tray events processed in session main loop

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-28 16:06:16 -07:00
parent 8246d135f9
commit dea96bd300
5 changed files with 1256 additions and 56 deletions

View File

@@ -14,13 +14,14 @@ mod encoder;
mod input;
mod session;
mod transport;
mod tray;
pub mod proto {
include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
}
use anyhow::Result;
use tracing::{info, error, Level};
use tracing::{info, error, warn, Level};
use tracing_subscriber::FmtSubscriber;
#[cfg(windows)]
@@ -122,19 +123,51 @@ async fn run_agent(config: config::Config) -> Result<()> {
// Create session manager
let mut session = session::SessionManager::new(config.clone());
let is_support_session = config.support_code.is_some();
let hostname = config.hostname();
// Create tray icon
let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref()) {
Ok(t) => {
info!("Tray icon created");
Some(t)
}
Err(e) => {
warn!("Failed to create tray icon: {}. Continuing without tray.", e);
None
}
};
// Connect to server and run main loop
loop {
info!("Connecting to server...");
// Check if user requested exit via tray before connecting
if let Some(ref t) = tray {
if t.exit_requested() {
info!("Exit requested by user");
return Ok(());
}
}
match session.connect().await {
Ok(_) => {
info!("Connected to server");
// Run session until disconnect
if let Err(e) = session.run().await {
// Update tray status
if let Some(ref t) = tray {
t.update_status("Status: Connected");
}
// Run session until disconnect, passing tray for event processing
if let Err(e) = session.run_with_tray(tray.as_ref()).await {
let error_msg = e.to_string();
// Check if this is a user-initiated exit
if error_msg.contains("USER_EXIT") {
info!("Session ended by user");
return Ok(());
}
// Check if this is a cancellation
if error_msg.contains("SESSION_CANCELLED") {
info!("Session was cancelled by technician");
@@ -173,6 +206,14 @@ async fn run_agent(config: config::Config) -> Result<()> {
return Ok(());
}
// Check if user requested exit via tray
if let Some(ref t) = tray {
if t.exit_requested() {
info!("Exit requested by user");
return Ok(());
}
}
// Wait before reconnecting (only for persistent agent connections)
info!("Reconnecting in 5 seconds...");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;