From 6818ee3c51b5e7d7583a526120f1e01057b04fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Fri, 22 May 2026 12:22:15 +0200 Subject: [PATCH 1/2] rounded corners for tray window on macos --- src-tauri/Cargo.lock | 69 ++++++++++++++++++++++++++- src-tauri/Cargo.toml | 1 + src-tauri/src/tray.rs | 64 ++++++++++++++----------- src-tauri/src/window_manager/macos.rs | 35 ++++++++++++++ src-tauri/src/window_manager/mod.rs | 11 ++++- 5 files changed, 150 insertions(+), 30 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index dff0dfb0..ac7ecedc 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -642,6 +642,12 @@ dependencies = [ "digest", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -1028,6 +1034,35 @@ dependencies = [ "cc", ] +[[package]] +name = "cocoa" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" +dependencies = [ + "bitflags 2.11.1", + "block", + "cocoa-foundation", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types 0.5.0", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +dependencies = [ + "bitflags 2.11.1", + "block", + "core-foundation 0.10.1", + "core-graphics-types", + "objc", +] + [[package]] name = "colorchoice" version = "1.0.5" @@ -1141,6 +1176,19 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + [[package]] name = "core-graphics" version = "0.25.0" @@ -1415,6 +1463,7 @@ dependencies = [ "block2 0.6.2", "chrono", "clap", + "cocoa", "common", "dark-light", "defguard_wireguard_rs", @@ -3481,6 +3530,15 @@ dependencies = [ "time", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "markup5ever" version = "0.38.0" @@ -3866,6 +3924,15 @@ dependencies = [ "libc", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "objc-sys" version = "0.3.5" @@ -6291,7 +6358,7 @@ dependencies = [ "bitflags 2.11.1", "block2 0.6.2", "core-foundation 0.10.1", - "core-graphics", + "core-graphics 0.25.0", "crossbeam-channel", "dbus", "dispatch2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4425dd8c..d83d185c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -124,6 +124,7 @@ x25519-dalek = { version = "2", features = [ ] } [target.'cfg(target_os = "macos")'.dependencies] +cocoa = "0.26" block2 = "0.6" objc2 = "0.6" objc2-foundation = "0.3" diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index b4efd353..d2279e53 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -144,38 +144,48 @@ pub async fn setup_tray(app: &AppHandle) -> Result<(), Error> { TrayIconBuilder::with_id(TRAY_ICON_ID) .menu(&tray_menu) - .show_menu_on_left_click(cfg!(target_os = "macos")) + .show_menu_on_left_click(false) .on_tray_icon_event(|icon, event| { store_tray_click_position(icon.app_handle(), &event); - if let TrayIconEvent::Click { - button: MouseButton::Left, - button_state: MouseButtonState::Up, - .. - } = event - { - let app = icon.app_handle(); - let any_visible = [NEW_UI_WINDOW_ID, OLD_UI_WINDOW_ID].iter().any(|id| { - app.get_webview_window(id) - .and_then(|w| w.is_visible().ok()) - .unwrap_or(false) - }); - if any_visible { - hide_visible_windows(app); - } else { - #[cfg(not(target_os = "linux"))] - { - let has_locations = tauri::async_runtime::block_on( - crate::window_manager::has_non_service_locations(), - ); - if has_locations { - show_new_ui_window_near_tray(app); - } else { - let _ = WindowManager::open_full_view(app); + match event { + #[cfg(target_os = "macos")] + TrayIconEvent::Click { + button: MouseButton::Right, + button_state: MouseButtonState::Up, + .. + } => { + let _ = icon.show_menu(); + } + TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } => { + let app = icon.app_handle(); + let any_visible = [NEW_UI_WINDOW_ID, OLD_UI_WINDOW_ID].iter().any(|id| { + app.get_webview_window(id) + .and_then(|w| w.is_visible().ok()) + .unwrap_or(false) + }); + if any_visible { + hide_visible_windows(app); + } else { + #[cfg(not(target_os = "linux"))] + { + let has_locations = tauri::async_runtime::block_on( + crate::window_manager::has_non_service_locations(), + ); + if has_locations { + show_new_ui_window_near_tray(app); + } else { + let _ = WindowManager::open_full_view(app); + } } + #[cfg(target_os = "linux")] + show_new_ui_window_near_tray(app); } - #[cfg(target_os = "linux")] - show_new_ui_window_near_tray(app); } + _ => {} } }) .on_menu_event(handle_tray_menu_event) diff --git a/src-tauri/src/window_manager/macos.rs b/src-tauri/src/window_manager/macos.rs index d93964dc..10359b9d 100644 --- a/src-tauri/src/window_manager/macos.rs +++ b/src-tauri/src/window_manager/macos.rs @@ -1,8 +1,43 @@ use tauri::{AppHandle, LogicalPosition, Manager, Monitor, PhysicalSize, Position, WebviewWindow}; +#[cfg(target_os = "macos")] +use tauri::Runtime; + use crate::appstate::AppState; use crate::window_manager::{WindowManager, NEW_UI_WINDOW_ID, OLD_UI_WINDOW_ID}; +#[cfg(target_os = "macos")] +use cocoa::{ + appkit::{NSView, NSWindow, NSWindowStyleMask}, + base::id, +}; + +#[cfg(target_os = "macos")] +pub fn enable_rounded_corners(window: WebviewWindow) -> Result<(), String> { + window + .with_webview(move |webview| { + unsafe { + let ns_window = webview.ns_window() as id; + + let mut style_mask = ns_window.styleMask(); + + // Add necessary styles for rounded corners + style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; + style_mask |= NSWindowStyleMask::NSTitledWindowMask; + style_mask |= NSWindowStyleMask::NSClosableWindowMask; + style_mask |= NSWindowStyleMask::NSMiniaturizableWindowMask; + style_mask |= NSWindowStyleMask::NSResizableWindowMask; + + ns_window.setStyleMask_(style_mask); + ns_window.setTitlebarAppearsTransparent_(cocoa::base::YES); + + let content_view = ns_window.contentView(); + content_view.setWantsLayer(cocoa::base::YES); + } + }) + .map_err(|e| e.to_string()) +} + /// Try to get monitor at the given position, with a fall back to primary monitor, and then to the /// first one on the list of available monitors. fn get_monitor_for_position(app: &AppHandle, x: f64, y: f64) -> Option { diff --git a/src-tauri/src/window_manager/mod.rs b/src-tauri/src/window_manager/mod.rs index d821fd48..b08ba8f8 100644 --- a/src-tauri/src/window_manager/mod.rs +++ b/src-tauri/src/window_manager/mod.rs @@ -42,7 +42,7 @@ pub struct WindowManager; impl WindowManager { pub fn build_tray_window(app: &AppHandle) -> tauri::Result { - WebviewWindowBuilder::new(app, NEW_UI_WINDOW_ID, new_ui_url()) + let window = WebviewWindowBuilder::new(app, NEW_UI_WINDOW_ID, new_ui_url()) .title("Defguard") .inner_size(NEW_UI_WIDTH, NEW_UI_HEIGHT) .resizable(false) @@ -50,7 +50,14 @@ impl WindowManager { .visible(false) .always_on_top(true) .skip_taskbar(true) - .build() + .build()?; + + #[cfg(target_os = "macos")] + if let Err(err) = macos::enable_rounded_corners(window.clone()) { + tracing::warn!("Failed to enable rounded corners on tray window: {err}"); + } + + Ok(window) } pub fn build_full_window(app: &AppHandle) -> tauri::Result { From 9b7db928087fbfffbdc7fc3c4df1b4878afd35c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Fri, 22 May 2026 12:31:45 +0200 Subject: [PATCH 2/2] Update ConnectionsWatcher.tsx --- .../components/ConnectionWatcher/ConnectionsWatcher.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/ConnectionsWatcher.tsx b/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/ConnectionsWatcher.tsx index 6e2afcf2..139ac5a8 100644 --- a/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/ConnectionsWatcher.tsx +++ b/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/ConnectionsWatcher.tsx @@ -60,6 +60,7 @@ export const ConnectionWatcher = () => { const hover = useHover(context, { handleClose: safePolygon(), + enabled: connected, }); const dismiss = useDismiss(context, {