|
| 1 | +/// Linux GTK HeaderBar for native CSD titlebar. |
| 2 | +/// |
| 3 | +/// Replaces the standard window manager titlebar with a GTK HeaderBar |
| 4 | +/// whose background color adapts to the active frontend theme. |
| 5 | +/// |
| 6 | +/// GTK objects are `!Sync`, so the [`CssProvider`] lives in a main-thread |
| 7 | +/// `thread_local`. The Tauri command [`set_titlebar_color`] bounces updates |
| 8 | +/// back to the main thread via `glib::idle_add_once`. |
| 9 | +#[cfg(target_os = "linux")] |
| 10 | +use std::cell::RefCell; |
| 11 | + |
| 12 | +#[cfg(target_os = "linux")] |
| 13 | +use gtk::prelude::*; |
| 14 | + |
| 15 | +#[cfg(target_os = "linux")] |
| 16 | +thread_local! { |
| 17 | + static CSS_PROVIDER: RefCell<Option<gtk::CssProvider>> = const { RefCell::new(None) }; |
| 18 | +} |
| 19 | + |
| 20 | +/// Create a GTK HeaderBar and set it as the window's titlebar. |
| 21 | +/// |
| 22 | +/// Must be called during `setup()` before the window is shown. |
| 23 | +#[cfg(target_os = "linux")] |
| 24 | +pub fn setup_headerbar(window: &tauri::WebviewWindow) -> Result<(), Box<dyn std::error::Error>> { |
| 25 | + let gtk_window = window.gtk_window()?; |
| 26 | + |
| 27 | + let header_bar = gtk::HeaderBar::new(); |
| 28 | + header_bar.set_show_close_button(true); |
| 29 | + header_bar.set_title(None::<&str>); |
| 30 | + header_bar.set_has_subtitle(false); |
| 31 | + |
| 32 | + // Apply an initial transparent style so basecoat bg-background shows through |
| 33 | + let provider = gtk::CssProvider::new(); |
| 34 | + provider |
| 35 | + .load_from_data( |
| 36 | + b"headerbar { |
| 37 | + background: transparent; |
| 38 | + border: none; |
| 39 | + box-shadow: none; |
| 40 | + min-height: 28px; |
| 41 | + padding: 0 6px; |
| 42 | + }", |
| 43 | + ) |
| 44 | + .ok(); |
| 45 | + |
| 46 | + // Disambiguate: GtkWindowExt::screen (not WidgetExt::screen) |
| 47 | + let screen = gtk::prelude::GtkWindowExt::screen(>k_window) |
| 48 | + .expect("GTK window must have a screen"); |
| 49 | + gtk::StyleContext::add_provider_for_screen( |
| 50 | + &screen, |
| 51 | + &provider, |
| 52 | + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, |
| 53 | + ); |
| 54 | + |
| 55 | + // Store the provider in a main-thread thread_local for later updates |
| 56 | + CSS_PROVIDER.with(|cell| { |
| 57 | + *cell.borrow_mut() = Some(provider); |
| 58 | + }); |
| 59 | + |
| 60 | + gtk_window.set_titlebar(Some(&header_bar)); |
| 61 | + println!("GTK HeaderBar set as CSD titlebar"); |
| 62 | + |
| 63 | + Ok(()) |
| 64 | +} |
| 65 | + |
| 66 | +/// Update the HeaderBar background color from the frontend theme. |
| 67 | +/// |
| 68 | +/// The frontend calls this whenever the theme changes, passing the |
| 69 | +/// computed CSS `bg-background` color (e.g. `"rgb(30, 30, 30)"` or `"rgb(255, 255, 255)"`). |
| 70 | +/// |
| 71 | +/// Because Tauri commands run on a Tokio thread, this dispatches the GTK |
| 72 | +/// update back to the main thread via `glib::idle_add_once`. |
| 73 | +#[cfg(target_os = "linux")] |
| 74 | +#[tauri::command] |
| 75 | +pub fn set_titlebar_color(color: String) -> Result<(), String> { |
| 76 | + gtk::glib::idle_add_once(move || { |
| 77 | + CSS_PROVIDER.with(|cell| { |
| 78 | + if let Some(provider) = cell.borrow().as_ref() { |
| 79 | + let css = format!( |
| 80 | + "headerbar {{ |
| 81 | + background: {color}; |
| 82 | + border: none; |
| 83 | + box-shadow: none; |
| 84 | + min-height: 28px; |
| 85 | + padding: 0 6px; |
| 86 | + }}" |
| 87 | + ); |
| 88 | + provider.load_from_data(css.as_bytes()).ok(); |
| 89 | + } |
| 90 | + }); |
| 91 | + }); |
| 92 | + Ok(()) |
| 93 | +} |
| 94 | + |
| 95 | +/// No-op on non-Linux platforms. |
| 96 | +#[cfg(not(target_os = "linux"))] |
| 97 | +#[tauri::command] |
| 98 | +pub fn set_titlebar_color(_color: String) -> Result<(), String> { |
| 99 | + Ok(()) |
| 100 | +} |
0 commit comments