diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b086236..5266a4e 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -451,6 +451,7 @@ dependencies = [ name = "claude-app" version = "1.0.0" dependencies = [ + "gtk", "serde", "serde_json", "tauri", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index afcb224..ac2a148 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -15,6 +15,9 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" url = "2" +[target.'cfg(target_os = "linux")'.dependencies] +gtk = { version = "0.18", features = ["v3_24"] } + [features] default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 34047de..dbb2449 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -97,11 +97,10 @@ pub fn run() { .plugin(tauri_plugin_opener::init()) .invoke_handler(tauri::generate_handler![send_notification]) .setup(|app| { - // -- Create main window navigating directly to claude.ai -- - // - // on_navigation and on_new_window are builder methods, so they must - // be chained before .build(). The close-to-tray handler is set on - // the built window via on_window_event. + // NOTE: Decorations disabled due to upstream bug where CSD titlebar + // buttons become non-interactive after hide()/show() on Linux. + // Tracked at: https://github.com/tauri-apps/tauri/issues/11856 + // https://github.com/tauri-apps/tao/issues/1046 let webview_window = WebviewWindowBuilder::new( app, "main", @@ -110,6 +109,7 @@ pub fn run() { .title("Claude") .inner_size(1200.0, 800.0) .min_inner_size(400.0, 300.0) + .resizable(true) // Standard WebKit user agent so claude.ai serves the full experience .user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15") .initialization_script(NOTIFICATION_BRIDGE_JS) @@ -124,38 +124,71 @@ pub fn run() { }) .build()?; - // -- Close-to-tray: hide window instead of quitting -- - let win_clone = webview_window.clone(); - webview_window.on_window_event(move |event| { - if let WindowEvent::CloseRequested { api, .. } = event { - api.prevent_close(); - let _ = win_clone.hide(); + // Replace the default GTK header bar with one that has no buttons. + // The standard CSD buttons become non-interactive after hide()/show() + // due to upstream bug: https://github.com/tauri-apps/tauri/issues/11856 + #[cfg(target_os = "linux")] + { + use gtk::prelude::*; + if let Ok(gtk_win) = webview_window.gtk_window() { + let header = gtk::HeaderBar::new(); + header.set_show_close_button(false); + header.set_title(Some("Claude")); + header.show(); + gtk_win.set_titlebar(Some(&header)); } - }); + } // -- System tray -- - let show_item = - MenuItem::with_id(app, "show", "Show Claude", true, None::<&str>)?; + let toggle_item = + MenuItem::with_id(app, "toggle", "Hide Claude", true, None::<&str>)?; let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?; - let menu = Menu::with_items(app, &[&show_item, &quit_item])?; + let menu = Menu::with_items(app, &[&toggle_item, &quit_item])?; let tray_icon = Image::from_bytes(include_bytes!("../icons/32x32.png"))?; + fn show_window(win: &tauri::WebviewWindow, toggle: &MenuItem) { + let _ = win.show(); + let _ = win.set_focus(); + let _ = toggle.set_text("Hide Claude"); + } + + fn hide_window(win: &tauri::WebviewWindow, toggle: &MenuItem) { + let _ = win.hide(); + let _ = toggle.set_text("Show Claude"); + } + + // -- Close-to-tray: hide window instead of quitting -- + let win_for_close = webview_window.clone(); + let toggle_for_close = toggle_item.clone(); + webview_window.on_window_event(move |event| { + if let WindowEvent::CloseRequested { api, .. } = event { + api.prevent_close(); + hide_window(&win_for_close, &toggle_for_close); + } + }); + + let toggle_for_menu = toggle_item.clone(); + let toggle_for_tray = toggle_item.clone(); let tray_handle = app.handle().clone(); TrayIconBuilder::new() .icon(tray_icon) .tooltip("Claude") .menu(&menu) .on_menu_event(move |app_handle, event| match event.id().as_ref() { - "show" => { + "toggle" => { if let Some(win) = app_handle.get_webview_window("main") { - let _ = win.show(); - let _ = win.set_focus(); + let visible = win.is_visible().unwrap_or(false); + if visible { + hide_window(&win, &toggle_for_menu); + } else { + show_window(&win, &toggle_for_menu); + } } } "quit" => { - app_handle.exit(0); + std::process::exit(0); } _ => {} }) @@ -166,8 +199,12 @@ pub fn run() { } = event { if let Some(win) = tray_handle.get_webview_window("main") { - let _ = win.show(); - let _ = win.set_focus(); + let visible = win.is_visible().unwrap_or(false); + if visible { + hide_window(&win, &toggle_for_tray); + } else { + show_window(&win, &toggle_for_tray); + } } } })