Remove CSD titlebar buttons, fix tray toggle and quit

- Replace GTK header bar with buttonless version to work around
  upstream bug where CSD buttons become non-interactive after
  hide()/show() (tauri-apps/tauri#11856, tauri-apps/tao#1046)
- Tray menu now toggles between "Hide Claude" / "Show Claude"
- Left-click tray icon toggles window visibility
- Fix quit from tray (use std::process::exit to bypass keep-alive)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-02-16 13:04:59 +01:00
commit 6b2c8ffa08
3 changed files with 62 additions and 21 deletions

1
src-tauri/Cargo.lock generated
View file

@ -451,6 +451,7 @@ dependencies = [
name = "claude-app"
version = "1.0.0"
dependencies = [
"gtk",
"serde",
"serde_json",
"tauri",

View file

@ -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"]

View file

@ -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<tauri::Wry>) {
let _ = win.show();
let _ = win.set_focus();
let _ = toggle.set_text("Hide Claude");
}
fn hide_window(win: &tauri::WebviewWindow, toggle: &MenuItem<tauri::Wry>) {
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);
}
}
}
})