diff --git a/src/app/features/desktop/TitleBar.tsx b/src/app/features/desktop/TitleBar.tsx index 41f3d379f..52ac44b7e 100644 --- a/src/app/features/desktop/TitleBar.tsx +++ b/src/app/features/desktop/TitleBar.tsx @@ -1,7 +1,7 @@ import React, { MouseEvent, ReactNode } from 'react'; import { useAtomValue } from 'jotai'; import { Text } from 'folds'; -import { customWindowChromeAtom } from '../../state/customWindowChrome'; +import { customChromeShownAtom } from '../../state/customWindowChrome'; import { invokeTauri, isTauri } from '../../hooks/useTauri'; import * as css from './TitleBar.css'; @@ -75,7 +75,7 @@ function ControlButton({ label, glyph, onClick, close }: ControlButtonProps) { * real traffic lights — are stripped while custom chrome is on. */ export function TitleBar() { - const enabled = useAtomValue(customWindowChromeAtom); + const enabled = useAtomValue(customChromeShownAtom); if (!isTauri() || !enabled) return null; diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 1767dc246..892321e06 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -160,7 +160,7 @@ function DesktopChromeSetting() { } /> diff --git a/src/app/hooks/useTauriWindowChrome.ts b/src/app/hooks/useTauriWindowChrome.ts index 0f42c6bf1..28ce34361 100644 --- a/src/app/hooks/useTauriWindowChrome.ts +++ b/src/app/hooks/useTauriWindowChrome.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; -import { useAtomValue } from 'jotai'; -import { customWindowChromeAtom } from '../state/customWindowChrome'; -import { invokeTauri, isTauri } from './useTauri'; +import { useAtomValue, useSetAtom } from 'jotai'; +import { customWindowChromeAtom, nativeChromeActiveAtom } from '../state/customWindowChrome'; +import { isTauri, tauriInvoke } from './useTauri'; /** * P5-47 — drive the native window frame from the `customWindowChromeAtom`. @@ -11,12 +11,29 @@ import { invokeTauri, isTauri } from './useTauri'; * web `` can take over, `enabled = false` restores the native frame. * No-op in the browser (`isTauri()` guard), so it's safe to call unconditionally * from the app shell. + * + * The command answers whether the native frame is stripped *now* (on Linux / + * Wayland the frame only changes at the next launch); that drives whether the + * Lotus titlebar is shown. Older desktop builds answer nothing, so the setting + * is used as before. */ export function useTauriWindowChrome(): void { const enabled = useAtomValue(customWindowChromeAtom); + const setNativeActive = useSetAtom(nativeChromeActiveAtom); useEffect(() => { - if (!isTauri()) return; - invokeTauri('set_custom_chrome', { enabled }); - }, [enabled]); + const invoke = isTauri() ? tauriInvoke() : undefined; + if (!invoke) return undefined; + let cancelled = false; + invoke('set_custom_chrome', { enabled }) + .then((active) => { + if (!cancelled) setNativeActive(typeof active === 'boolean' ? active : enabled); + }) + .catch(() => { + if (!cancelled) setNativeActive(enabled); + }); + return () => { + cancelled = true; + }; + }, [enabled, setNativeActive]); } diff --git a/src/app/pages/App.tsx b/src/app/pages/App.tsx index c005066b3..79b519408 100644 --- a/src/app/pages/App.tsx +++ b/src/app/pages/App.tsx @@ -31,7 +31,7 @@ import { useSessionAppeared } from '../hooks/useSessionAppeared'; import { useTauriWindowChrome } from '../hooks/useTauriWindowChrome'; import { isTauri } from '../hooks/useTauri'; import { TitleBar } from '../features/desktop/TitleBar'; -import { customWindowChromeAtom } from '../state/customWindowChrome'; +import { customChromeShownAtom } from '../state/customWindowChrome'; import { SeasonalEffect } from '../components/seasonal/SeasonalEffect'; import { applyCustomAccent, removeCustomAccent } from '../utils/accentColor'; import { zIndices } from '../styles/zIndex'; @@ -110,7 +110,7 @@ function TauriEffects() { // default, and always in the browser) this returns children unchanged, so there // is zero layout impact for everyone else. function DesktopChrome({ children }: { children: ReactNode }) { - const customChrome = useAtomValue(customWindowChromeAtom); + const customChrome = useAtomValue(customChromeShownAtom); useTauriWindowChrome(); const useChrome = isTauri() && customChrome; // Keep the wrapper element structure STABLE across the toggle so flipping the diff --git a/src/app/state/customWindowChrome.ts b/src/app/state/customWindowChrome.ts index ea8d3f7a3..ab0fdb642 100644 --- a/src/app/state/customWindowChrome.ts +++ b/src/app/state/customWindowChrome.ts @@ -1,3 +1,4 @@ +import { atom } from 'jotai'; import { atomWithLocalStorage, getLocalStorageItem, @@ -20,3 +21,20 @@ export const customWindowChromeAtom = atomWithLocalStorage( (key) => getLocalStorageItem(key, false), (key, value) => setLocalStorageItem(key, value), ); + +/** + * [cinny-desktop: Linux custom chrome] Whether the native frame is **actually** + * stripped right now, as reported by the desktop's `set_custom_chrome`. It can + * differ from the setting: on Linux / Wayland the frame only changes when the + * app restarts. `null` until the desktop has answered (or on desktop builds + * that don't report it). + */ +export const nativeChromeActiveAtom = atom(null); + +/** + * Show the Lotus titlebar only when the native frame is really gone (never two + * titlebars, never none): the desktop's report, else the setting. + */ +export const customChromeShownAtom = atom( + (get) => get(nativeChromeActiveAtom) ?? get(customWindowChromeAtom), +);