diff --git a/new-ui/src/pages/compact/CompactPage/CompactPage.tsx b/new-ui/src/pages/compact/CompactPage/CompactPage.tsx index 23d47e31..53ed3731 100644 --- a/new-ui/src/pages/compact/CompactPage/CompactPage.tsx +++ b/new-ui/src/pages/compact/CompactPage/CompactPage.tsx @@ -1,14 +1,33 @@ import clsx from 'clsx'; import './style.scss'; +import { useMutation } from '@tanstack/react-query'; import type { JSX, PropsWithChildren } from 'react'; +import { IconButton } from '../../../shared/components/IconButton/IconButton'; +import { IconButtonVariant } from '../../../shared/components/IconButton/types'; +import { api } from '../../../shared/rust-api/api'; interface Props extends PropsWithChildren { containerProps?: JSX.IntrinsicElements['main']; } export const CompactPage = ({ children, containerProps }: Props) => { + const { mutate: closeWindow, isPending } = useMutation({ + mutationFn: api.closeTrayWindow, + }); + return (
+
+ { + if (!isPending) { + closeWindow(); + } + }} + /> +
{children}
); diff --git a/new-ui/src/pages/compact/CompactPage/style.scss b/new-ui/src/pages/compact/CompactPage/style.scss index 0e6146f5..b2c35275 100644 --- a/new-ui/src/pages/compact/CompactPage/style.scss +++ b/new-ui/src/pages/compact/CompactPage/style.scss @@ -1,4 +1,11 @@ .compact-page { box-sizing: border-box; - padding: 8px; + padding: var(--spacing-md); + position: relative; + + > .close-window { + position: absolute; + right: var(--spacing-md); + top: var(--spacing-md); + } } diff --git a/new-ui/src/shared/components/Button/style.scss b/new-ui/src/shared/components/Button/style.scss index 46828297..5cc175e4 100644 --- a/new-ui/src/shared/components/Button/style.scss +++ b/new-ui/src/shared/components/Button/style.scss @@ -24,6 +24,7 @@ display: inline-block; min-width: 0; user-select: none; + background-clip: padding-box; > .btn-content { display: inline-grid; @@ -60,6 +61,7 @@ .text { font: var(--btn-font); color: inherit; + user-select: none; @include animate(color); } @@ -84,7 +86,7 @@ &.size-primary { --btn-font: var(--t-button-label-primary); - --btn-size: 40px; + --btn-size: 36px; border-radius: 8px; padding: var(--spacing-sm) var(--spacing-lg); diff --git a/new-ui/src/shared/components/Divider/style.scss b/new-ui/src/shared/components/Divider/style.scss index a75bd2fb..e47a4fee 100644 --- a/new-ui/src/shared/components/Divider/style.scss +++ b/new-ui/src/shared/components/Divider/style.scss @@ -1,6 +1,6 @@ .divider { --divider-line-size: 1px; - --divider-color: var(--bg-white-20); + --divider-color: var(--bg-white-10); user-select: none; diff --git a/new-ui/src/shared/components/LocationCard/LocationCard.tsx b/new-ui/src/shared/components/LocationCard/LocationCard.tsx index 72b6cbc9..7216e34f 100644 --- a/new-ui/src/shared/components/LocationCard/LocationCard.tsx +++ b/new-ui/src/shared/components/LocationCard/LocationCard.tsx @@ -57,7 +57,12 @@ const LocationCardInner = ({ isOpen, onOpen, disableOpen }: InnerProps) => { data-network={location.network_id} data-id={location.id} > -
+
@@ -82,7 +87,6 @@ const LocationCardInner = ({ isOpen, onOpen, disableOpen }: InnerProps) => { icon={IconKind.ArrowSmall} variant={isOpen ? IconButtonVariant.SmallSelected : IconButtonVariant.Small} iconRotation={isOpen ? Direction.DOWN : Direction.RIGHT} - onClick={onOpen} /> )}
diff --git a/new-ui/src/shared/components/LocationCard/components/MfaSelector/MfaSelector.tsx b/new-ui/src/shared/components/LocationCard/components/MfaSelector/MfaSelector.tsx index 7f1f65ce..490e2eb0 100644 --- a/new-ui/src/shared/components/LocationCard/components/MfaSelector/MfaSelector.tsx +++ b/new-ui/src/shared/components/LocationCard/components/MfaSelector/MfaSelector.tsx @@ -25,11 +25,11 @@ export const MfaSelector = ({ case 'email': return 'mail'; case 'mobileapprove': - return 'mobile-lock'; + return 'mobile'; case 'oidc': return 'token'; case 'totp': - return 'mobile-lock'; + return 'lock-closed'; case 'biometric': return 'biometric'; } diff --git a/new-ui/src/shared/components/LocationCard/components/MfaSelector/style.scss b/new-ui/src/shared/components/LocationCard/components/MfaSelector/style.scss index 3d33c107..16432580 100644 --- a/new-ui/src/shared/components/LocationCard/components/MfaSelector/style.scss +++ b/new-ui/src/shared/components/LocationCard/components/MfaSelector/style.scss @@ -3,6 +3,7 @@ --border: var(--border-default); --icon: var(--fg-white-80); --color: var(--fg-white-80); + --box-shadow: box-shadow: 0 4px 4px 0 rgb(0 0 0 / 0%); display: grid; grid-template-columns: 20px minmax(0, 1fr) 16px; @@ -13,26 +14,29 @@ user-select: none; align-items: center; box-sizing: border-box; + box-shadow: var(--box-shadow); padding: 0 var(--spacing-md); min-height: 40px; border-radius: 8px; cursor: pointer; transition-duration: 250ms; transition-timing-function: cubic-bezier(0.1, 0.9, 0.2, 1); - transition-property: border-color, background, color; + transition-property: border-color, background, color, box-shadow; + background-clip: padding-box; &:hover { --bg: var(--bg-white-5); --color: var(--fg-white-100); - --border: var(--border-default); + --border: var(--border-action-disabled); --icon: var(--fg-white-100); } &.selected { - --bg: var(--bg-white-5); + --bg: var(--bg-white-10); --color: var(--fg-white-100); - --border: transparent; + --border: var(--border-action-disabled); --icon: var(--fg-white-100); + --box-shadow: box-shadow: 0 4px 4px 0 rgb(0 0 0 / 5%); } > .middle { @@ -40,7 +44,7 @@ flex-flow: row nowrap; align-items: center; justify-content: flex-start; - column-gap: var(--spacing-xs); + column-gap: var(--spacing-md); } .default-badge { @@ -71,6 +75,6 @@ .name { color: inherit; - font: var(--t-body-xs); + font: var(--t-body-sm-400); } } diff --git a/new-ui/src/shared/components/LocationCard/context/context.tsx b/new-ui/src/shared/components/LocationCard/context/context.tsx index b2e7e8b4..00005f97 100644 --- a/new-ui/src/shared/components/LocationCard/context/context.tsx +++ b/new-ui/src/shared/components/LocationCard/context/context.tsx @@ -1,5 +1,5 @@ import { createContext, type ReactNode, useCallback, useContext, useState } from 'react'; -import type { InstanceInfo, LocationInfo } from '../../../rust-api/types'; +import type { InstanceInfo, LocationInfo, MfaMethodValue } from '../../../rust-api/types'; import { MfaMethod } from '../../../rust-api/types'; import { LocationCardViews, type LocationCardViewsValue } from './types'; @@ -10,6 +10,8 @@ interface LocationCardContextValue { previousView: LocationCardViewsValue | null; setView: (view: LocationCardViewsValue) => void; startMfa: () => void; + localMfaMethod: MfaMethodValue; + setLocalMfaMethod: (method: MfaMethodValue) => void; } const LocationCardContext = createContext(null); @@ -37,6 +39,9 @@ export const LocationCardProvider = ({ const [currentView, setCurrentView] = useState( location.active ? LocationCardViews.Connected : LocationCardViews.Default, ); + const [localMfaMethod, setLocalMfaMethod] = useState( + location.mfa_method ?? MfaMethod.Totp, + ); const setView = useCallback( (view: LocationCardViewsValue) => { @@ -47,7 +52,7 @@ export const LocationCardProvider = ({ ); const startMfa = useCallback(() => { - switch (location.mfa_method) { + switch (localMfaMethod) { case MfaMethod.Totp: setView(LocationCardViews.MfaTotp); break; @@ -61,7 +66,7 @@ export const LocationCardProvider = ({ setView(LocationCardViews.MfaMobile); break; } - }, [location.mfa_method, setView]); + }, [localMfaMethod, setView]); return ( {children} diff --git a/new-ui/src/shared/components/LocationCard/style.scss b/new-ui/src/shared/components/LocationCard/style.scss index 9221d88a..c8a68b12 100644 --- a/new-ui/src/shared/components/LocationCard/style.scss +++ b/new-ui/src/shared/components/LocationCard/style.scss @@ -1,7 +1,7 @@ .location-card { - border-radius: 12px; + border-radius: 16px; box-sizing: border-box; - padding: var(--spacing-md) var(--spacing-lg); + padding: var(--spacing-lg); background-color: var(--bg-dark-blue-40); > .top-track { @@ -11,6 +11,17 @@ justify-content: flex-start; user-select: none; + &.interactive { + cursor: pointer; + + &:hover { + > .right > .icon-button { + --bg: var(--c-white-20); + --icon: var(--c-white-100); + } + } + } + > .left { display: flex; flex-flow: row; diff --git a/new-ui/src/shared/components/LocationCard/views/DefaultView/DefaultView.tsx b/new-ui/src/shared/components/LocationCard/views/DefaultView/DefaultView.tsx index acfb49a9..0b5ce633 100644 --- a/new-ui/src/shared/components/LocationCard/views/DefaultView/DefaultView.tsx +++ b/new-ui/src/shared/components/LocationCard/views/DefaultView/DefaultView.tsx @@ -38,7 +38,11 @@ export const DefaultView = () => { { updateRouting({ connectionType: location.connection_type, diff --git a/new-ui/src/shared/components/LocationCard/views/DefaultView/style.scss b/new-ui/src/shared/components/LocationCard/views/DefaultView/style.scss index b32113ff..658c869b 100644 --- a/new-ui/src/shared/components/LocationCard/views/DefaultView/style.scss +++ b/new-ui/src/shared/components/LocationCard/views/DefaultView/style.scss @@ -5,9 +5,10 @@ grid-template-rows: 1fr; align-items: center; column-gap: var(--spacing-md); + user-select: none; > .name { - font: var(--t-body-xs-500); + font: var(--t-body-sm-400); color: var(--fg-white-100); } @@ -17,10 +18,12 @@ display: inline-flex; flex-flow: row nowrap; align-items: center; + justify-content: center; padding: 0 4px; - height: 18px; - width: 32px; - background-color: var(--bg-white-100); + min-height: 20px; + width: 36px; + background-color: transparent; + border: 1px solid var(--bg-white-60); p { font: var(--font-family-body); @@ -28,7 +31,7 @@ font-weight: 500; line-height: 16px; letter-spacing: 0.11px; - color: var(--fg-action); + color: var(--fg-white-60); } } } diff --git a/new-ui/src/shared/components/LocationCard/views/LocationCardMfaSettings/LocationCardMfaSettings.tsx b/new-ui/src/shared/components/LocationCard/views/LocationCardMfaSettings/LocationCardMfaSettings.tsx index c8314ae6..04b066f6 100644 --- a/new-ui/src/shared/components/LocationCard/views/LocationCardMfaSettings/LocationCardMfaSettings.tsx +++ b/new-ui/src/shared/components/LocationCard/views/LocationCardMfaSettings/LocationCardMfaSettings.tsx @@ -30,14 +30,18 @@ export const LocationCardMfaSettings = () => { }, }); - const { previousView, setView, location } = useLocationCardContext(); + const { previousView, setView, location, localMfaMethod, setLocalMfaMethod } = + useLocationCardContext(); - const mfaMethod = location.mfa_method ?? MfaMethod.Totp; + const locationDefaultMfaMethod = location.mfa_method ?? MfaMethod.Totp; - const [selectedPref, setSelectedPref] = useState( - mfaMethod ?? MfaMethod.Totp, + const [selectedMethod, setSelectedPref] = useState( + localMfaMethod ?? MfaMethod.Totp, ); + const isFromDefault = previousView === LocationCardViews.Default; + const [setAsDefault, setSetAsDefault] = useState(true); + const MfaFactorsList = useMemo((): MfaMethodValue[] => { if (location.location_mfa_mode === LocationMfaMode.Internal) { return [MfaMethod.Totp, MfaMethod.Email, MfaMethod.MobileApprove]; @@ -46,13 +50,14 @@ export const LocationCardMfaSettings = () => { }, [location.location_mfa_mode]); const handleSubmit = () => { - if (selectedPref !== mfaMethod) { + setLocalMfaMethod(selectedMethod); + if ((isFromDefault || setAsDefault) && selectedMethod !== locationDefaultMfaMethod) { setMfaMethod({ locationId: location.id, - mfaMethod: selectedPref, + mfaMethod: selectedMethod, }); - setView(previousView ?? LocationCardViews.Default); } + setView(previousView ?? LocationCardViews.Default); }; return ( @@ -70,21 +75,19 @@ export const LocationCardMfaSettings = () => { setSelectedPref(factor)} /> ))}
- + {!isFromDefault && ( + setSetAsDefault((prev) => !prev)} + text="Set as default MFA method" + /> + )} { />
diff --git a/new-ui/src/shared/components/Toggle/style.scss b/new-ui/src/shared/components/Toggle/style.scss index f8eb8a32..971629e5 100644 --- a/new-ui/src/shared/components/Toggle/style.scss +++ b/new-ui/src/shared/components/Toggle/style.scss @@ -26,6 +26,7 @@ border: var(--border-1) solid var(--border); min-width: 36px; flex-shrink: 0; + background-clip: padding-box; @include animate(border-color, background-color); diff --git a/new-ui/src/shared/components/WindowHeader/WindowHeader.tsx b/new-ui/src/shared/components/WindowHeader/WindowHeader.tsx index ecac32c8..f78c6ae0 100644 --- a/new-ui/src/shared/components/WindowHeader/WindowHeader.tsx +++ b/new-ui/src/shared/components/WindowHeader/WindowHeader.tsx @@ -11,7 +11,7 @@ export const WindowHeader = ({ variant }: Props) => {
-

Defguard VPN Client

+

Defguard VPN Client

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 5f3308bc..6e2afcf2 100644 --- a/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/ConnectionsWatcher.tsx +++ b/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/ConnectionsWatcher.tsx @@ -4,10 +4,11 @@ import { FloatingPortal, size as floatingSize, offset, + safePolygon, shift, - useClick, useDismiss, useFloating, + useHover, useInteractions, } from '@floating-ui/react'; import { useMutation, useQuery } from '@tanstack/react-query'; @@ -57,9 +58,8 @@ export const ConnectionWatcher = () => { whileElementsMounted: autoUpdate, }); - const click = useClick(context, { - toggle: true, - enabled: connected, + const hover = useHover(context, { + handleClose: safePolygon(), }); const dismiss = useDismiss(context, { @@ -67,7 +67,7 @@ export const ConnectionWatcher = () => { outsidePress: true, }); - const { getFloatingProps, getReferenceProps } = useInteractions([click, dismiss]); + const { getFloatingProps, getReferenceProps } = useInteractions([hover, dismiss]); return ( <> diff --git a/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/style.scss b/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/style.scss index e40bfe4c..3f2842d0 100644 --- a/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/style.scss +++ b/new-ui/src/shared/components/WindowHeader/components/ConnectionWatcher/style.scss @@ -26,7 +26,6 @@ display: flex; flex-flow: row nowrap; align-items: center; - cursor: pointer; padding: 0 var(--spacing-xs); background-color: var(--bg-success); @@ -55,6 +54,8 @@ font: var(--t-menu-title); color: var(--fg-white-60); min-height: 24px; + line-height: 24px; + font-weight: 400; } .connection { @@ -70,6 +71,10 @@ svg circle { fill: var(--bg-success); } + + p { + font: var(--t-menu-text); + } } .disconnect { diff --git a/new-ui/src/shared/rust-api/api.ts b/new-ui/src/shared/rust-api/api.ts index 4628ab90..435966e9 100644 --- a/new-ui/src/shared/rust-api/api.ts +++ b/new-ui/src/shared/rust-api/api.ts @@ -128,6 +128,8 @@ const getEdgeRequestHeaders = async (): Promise => { const swapToOldUi = async () => invoke(TauriCommand.SwapToOldUi); +const closeTrayWindow = async () => invoke(TauriCommand.CloseTrayWindow); + export const api = { getEdgeRequestHeaders, // Instances @@ -168,4 +170,5 @@ export const api = { disconnectLocations, // Window swapToOldUi, + closeTrayWindow, }; diff --git a/new-ui/src/shared/rust-api/types.ts b/new-ui/src/shared/rust-api/types.ts index f82a6716..708d1b51 100644 --- a/new-ui/src/shared/rust-api/types.ts +++ b/new-ui/src/shared/rust-api/types.ts @@ -104,6 +104,7 @@ export const TauriCommand = { DisconnectLocations: 'disconnect_locations', //Window SwapToOldUi: 'swap_to_old_ui', + CloseTrayWindow: 'close_tray_window', } as const; export type TauriCommand = (typeof TauriCommand)[keyof typeof TauriCommand]; diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3ad98c6d..4425dd8c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -141,6 +141,9 @@ windows = { version = "0.62", features = [ "Win32", "Win32_System", "Win32_System_RemoteDesktop", + "Win32_Graphics_Gdi", + "Win32_UI_HiDpi", + "Win32_Foundation", ] } windows-acl = "0.3" windows-service = "0.8" diff --git a/src-tauri/permissions/default.toml b/src-tauri/permissions/default.toml index 303597ed..610c903d 100644 --- a/src-tauri/permissions/default.toml +++ b/src-tauri/permissions/default.toml @@ -34,6 +34,7 @@ commands.allow = [ "open_old_ui_window", "swap_to_new_ui", "swap_to_old_ui", + "close_tray_window", "all_active_connections", "disconnect_locations", "get_posture_data", diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs index 1428af49..34a85400 100644 --- a/src-tauri/src/bin/defguard-client.rs +++ b/src-tauri/src/bin/defguard-client.rs @@ -29,11 +29,11 @@ use defguard_client::{ service, tray::{configure_tray_icon, setup_tray, show_main_window}, utils::load_log_targets, - window::*, + window_manager::*, LOG_FILENAME, VERSION, }; use log::{Level, LevelFilter}; -use tauri::{AppHandle, Builder, Manager, RunEvent, WebviewWindowBuilder, WindowEvent}; +use tauri::{AppHandle, Builder, Manager, RunEvent, WindowEvent}; use tauri_plugin_log::{Target, TargetKind}; #[macro_use] @@ -188,18 +188,22 @@ fn main() { open_old_ui_window, swap_to_new_ui, swap_to_old_ui, + close_tray_window, all_active_connections, disconnect_locations, ]) .on_window_event(|window, event| { if let WindowEvent::CloseRequested { api, .. } = event { - #[cfg(not(target_os = "macos"))] - let _ = window.hide(); + // Only prevent close on the tray (new-ui) window; let other windows close normally. + if window.label() == NEW_UI_WINDOW_ID { + #[cfg(not(target_os = "macos"))] + let _ = window.hide(); - #[cfg(target_os = "macos")] - let _ = tauri::AppHandle::hide(window.app_handle()); + #[cfg(target_os = "macos")] + let _ = tauri::AppHandle::hide(window.app_handle()); - api.prevent_close(); + api.prevent_close(); + } } }) // Initialize plugins here, except for `tauri_plugin_log` which is handled in `setup()`. @@ -214,7 +218,6 @@ fn main() { .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_notification::init()) - .plugin(tauri_plugin_window_state::Builder::new().build()) .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_process::init()) @@ -348,18 +351,22 @@ fn main() { let state = AppState::new(config, provisioning_config); app.manage(state); - // Open new UI window. - WebviewWindowBuilder::new(app, NEW_UI_WINDOW_ID, new_ui_url()) - .title("New UI") - .inner_size(NEW_UI_WIDTH, NEW_UI_HEIGHT) - .visible(false) - .build()?; - - // Open old UI window. - WebviewWindowBuilder::new(app, OLD_UI_WINDOW_ID, old_ui_url()) - .title("Old UI") - .inner_size(OLD_UI_WIDTH, OLD_UI_HEIGHT) - .build()?; + #[cfg(target_os = "linux")] + { + let _ = WindowManager::open_full_view(app_handle); + } + #[cfg(not(target_os = "linux"))] + { + let has_locations = tauri::async_runtime::block_on( + defguard_client::window_manager::has_non_service_locations() + ); + if has_locations { + WindowManager::open_tray(app_handle)?; + } else { + info!("No locations found, spawning full view on startup."); + let _ = WindowManager::open_full_view(app_handle); + } + } info!("App setup completed, log level: {log_level}"); Ok(()) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2d3137f0..abcf5937 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -32,7 +32,7 @@ pub mod service; pub mod tray; pub mod utils; pub mod wg_config; -pub mod window; +pub mod window_manager; pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "-", env!("VERGEN_GIT_SHA")); pub const MIN_CORE_VERSION: Version = Version::new(1, 6, 0); diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index 2452bdf5..4339c6bd 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -15,7 +15,7 @@ use crate::{ database::{models::location::Location, DB_POOL}, error::Error, events::EventKey, - window::{show_new_ui_window_near_tray, NEW_UI_WINDOW_ID, OLD_UI_WINDOW_ID}, + window_manager::{show_new_ui_window_near_tray, NEW_UI_WINDOW_ID, OLD_UI_WINDOW_ID}, ConnectionType, }; @@ -148,8 +148,9 @@ pub async fn setup_tray(app: &AppHandle) -> Result<(), Error> { .show_menu_on_left_click(true) .on_tray_icon_event(|icon, event| { store_tray_click_position(icon.app_handle(), &event); - if let TrayIconEvent::DoubleClick { + if let TrayIconEvent::Click { button: MouseButton::Left, + button_state: MouseButtonState::Up, .. } = event { @@ -166,8 +167,9 @@ pub async fn setup_tray(app: &AppHandle) -> Result<(), Error> { .show_menu_on_left_click(false) .on_tray_icon_event(|icon, event| { store_tray_click_position(icon.app_handle(), &event); - if let TrayIconEvent::DoubleClick { + if let TrayIconEvent::Click { button: MouseButton::Left, + button_state: MouseButtonState::Up, .. } = event { diff --git a/src-tauri/src/window.rs b/src-tauri/src/window.rs deleted file mode 100644 index dcfc1332..00000000 --- a/src-tauri/src/window.rs +++ /dev/null @@ -1,156 +0,0 @@ -use tauri::{ - webview::WebviewWindowBuilder, AppHandle, LogicalPosition, Manager, Monitor, PhysicalSize, - Position, WebviewUrl, WebviewWindow, -}; - -use crate::appstate::AppState; - -pub const NEW_UI_WINDOW_ID: &str = "new-ui"; -pub const OLD_UI_WINDOW_ID: &str = "old-ui"; -pub const NEW_UI_WIDTH: f64 = 360.0; -pub const NEW_UI_HEIGHT: f64 = 675.0; -pub const OLD_UI_WIDTH: f64 = 920.0; -pub const OLD_UI_HEIGHT: f64 = 720.0; -#[cfg(windows)] -const TASKBAR_HEIGHT: f64 = 48.0; -#[cfg(not(windows))] -const TASKBAR_HEIGHT: f64 = 0.0; - -#[must_use] -pub fn new_ui_url() -> WebviewUrl { - if cfg!(defguard_client_dev) { - WebviewUrl::External("http://localhost:5072".parse().unwrap()) - } else { - WebviewUrl::App("new-ui/".into()) - } -} - -#[must_use] -pub fn old_ui_url() -> WebviewUrl { - if cfg!(defguard_client_dev) { - WebviewUrl::External("http://localhost:5071".parse().unwrap()) - } else { - WebviewUrl::App("old-ui/index.html".into()) - } -} - -/// 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 { - if let Ok(Some(monitor)) = app.monitor_from_point(x, y) { - return Some(monitor); - } - - if let Ok(Some(monitor)) = app.primary_monitor() { - return Some(monitor); - } - - // On macOS, it seems this is the only working method (as of Tauri 2.11), but fortunately it - // returns the current monitor as the first one. - if let Ok(mut monitors) = app.available_monitors() { - monitors.pop() - } else { - None - } -} - -fn get_tray_window_position( - app: &AppHandle, - size: PhysicalSize, -) -> Option> { - let app_state = app.state::(); - let tray_position = app_state.tray_click_position.lock().unwrap().to_owned()?; - - let monitor = get_monitor_for_position(app, tray_position.x, tray_position.y)?; - - let scale_factor = monitor.scale_factor(); - let monitor_position = monitor.position().to_logical::(scale_factor); - let monitor_size = monitor.size().to_logical::(scale_factor); - let tray_position = tray_position.to_logical::(scale_factor); - let window_size = size.to_logical::(scale_factor); - - let mut x = tray_position.x; - let mut y = tray_position.y; - - x = x.clamp( - monitor_position.x, - monitor_position.x + monitor_size.width - window_size.width, - ); - y = y.clamp( - monitor_position.y, - monitor_position.y + monitor_size.height - window_size.height - TASKBAR_HEIGHT, - ); - - Some(LogicalPosition::new(x, y)) -} - -fn position_window_near_tray(app: &AppHandle, window: &WebviewWindow) { - let size = window.outer_size().unwrap_or_default(); - if let Some(position) = get_tray_window_position(app, size) { - if let Err(err) = window.set_position(Position::Logical(position)) { - warn!("Failed to position window near tray icon: {err}"); - } - } -} - -fn show_new_ui_window_internal(app: &AppHandle, near_tray: bool) { - let window = if let Some(window) = app.get_webview_window(NEW_UI_WINDOW_ID) { - let _ = window.unminimize(); - window - } else { - WebviewWindowBuilder::new(app, NEW_UI_WINDOW_ID, new_ui_url()) - .title("New UI") - .inner_size(NEW_UI_WIDTH, NEW_UI_HEIGHT) - .build() - .unwrap() - }; - if near_tray { - position_window_near_tray(app, &window); - } - #[cfg(target_os = "macos")] - let _ = app.show(); - let _ = window.show(); - let _ = window.set_focus(); -} - -pub(crate) fn show_new_ui_window(app: &AppHandle) { - show_new_ui_window_internal(app, false); -} - -pub(crate) fn show_new_ui_window_near_tray(app: &AppHandle) { - show_new_ui_window_internal(app, true); -} - -#[tauri::command] -pub fn open_new_ui_window(app: AppHandle) { - show_new_ui_window(&app); -} - -#[tauri::command] -pub fn open_old_ui_window(app: AppHandle) { - let _window = WebviewWindowBuilder::new(&app, OLD_UI_WINDOW_ID, old_ui_url()) - .title("Old UI") - .inner_size(OLD_UI_WIDTH, OLD_UI_HEIGHT) - .build() - .unwrap(); -} - -#[tauri::command] -pub fn swap_to_old_ui(app: AppHandle) { - WebviewWindowBuilder::new(&app, OLD_UI_WINDOW_ID, old_ui_url()) - .title("Old UI") - .inner_size(OLD_UI_WIDTH, OLD_UI_HEIGHT) - .build() - .unwrap(); - if let Some(w) = app.get_webview_window(NEW_UI_WINDOW_ID) { - w.close().unwrap(); - } -} - -#[tauri::command] -pub fn swap_to_new_ui(app: AppHandle) { - show_new_ui_window(&app); - if let Some(w) = app.get_webview_window(OLD_UI_WINDOW_ID) { - w.close().unwrap(); - } -} diff --git a/src-tauri/src/window_manager/macos.rs b/src-tauri/src/window_manager/macos.rs new file mode 100644 index 00000000..d93964dc --- /dev/null +++ b/src-tauri/src/window_manager/macos.rs @@ -0,0 +1,109 @@ +use tauri::{AppHandle, LogicalPosition, Manager, Monitor, PhysicalSize, Position, WebviewWindow}; + +use crate::appstate::AppState; +use crate::window_manager::{WindowManager, NEW_UI_WINDOW_ID, OLD_UI_WINDOW_ID}; + +/// 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 { + if let Ok(Some(monitor)) = app.monitor_from_point(x, y) { + return Some(monitor); + } + + if let Ok(Some(monitor)) = app.primary_monitor() { + return Some(monitor); + } + + // On macOS, it seems this is the only working method (as of Tauri 2.11), but fortunately it + // returns the current monitor as the first one. + if let Ok(mut monitors) = app.available_monitors() { + monitors.pop() + } else { + None + } +} + +fn get_tray_window_position( + app: &AppHandle, + size: PhysicalSize, +) -> Option> { + let app_state = app.state::(); + let tray_click_position = app_state.tray_click_position.lock().unwrap().to_owned(); + + if let Some(tray_position) = tray_click_position { + let monitor = get_monitor_for_position(app, tray_position.x, tray_position.y)?; + + let scale_factor = monitor.scale_factor(); + let monitor_position = monitor.position().to_logical::(scale_factor); + let monitor_size = monitor.size().to_logical::(scale_factor); + let tray_position = tray_position.to_logical::(scale_factor); + let window_size = size.to_logical::(scale_factor); + + let mut x = tray_position.x; + let mut y = tray_position.y; + + x = x.clamp( + monitor_position.x, + monitor_position.x + monitor_size.width - window_size.width, + ); + y = y.clamp( + monitor_position.y, + monitor_position.y + monitor_size.height - window_size.height, + ); + + Some(LogicalPosition::new(x, y)) + } else { + let monitor = app.primary_monitor().ok().flatten()?; + let scale_factor = monitor.scale_factor(); + let monitor_position = monitor.position().to_logical::(scale_factor); + let monitor_size = monitor.size().to_logical::(scale_factor); + let window_size = size.to_logical::(scale_factor); + + let gap = crate::window_manager::WINDOW_GAP; + + let x = monitor_position.x + monitor_size.width - window_size.width - gap; + let y = monitor_position.y + gap; + + Some(LogicalPosition::new(x, y)) + } +} + +fn position_window_near_tray(app: &AppHandle, window: &WebviewWindow) { + let size = window.outer_size().unwrap_or_default(); + if let Some(position) = get_tray_window_position(app, size) { + if let Err(err) = window.set_position(Position::Logical(position)) { + warn!("Failed to position window near tray icon: {err}"); + } + } +} + +impl WindowManager { + pub fn open_tray(app: &AppHandle) -> tauri::Result { + let window = if let Some(window) = app.get_webview_window(NEW_UI_WINDOW_ID) { + let _ = window.unminimize(); + window + } else { + Self::build_tray_window(app)? + }; + position_window_near_tray(app, &window); + #[cfg(target_os = "macos")] + let _ = app.show(); + let _ = window.show(); + let _ = window.set_focus(); + Ok(window) + } + + pub fn open_full_view(app: &AppHandle) -> tauri::Result { + let window = if let Some(window) = app.get_webview_window(OLD_UI_WINDOW_ID) { + let _ = window.unminimize(); + window + } else { + Self::build_full_window(app)? + }; + #[cfg(target_os = "macos")] + let _ = app.show(); + let _ = window.show(); + let _ = window.set_focus(); + Ok(window) + } +} diff --git a/src-tauri/src/window_manager/mod.rs b/src-tauri/src/window_manager/mod.rs new file mode 100644 index 00000000..780e41cc --- /dev/null +++ b/src-tauri/src/window_manager/mod.rs @@ -0,0 +1,161 @@ +use tauri::{AppHandle, WebviewUrl, WebviewWindow, WebviewWindowBuilder}; + +#[cfg(not(target_os = "linux"))] +use crate::database::{models::location::Location, DB_POOL}; + +/// Returns `true` if there are any non-service locations in the database. +#[cfg(not(target_os = "linux"))] +pub async fn has_non_service_locations() -> bool { + match Location::all(&*DB_POOL, false).await { + Ok(locations) => !locations.is_empty(), + Err(_) => false, + } +} + +pub const NEW_UI_WINDOW_ID: &str = "new-ui"; +pub const OLD_UI_WINDOW_ID: &str = "old-ui"; +pub const NEW_UI_WIDTH: f64 = 380.0; +pub const NEW_UI_HEIGHT: f64 = 640.0; +pub const OLD_UI_WIDTH: f64 = 1280.0; +pub const OLD_UI_HEIGHT: f64 = 920.0; +pub const WINDOW_GAP: f64 = 20.0; + +#[must_use] +pub fn new_ui_url() -> WebviewUrl { + if cfg!(any(defguard_client_dev, debug_assertions)) { + WebviewUrl::External("http://localhost:5072".parse().unwrap()) + } else { + WebviewUrl::App("new-ui/".into()) + } +} + +#[must_use] +pub fn old_ui_url() -> WebviewUrl { + if cfg!(any(defguard_client_dev, debug_assertions)) { + WebviewUrl::External("http://localhost:5071".parse().unwrap()) + } else { + WebviewUrl::App("old-ui/index.html".into()) + } +} + +pub struct WindowManager; + +impl WindowManager { + pub fn build_tray_window(app: &AppHandle) -> tauri::Result { + WebviewWindowBuilder::new(app, NEW_UI_WINDOW_ID, new_ui_url()) + .title("Defguard") + .inner_size(NEW_UI_WIDTH, NEW_UI_HEIGHT) + .resizable(false) + .decorations(false) + .visible(false) + .always_on_top(true) + .skip_taskbar(true) + .build() + } + + pub fn build_full_window(app: &AppHandle) -> tauri::Result { + WebviewWindowBuilder::new(app, OLD_UI_WINDOW_ID, old_ui_url()) + .title("Defguard") + .inner_size(OLD_UI_WIDTH, OLD_UI_HEIGHT) + .decorations(true) + .build() + } +} + +#[cfg(target_os = "windows")] +pub mod windows; + +#[cfg(not(target_os = "windows"))] +pub mod macos; + +// Export tauri commands so they can be registered in main.rs +pub(crate) fn show_new_ui_window(app: &AppHandle) { + let _ = WindowManager::open_tray(app); +} + +pub(crate) fn show_new_ui_window_near_tray(app: &AppHandle) { + show_new_ui_window(app); +} + +#[tauri::command] +pub fn open_new_ui_window(app: AppHandle) { + show_new_ui_window(&app); +} + +#[tauri::command] +pub fn open_old_ui_window(app: AppHandle) { + let _ = WindowManager::open_full_view(&app); +} + +#[tauri::command] +pub fn swap_to_old_ui(app: AppHandle) { + tracing::info!("swap_to_old_ui called"); + tauri::async_runtime::spawn(async move { + // Sleep briefly to let the IPC handler return + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + tracing::info!("swap_to_old_ui task: Opening full view"); + match WindowManager::open_full_view(&app) { + Ok(_) => { + tracing::info!( + "swap_to_old_ui task: open_full_view succeeded, sleeping before destroy" + ); + tokio::time::sleep(std::time::Duration::from_millis(150)).await; + if let Some(w) = tauri::Manager::get_webview_window(&app, NEW_UI_WINDOW_ID) { + tracing::info!("swap_to_old_ui task: Destroying new-ui window"); + if let Err(e) = w.destroy() { + tracing::error!( + "swap_to_old_ui task: Failed to destroy new-ui window: {:?}", + e + ); + } + } else { + tracing::warn!("swap_to_old_ui task: new-ui window not found to destroy"); + } + } + Err(e) => { + tracing::error!("swap_to_old_ui task: open_full_view failed: {:?}", e); + } + } + }); +} + +#[tauri::command] +pub fn close_tray_window(app: AppHandle) { + tracing::info!("close_tray_window called"); + tauri::async_runtime::spawn(async move { + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + if let Some(w) = tauri::Manager::get_webview_window(&app, NEW_UI_WINDOW_ID) { + tracing::info!("close_tray_window task: Destroying new-ui window"); + if let Err(e) = w.destroy() { + tracing::error!( + "close_tray_window task: Failed to destroy new-ui window: {:?}", + e + ); + } + } else { + tracing::warn!("close_tray_window task: new-ui window not found to destroy"); + } + }); +} + +#[tauri::command] +pub fn swap_to_new_ui(app: AppHandle) { + tracing::info!("swap_to_new_ui called"); + tauri::async_runtime::spawn(async move { + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + tracing::info!("swap_to_new_ui task: Showing new UI window"); + show_new_ui_window(&app); + tokio::time::sleep(std::time::Duration::from_millis(150)).await; + if let Some(w) = tauri::Manager::get_webview_window(&app, OLD_UI_WINDOW_ID) { + tracing::info!("swap_to_new_ui task: Destroying old-ui window"); + if let Err(e) = w.destroy() { + tracing::error!( + "swap_to_new_ui task: Failed to destroy old-ui window: {:?}", + e + ); + } + } else { + tracing::warn!("swap_to_new_ui task: old-ui window not found to destroy"); + } + }); +} diff --git a/src-tauri/src/window_manager/windows.rs b/src-tauri/src/window_manager/windows.rs new file mode 100644 index 00000000..56c5dc46 --- /dev/null +++ b/src-tauri/src/window_manager/windows.rs @@ -0,0 +1,338 @@ +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; + +use windows::Win32::Foundation::{LPARAM, RECT}; +use windows::Win32::Graphics::Gdi::{ + EnumDisplayMonitors, GetMonitorInfoW, HDC, HMONITOR, MONITORINFOEXW, +}; +use windows::Win32::UI::HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI}; + +use crate::window_manager::{ + WindowManager, NEW_UI_HEIGHT, NEW_UI_WIDTH, NEW_UI_WINDOW_ID, OLD_UI_HEIGHT, OLD_UI_WIDTH, + OLD_UI_WINDOW_ID, WINDOW_GAP, +}; + +#[derive(Debug, Clone, PartialEq)] +pub enum TaskbarPosition { + Bottom, + Top, + Left, + Right, + HiddenOrNone, +} + +#[derive(Debug, Clone)] +pub struct MonitorInfo { + pub name: String, + pub is_primary: bool, + pub physical_x: i32, + pub physical_y: i32, + pub physical_width: u32, + pub physical_height: u32, + pub scale_factor: f64, + pub taskbar_position: TaskbarPosition, + pub taskbar_size: u32, +} + +impl WindowManager { + pub fn get_monitors() -> Vec { + let mut monitors: Vec = Vec::new(); + + unsafe extern "system" fn monitor_enum_proc( + hmonitor: HMONITOR, + _hdc: HDC, + _rect: *mut RECT, + lparam: LPARAM, + ) -> windows::core::BOOL { + let monitors = &mut *(lparam.0 as *mut Vec); + + let mut info = MONITORINFOEXW::default(); + info.monitorInfo.cbSize = std::mem::size_of::() as u32; + + if GetMonitorInfoW(hmonitor, &mut info as *mut _ as *mut _).as_bool() { + // Name + let name_len = info + .szDevice + .iter() + .position(|&c| c == 0) + .unwrap_or(info.szDevice.len()); + let name = OsString::from_wide(&info.szDevice[..name_len]) + .to_string_lossy() + .into_owned(); + + let is_primary = (info.monitorInfo.dwFlags & 1) != 0; + + // DPI and Scaling + let mut dpi_x = 0; + let mut dpi_y = 0; + let scale_factor = if GetDpiForMonitor( + hmonitor, + MDT_EFFECTIVE_DPI, + &mut dpi_x, + &mut dpi_y, + ) + .is_ok() + { + dpi_x as f64 / 96.0 + } else { + 1.0 + }; + + let physical_x = info.monitorInfo.rcMonitor.left; + let physical_y = info.monitorInfo.rcMonitor.top; + let physical_width = (info.monitorInfo.rcMonitor.right + - info.monitorInfo.rcMonitor.left) + .unsigned_abs(); + let physical_height = (info.monitorInfo.rcMonitor.bottom + - info.monitorInfo.rcMonitor.top) + .unsigned_abs(); + + // Taskbar position and size + let mut taskbar_position = TaskbarPosition::HiddenOrNone; + let mut taskbar_size = 0; + + let mon = info.monitorInfo.rcMonitor; + let work = info.monitorInfo.rcWork; + + if work.bottom < mon.bottom { + taskbar_position = TaskbarPosition::Bottom; + taskbar_size = (mon.bottom - work.bottom).unsigned_abs(); + } else if work.top > mon.top { + taskbar_position = TaskbarPosition::Top; + taskbar_size = (work.top - mon.top).unsigned_abs(); + } else if work.left > mon.left { + taskbar_position = TaskbarPosition::Left; + taskbar_size = (work.left - mon.left).unsigned_abs(); + } else if work.right < mon.right { + taskbar_position = TaskbarPosition::Right; + taskbar_size = (mon.right - work.right).unsigned_abs(); + } + + monitors.push(MonitorInfo { + name, + is_primary, + physical_x, + physical_y, + physical_width, + physical_height, + scale_factor, + taskbar_position, + taskbar_size, + }); + } + + true.into() + } + + unsafe { + let _ = EnumDisplayMonitors( + None, + None, + Some(monitor_enum_proc), + LPARAM(&mut monitors as *mut _ as isize), + ); + } + + monitors + } + + pub fn open_tray(app: &tauri::AppHandle) -> tauri::Result { + let state = tauri::Manager::state::(app); + let tray_pos = *state.tray_click_position.lock().unwrap(); + let monitors = Self::get_monitors(); + let primary = monitors + .iter() + .find(|m| m.is_primary) + .unwrap_or(&monitors[0]); + + let window = if let Some(window) = tauri::Manager::get_webview_window(app, NEW_UI_WINDOW_ID) + { + let _ = window.unminimize(); + window + } else { + Self::build_tray_window(app)? + }; + + let logical_width = NEW_UI_WIDTH; + let logical_height = NEW_UI_HEIGHT; + + let physical_width = (logical_width * primary.scale_factor) as i32; + let physical_height = (logical_height * primary.scale_factor) as i32; + + let physical_gap = (WINDOW_GAP * primary.scale_factor) as i32; + + let work_left = primary.physical_x + + if primary.taskbar_position == TaskbarPosition::Left { + primary.taskbar_size as i32 + } else { + 0 + }; + let work_top = primary.physical_y + + if primary.taskbar_position == TaskbarPosition::Top { + primary.taskbar_size as i32 + } else { + 0 + }; + let work_right = primary.physical_x + primary.physical_width as i32 + - if primary.taskbar_position == TaskbarPosition::Right { + primary.taskbar_size as i32 + } else { + 0 + }; + let work_bottom = primary.physical_y + primary.physical_height as i32 + - if primary.taskbar_position == TaskbarPosition::Bottom { + primary.taskbar_size as i32 + } else { + 0 + }; + + let (final_x, final_y) = if let Some(pos) = tray_pos { + let icon_x = pos.x as i32; + let icon_y = pos.y as i32; + let icon_width = 0; + let icon_height = 0; + + let icon_center_x = icon_x + (icon_width / 2); + let default_x = icon_center_x - (physical_width / 2); + let max_x = work_right - physical_gap - physical_width; + let min_x = work_left + physical_gap; + let clamped_x = default_x.clamp(min_x, max_x); + + let icon_center_y = icon_y + (icon_height / 2); + let default_y = icon_center_y - (physical_height / 2); + let max_y = work_bottom - physical_gap - physical_height; + let min_y = work_top + physical_gap; + let clamped_y = default_y.clamp(min_y, max_y); + + match primary.taskbar_position { + TaskbarPosition::Bottom => { + (clamped_x, work_bottom - physical_height - physical_gap) + } + TaskbarPosition::Top => (clamped_x, work_top + physical_gap), + TaskbarPosition::Left => (work_left + physical_gap, clamped_y), + TaskbarPosition::Right => (work_right - physical_width - physical_gap, clamped_y), + _ => (clamped_x, work_bottom - physical_height - physical_gap), + } + } else { + let x = work_right - physical_width - physical_gap; + let y = work_bottom - physical_height - physical_gap; + (x, y) + }; + + window.set_always_on_top(true)?; + window.set_position(tauri::PhysicalPosition::new(final_x, final_y))?; + window.show()?; + + let window_focus = window.clone(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_millis(50)); + // Toggle always_on_top to force Z-order above the Windows tray overflow popup + let _ = window_focus.set_always_on_top(false); + let _ = window_focus.set_always_on_top(true); + let _ = window_focus.set_focus(); + }); + + Ok(window) + } + + pub fn open_full_view(app: &tauri::AppHandle) -> tauri::Result { + log::info!("open_full_view: Getting monitors"); + let monitors = Self::get_monitors(); + log::info!("open_full_view: Found {} monitors", monitors.len()); + let primary = monitors + .iter() + .find(|m| m.is_primary) + .unwrap_or(&monitors[0]); + log::info!( + "open_full_view: Primary monitor scale factor: {}", + primary.scale_factor + ); + + log::info!("open_full_view: Checking if old-ui window exists"); + let window = if let Some(window) = tauri::Manager::get_webview_window(app, OLD_UI_WINDOW_ID) + { + log::info!("open_full_view: old-ui window exists, unminimizing"); + let _ = window.unminimize(); + window + } else { + log::info!("open_full_view: old-ui window does not exist, building it"); + let win = Self::build_full_window(app)?; + log::info!("open_full_view: old-ui window built successfully"); + win + }; + + log::info!("open_full_view: Querying outer_size"); + let outer_size = window.outer_size().unwrap_or(tauri::PhysicalSize { + width: (OLD_UI_WIDTH * primary.scale_factor) as u32, + height: (OLD_UI_HEIGHT * primary.scale_factor) as u32, + }); + log::info!("open_full_view: outer_size = {:?}", outer_size); + + log::info!("open_full_view: Querying inner_size"); + let inner_size = window.inner_size().unwrap_or(tauri::PhysicalSize { + width: (OLD_UI_WIDTH * primary.scale_factor) as u32, + height: (OLD_UI_HEIGHT * primary.scale_factor) as u32, + }); + log::info!("open_full_view: inner_size = {:?}", inner_size); + + let physical_width = outer_size.width as i32; + let physical_height = outer_size.height as i32; + + // Windows invisible borders (shadows) are included in outer_size for decorated windows. + let border_thickness = (physical_width - (inner_size.width as i32)) / 2; + let visible_height = physical_height - border_thickness; + + let physical_gap = (WINDOW_GAP * primary.scale_factor) as i32; + + let center_x = primary.physical_x + (primary.physical_width as i32 / 2); + let center_y = primary.physical_y + (primary.physical_height as i32 / 2); + + let mut window_x = center_x - (physical_width / 2); + let mut window_y = center_y - (visible_height / 2); + + let taskbar_size = primary.taskbar_size as i32; + + match primary.taskbar_position { + TaskbarPosition::Bottom => { + let max_y = primary.physical_y + primary.physical_height as i32 + - taskbar_size + - physical_gap; + if window_y + visible_height > max_y { + window_y = max_y - visible_height; + } + } + TaskbarPosition::Top => { + let min_y = primary.physical_y + taskbar_size + physical_gap; + if window_y < min_y { + window_y = min_y; + } + } + TaskbarPosition::Left => { + let min_x = primary.physical_x + taskbar_size + physical_gap; + if window_x + border_thickness < min_x { + window_x = min_x - border_thickness; + } + } + TaskbarPosition::Right => { + let max_x = primary.physical_x + primary.physical_width as i32 + - taskbar_size + - physical_gap; + if window_x + physical_width - border_thickness > max_x { + window_x = max_x - physical_width + border_thickness; + } + } + _ => {} + } + + log::info!( + "open_full_view: Setting position to ({}, {})", + window_x, + window_y + ); + window.set_position(tauri::PhysicalPosition::new(window_x, window_y))?; + log::info!("open_full_view: Position set, showing window"); + window.show()?; + log::info!("open_full_view: Window shown successfully"); + Ok(window) + } +} diff --git a/src/pages/client/clientAPI/clientApi.ts b/src/pages/client/clientAPI/clientApi.ts index 4278415d..8d0aec4a 100644 --- a/src/pages/client/clientAPI/clientApi.ts +++ b/src/pages/client/clientAPI/clientApi.ts @@ -130,8 +130,7 @@ const stopGlobalLogWatcher = async (): Promise => const getAppConfig = async (): Promise => invokeWrapper('command_get_app_config'); -const getPostureData = async (): Promise => - invokeWrapper('get_posture_data'); +const getPostureData = async (): Promise => invokeWrapper('get_posture_data'); const getProvisioningConfig = async (): Promise => invokeWrapper('get_provisioning_config'); diff --git a/src/shared/defguard-ui b/src/shared/defguard-ui index 1110ba80..4afa896c 160000 --- a/src/shared/defguard-ui +++ b/src/shared/defguard-ui @@ -1 +1 @@ -Subproject commit 1110ba807491689efc40a2e28383ea1c6186fcad +Subproject commit 4afa896cdc09c711ce867442818fe0e7b7b9839f