AI models shared registry + Code cleanup + Better async handling + Some fixes, etc
This commit is contained in:
parent
a8ff3442ff
commit
520b98143f
62 changed files with 1683 additions and 1239 deletions
|
|
@ -1,84 +1,64 @@
|
|||
mod menu;
|
||||
|
||||
use tray_icon::{
|
||||
menu::{AboutMetadata, Menu, MenuEvent, MenuItem, PredefinedMenuItem},
|
||||
TrayIconBuilder, TrayIconEvent,
|
||||
menu::MenuEvent,
|
||||
TrayIconBuilder,
|
||||
};
|
||||
use winit::event_loop::{ControlFlow, EventLoopBuilder};
|
||||
use image;
|
||||
use std::process::Command;
|
||||
|
||||
#[cfg(target_os="windows")]
|
||||
use winit::platform::windows::EventLoopBuilderExtWindows;
|
||||
|
||||
use jarvis_core::{config, i18n, ipc::{self, IpcEvent}};
|
||||
use jarvis_core::{config, i18n, voices, ipc::{self, IpcEvent}, SettingsManager};
|
||||
|
||||
const TRAY_ICON_BYTES: &[u8] = include_bytes!("../../../resources/icons/32x32.png");
|
||||
|
||||
pub fn init_blocking() {
|
||||
// load tray icon
|
||||
//let icon_path = format!("{}/../../resources/icons/{}", env!("CARGO_MANIFEST_DIR"), config::TRAY_ICON);
|
||||
//let icon = load_icon(std::path::Path::new(&icon_path));
|
||||
pub fn init_blocking(settings: SettingsManager) {
|
||||
let icon = load_icon_from_bytes(TRAY_ICON_BYTES);
|
||||
|
||||
// form tray menu
|
||||
// let tray_menu = Menu::with_items(&[
|
||||
// &MenuItem::new("Перезапуск", true, None),
|
||||
// &MenuItem::new("Настройки", true, None),
|
||||
// &MenuItem::new("Выход", true, None),
|
||||
// ])
|
||||
// .unwrap();
|
||||
|
||||
let tray_menu = Menu::with_items(&[
|
||||
&MenuItem::with_id("restart", i18n::t("tray-restart"), true, None),
|
||||
&MenuItem::with_id("settings", i18n::t("tray-settings"), true, None),
|
||||
&MenuItem::with_id("exit", i18n::t("tray-exit"), true, None),
|
||||
]).unwrap();
|
||||
// build menu with settings submenus
|
||||
let tray_menu = menu::build(&settings);
|
||||
let menu::TrayMenu { menu, state: tray_state } = tray_menu;
|
||||
|
||||
let _tray_icon = TrayIconBuilder::new()
|
||||
.with_menu(Box::new(tray_menu))
|
||||
.with_menu(Box::new(menu))
|
||||
.with_tooltip(i18n::t("tray-tooltip"))
|
||||
.with_icon(icon)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let menu_channel = MenuEvent::receiver();
|
||||
// let tray_channel = TrayIconEvent::receiver();
|
||||
|
||||
// @TODO: Test on Linux
|
||||
// We need gtk for the tray icon to show up, we need to initialize gtk and create the tray_icon
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
gtk::init().unwrap();
|
||||
glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
|
||||
if let Ok(event) = menu_channel.try_recv() {
|
||||
handle_menu_event(&event);
|
||||
handle_menu_event(&event, &settings, &tray_state);
|
||||
}
|
||||
glib::ControlFlow::Continue
|
||||
});
|
||||
gtk::main();
|
||||
}
|
||||
|
||||
// @TODO: Test on MacOS
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// macOS needs proper run loop - tao or winit on main thread
|
||||
use winit::event_loop::{EventLoop, ControlFlow};
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
event_loop.run(move |_event, elwt| {
|
||||
elwt.set_control_flow(ControlFlow::Wait);
|
||||
if let Ok(event) = menu_channel.try_recv() {
|
||||
handle_menu_event(&event);
|
||||
handle_menu_event(&event, &settings, &tray_state);
|
||||
}
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// simple polling works on Windows
|
||||
loop {
|
||||
if let Ok(event) = menu_channel.try_recv() {
|
||||
handle_menu_event(&event);
|
||||
handle_menu_event(&event, &settings, &tray_state);
|
||||
}
|
||||
|
||||
// pump Windows messages
|
||||
|
|
@ -101,8 +81,65 @@ pub fn init_blocking() {
|
|||
info!("Tray initialized.");
|
||||
}
|
||||
|
||||
fn handle_menu_event(event: &MenuEvent) {
|
||||
match event.id.0.as_str() {
|
||||
fn handle_menu_event(event: &MenuEvent, settings: &SettingsManager, tray_state: &menu::TrayState) {
|
||||
let id = event.id.0.as_str();
|
||||
|
||||
// -- radio group: "set:key:value"
|
||||
if let Some(rest) = id.strip_prefix("set:") {
|
||||
if let Some((key, value)) = rest.split_once(':') {
|
||||
match settings.write(key, value) {
|
||||
Ok(()) => {
|
||||
info!("Tray: {} = {}", key, value);
|
||||
|
||||
// update check marks in the radio group
|
||||
for group in &tray_state.radio_groups {
|
||||
if group.setting_key == key {
|
||||
group.select(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// apply side effects
|
||||
match key {
|
||||
"language" => {
|
||||
i18n::set_language(value);
|
||||
}
|
||||
"assistant_voice" => {
|
||||
voices::set_current_voice(value);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Tray: failed to set {} = {}: {}", key, value, e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// -- toggle: "toggle:key"
|
||||
if let Some(key) = id.strip_prefix("toggle:") {
|
||||
match key {
|
||||
"gain_normalizer" => {
|
||||
// CheckMenuItem auto-toggles on click, just read the new state
|
||||
let new_val = tray_state.gain_toggle.is_checked();
|
||||
let val_str = if new_val { "true" } else { "false" };
|
||||
if let Err(e) = settings.write(key, val_str) {
|
||||
warn!("Tray: failed to toggle {}: {}", key, e);
|
||||
// revert visual state on error
|
||||
tray_state.gain_toggle.set_checked(!new_val);
|
||||
} else {
|
||||
info!("Tray: {} = {}", key, val_str);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// -- action items
|
||||
match id {
|
||||
"exit" => std::process::exit(0),
|
||||
"restart" => {
|
||||
info!("Restarting from tray menu...");
|
||||
|
|
@ -116,6 +153,8 @@ fn handle_menu_event(event: &MenuEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
// HELPERS
|
||||
|
||||
fn load_icon_from_bytes(bytes: &[u8]) -> tray_icon::Icon {
|
||||
let image = image::load_from_memory(bytes)
|
||||
.expect("Failed to load icon")
|
||||
|
|
@ -125,20 +164,7 @@ fn load_icon_from_bytes(bytes: &[u8]) -> tray_icon::Icon {
|
|||
tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to create icon")
|
||||
}
|
||||
|
||||
fn load_icon(path: &std::path::Path) -> tray_icon::Icon {
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
let image = image::open(path)
|
||||
.expect("Failed to open icon path")
|
||||
.into_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let rgba = image.into_raw();
|
||||
(rgba, width, height)
|
||||
};
|
||||
tray_icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
|
||||
}
|
||||
|
||||
fn restart_app() {
|
||||
// get current executable path
|
||||
let exe_path = match std::env::current_exe() {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
|
|
@ -147,7 +173,6 @@ fn restart_app() {
|
|||
}
|
||||
};
|
||||
|
||||
// spawn new instance
|
||||
match Command::new(&exe_path).spawn() {
|
||||
Ok(_) => {
|
||||
info!("Spawned new instance, exiting current...");
|
||||
|
|
@ -160,13 +185,10 @@ fn restart_app() {
|
|||
}
|
||||
|
||||
fn open_settings() {
|
||||
// check if jarvis-gui is connected via IPC
|
||||
if ipc::has_clients() {
|
||||
// gui is running, send reveal event
|
||||
info!("GUI is connected, sending reveal event");
|
||||
ipc::send(IpcEvent::RevealWindow);
|
||||
} else {
|
||||
// gui not running, launch it
|
||||
info!("GUI not connected, launching jarvis-gui");
|
||||
launch_gui();
|
||||
}
|
||||
|
|
@ -181,7 +203,6 @@ fn launch_gui() {
|
|||
}
|
||||
};
|
||||
|
||||
// jarvis-gui should be in same directory as jarvis-app
|
||||
let gui_path = exe_path.parent()
|
||||
.map(|p| p.join(get_gui_executable_name()))
|
||||
.unwrap_or_else(|| get_gui_executable_name().into());
|
||||
|
|
@ -189,12 +210,8 @@ fn launch_gui() {
|
|||
info!("Launching GUI: {:?}", gui_path);
|
||||
|
||||
match Command::new(&gui_path).spawn() {
|
||||
Ok(_) => {
|
||||
info!("Launched jarvis-gui");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to launch jarvis-gui: {}", e);
|
||||
}
|
||||
Ok(_) => info!("Launched jarvis-gui"),
|
||||
Err(e) => error!("Failed to launch jarvis-gui: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -206,4 +223,4 @@ fn get_gui_executable_name() -> &'static str {
|
|||
#[cfg(not(target_os = "windows"))]
|
||||
fn get_gui_executable_name() -> &'static str {
|
||||
"jarvis-gui"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue