fix(desktop): show the Lotus titlebar only when the native frame is really gone #490

Merged
jared merged 1 commits from fix/linux-custom-chrome into lotus 2026-11-09 22:36:17 -05:00
5 changed files with 46 additions and 11 deletions
+2 -2
View File
@@ -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;
@@ -160,7 +160,7 @@ function DesktopChromeSetting() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile
title="Custom window chrome (beta)"
description="Replace the system title bar with a Lotus-styled one. Desktop only — reloads to apply."
description="Replace the system title bar with a Lotus-styled one. Desktop only — reloads to apply. On Linux (Wayland) it takes effect the next time Lotus Chat starts."
after={<Switch variant="Primary" value={customChrome} onChange={handleToggle} />}
/>
</SequenceCard>
+23 -6
View File
@@ -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 `<TitleBar/>` 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]);
}
+2 -2
View File
@@ -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
+18
View File
@@ -1,3 +1,4 @@
import { atom } from 'jotai';
import {
atomWithLocalStorage,
getLocalStorageItem,
@@ -20,3 +21,20 @@ export const customWindowChromeAtom = atomWithLocalStorage<boolean>(
(key) => getLocalStorageItem<boolean>(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<boolean | null>(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<boolean>(
(get) => get(nativeChromeActiveAtom) ?? get(customWindowChromeAtom),
);