use ksni::menu::StandardItem; use ksni::{Category, Icon, ToolTip, Tray, TrayMethods}; use std::sync::mpsc; pub enum TrayCommand { Show, QuickAdd, LogExpense, LogIncome, Quit, } static TRAY_ICON_32: &[u8] = include_bytes!("../data/icons/tray-icon-32x32.argb"); static TRAY_ICON_48: &[u8] = include_bytes!("../data/icons/tray-icon-48x48.argb"); struct OutlayTray { sender: mpsc::Sender, } impl Tray for OutlayTray { fn id(&self) -> String { "outlay".into() } fn title(&self) -> String { "Outlay".into() } fn icon_name(&self) -> String { String::new() } fn icon_pixmap(&self) -> Vec { vec![ Icon { width: 32, height: 32, data: TRAY_ICON_32.to_vec(), }, Icon { width: 48, height: 48, data: TRAY_ICON_48.to_vec(), }, ] } fn category(&self) -> Category { Category::ApplicationStatus } fn tool_tip(&self) -> ToolTip { ToolTip { title: "Outlay - Personal Finance".into(), description: String::new(), icon_name: String::new(), icon_pixmap: Vec::new(), } } fn activate(&mut self, _x: i32, _y: i32) { self.sender.send(TrayCommand::Show).ok(); } fn menu(&self) -> Vec> { vec![ StandardItem { label: "Show Outlay".into(), activate: Box::new(|tray: &mut Self| { tray.sender.send(TrayCommand::Show).ok(); }), ..Default::default() } .into(), ksni::MenuItem::Separator, StandardItem { label: "Quick Add".into(), activate: Box::new(|tray: &mut Self| { tray.sender.send(TrayCommand::QuickAdd).ok(); }), ..Default::default() } .into(), StandardItem { label: "Log Expense".into(), activate: Box::new(|tray: &mut Self| { tray.sender.send(TrayCommand::LogExpense).ok(); }), ..Default::default() } .into(), StandardItem { label: "Log Income".into(), activate: Box::new(|tray: &mut Self| { tray.sender.send(TrayCommand::LogIncome).ok(); }), ..Default::default() } .into(), ksni::MenuItem::Separator, StandardItem { label: "Quit".into(), activate: Box::new(|tray: &mut Self| { tray.sender.send(TrayCommand::Quit).ok(); }), ..Default::default() } .into(), ] } } pub fn spawn_tray(sender: mpsc::Sender) { let tray = OutlayTray { sender }; std::thread::spawn(move || { let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .expect("Failed to create tokio runtime for tray"); rt.block_on(async { match tray.spawn().await { Ok(_handle) => { std::future::pending::<()>().await; } Err(e) => { eprintln!("[tray] Failed to register: {:?}", e); } } }); }); }