#[allow(unused_imports)]
use std::sync::mpsc;
#[derive(Debug)]
#[allow(dead_code)]
pub enum TrayAction {
OpenDashboard,
Restart,
Stop,
Start,
CheckUpdate,
ViewLogs,
Quit,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum WrapperStatus {
Running,
Updating,
Stopped,
UpToDate,
UpdatedRestarting,
}
#[cfg(any(target_os = "windows", target_os = "macos"))]
mod platform {
use super::*;
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem};
use std::sync::mpsc as std_mpsc;
use tray_icon::{Icon, TrayIconBuilder};
const DASHBOARD_URL: &str = super::super::service::DASHBOARD_URL;
#[cfg(target_os = "windows")]
fn pump_platform_messages() -> bool {
use winapi::um::winuser::{
DispatchMessageW, PM_REMOVE, PeekMessageW, TranslateMessage, WM_QUIT,
};
unsafe {
let mut msg = std::mem::zeroed();
while PeekMessageW(&mut msg, std::ptr::null_mut(), 0, 0, PM_REMOVE) != 0 {
if msg.message == WM_QUIT {
return true;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
false
}
#[cfg(target_os = "macos")]
fn pump_platform_messages() -> bool {
false
}
fn build_icon() -> Result<Icon, tray_icon::BadIcon> {
let rgba = include_bytes!("assets/freenet_256x256.rgba").to_vec();
Icon::from_rgba(rgba, 256, 256)
}
fn open_url(url: &str) {
#[cfg(target_os = "windows")]
{
std::process::Command::new("cmd")
.args(["/c", "start", url])
.spawn()
.ok();
}
#[cfg(target_os = "macos")]
{
std::process::Command::new("open").arg(url).spawn().ok();
}
}
pub fn run_tray_event_loop(
action_tx: std_mpsc::Sender<TrayAction>,
status_rx: std_mpsc::Receiver<WrapperStatus>,
version: &str,
) {
let menu = Menu::new();
let open_dashboard = MenuItem::new("Open Dashboard", true, None);
let separator1 = PredefinedMenuItem::separator();
let status_item = MenuItem::new("Status: Starting...", false, None);
let version_item = MenuItem::new(format!("Version: {version}"), false, None);
let separator2 = PredefinedMenuItem::separator();
let stop_item = MenuItem::new("Stop", true, None);
let start_item = MenuItem::new("Start", false, None);
let restart_item = MenuItem::new("Restart", true, None);
let check_update = MenuItem::new("Check for Updates", true, None);
let view_logs = MenuItem::new("View Logs", true, None);
let separator3 = PredefinedMenuItem::separator();
let quit_item = MenuItem::new("Quit", true, None);
menu.append(&open_dashboard).ok();
menu.append(&separator1).ok();
menu.append(&status_item).ok();
menu.append(&version_item).ok();
menu.append(&separator2).ok();
menu.append(&stop_item).ok();
menu.append(&start_item).ok();
menu.append(&restart_item).ok();
menu.append(&check_update).ok();
menu.append(&view_logs).ok();
menu.append(&separator3).ok();
menu.append(&quit_item).ok();
let icon = match build_icon() {
Ok(i) => i,
Err(e) => {
eprintln!("Failed to build tray icon: {e}. Running without tray.");
return;
}
};
let _tray = match TrayIconBuilder::new()
.with_menu(Box::new(menu))
.with_tooltip(format!("Freenet {version} - Starting..."))
.with_icon(icon)
.build()
{
Ok(t) => t,
Err(e) => {
eprintln!("Failed to create tray icon: {e}. Running without tray.");
return;
}
};
let menu_rx = MenuEvent::receiver();
let open_dashboard_id = open_dashboard.id().clone();
let stop_id = stop_item.id().clone();
let start_id = start_item.id().clone();
let restart_id = restart_item.id().clone();
let check_update_id = check_update.id().clone();
let view_logs_id = view_logs.id().clone();
let quit_id = quit_item.id().clone();
loop {
if pump_platform_messages() {
action_tx.send(TrayAction::Quit).ok();
break;
}
if let Ok(event) = menu_rx.try_recv() {
let action = if event.id == open_dashboard_id {
open_url(DASHBOARD_URL);
None
} else if event.id == stop_id {
Some(TrayAction::Stop)
} else if event.id == start_id {
Some(TrayAction::Start)
} else if event.id == restart_id {
Some(TrayAction::Restart)
} else if event.id == check_update_id {
Some(TrayAction::CheckUpdate)
} else if event.id == view_logs_id {
Some(TrayAction::ViewLogs)
} else if event.id == quit_id {
action_tx.send(TrayAction::Quit).ok();
break;
} else {
None
};
if let Some(action) = action {
action_tx.send(action).ok();
}
}
if let Ok(status) = status_rx.try_recv() {
let status_text = match &status {
WrapperStatus::Running => "Running",
WrapperStatus::Updating => "Checking for updates...",
WrapperStatus::Stopped => "Stopped",
WrapperStatus::UpToDate => "Up to date",
WrapperStatus::UpdatedRestarting => "Updated! Restarting...",
};
status_item.set_text(format!("Status: {status_text}"));
_tray
.set_tooltip(Some(format!("Freenet {version} - {status_text}")))
.ok();
let is_running = matches!(
status,
WrapperStatus::Running | WrapperStatus::UpToDate | WrapperStatus::Updating
);
stop_item.set_enabled(is_running);
start_item.set_enabled(!is_running);
restart_item.set_enabled(is_running);
if matches!(status, WrapperStatus::UpdatedRestarting) {
std::thread::sleep(std::time::Duration::from_secs(1));
action_tx.send(TrayAction::Quit).ok();
break;
}
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
mod platform {
use super::*;
use std::sync::mpsc as std_mpsc;
pub fn run_tray_event_loop(
_action_tx: std_mpsc::Sender<TrayAction>,
_status_rx: std_mpsc::Receiver<WrapperStatus>,
_version: &str,
) {
}
}
#[allow(unused_imports, dead_code)]
pub use platform::run_tray_event_loop;
#[allow(dead_code)]
pub fn open_log_file() {
use freenet::tracing::tracer::get_log_dir;
let Some(log_dir) = get_log_dir() else {
eprintln!("Could not determine log directory");
return;
};
let latest = super::service::find_latest_log_file(&log_dir, "freenet");
match latest {
Some(path) => {
#[cfg(target_os = "windows")]
{
std::process::Command::new("notepad")
.arg(&path)
.spawn()
.ok();
}
#[cfg(target_os = "macos")]
{
std::process::Command::new("open").arg(&path).spawn().ok();
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
{
drop(std::process::Command::new("xdg-open").arg(&path).spawn());
}
}
None => {
eprintln!("No log files found in {}", log_dir.display());
}
}
}