diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index a3af1565..38afce38 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -222,6 +222,7 @@ const MAX_ZOOM_FACTOR = 3 const ZOOM_STEP = 0.1 const MAC_WINDOW_BACKGROUND_COLOR = '#1f1f1f' const MAIN_WINDOW_TABBING_IDENTIFIER = 'zennotes-vault-window' + const APP_WEBSITE_URL = 'https://zennotes.org' const APP_DISCORD_URL = 'https://discord.gg/W4fWzapKS6' const APP_REPOSITORY_URL = 'https://github.com/ZenNotes/zennotes' @@ -882,6 +883,7 @@ async function createWindow(options: CreateWindowOptions = {}): Promise => { + const cfg = await loadConfig() + return process.platform === 'linux' ? cfg.hideNativeTitleBar !== false : true + }) + + handle( + IPC.APP_SET_WINDOW_CHROME_SETTINGS, + async (_e, hide: boolean): Promise => { + const hideNativeTitleBar = hide !== false + await updateConfig((cfg) => ({ ...cfg, hideNativeTitleBar })) + return process.platform === 'linux' ? hideNativeTitleBar : true + } + ) + handle(IPC.WINDOW_TOGGLE_QUICK_CAPTURE, async () => { await toggleQuickCaptureWindow() }) diff --git a/apps/desktop/src/main/vault.ts b/apps/desktop/src/main/vault.ts index 94801123..bc24a090 100644 --- a/apps/desktop/src/main/vault.ts +++ b/apps/desktop/src/main/vault.ts @@ -270,6 +270,8 @@ export interface PersistedConfig { remoteWorkspaceProfiles: PersistedRemoteWorkspaceProfile[] windowState: PersistedWindowState | null zoomFactor: number + /** Hide the native Linux/GTK title bar and use ZenNotes' custom renderer chrome. */ + hideNativeTitleBar?: boolean /** Electron accelerator string for the system-wide quick capture hotkey. * Empty string disables the global shortcut. */ quickCaptureHotkey: string @@ -289,6 +291,7 @@ const DEFAULT_CONFIG: PersistedConfig = { remoteWorkspaceProfiles: [], windowState: null, zoomFactor: 1, + hideNativeTitleBar: true, quickCaptureHotkey: DEFAULT_QUICK_CAPTURE_HOTKEY, quickCapturePinned: false } @@ -425,6 +428,7 @@ function normalizePersistedConfig(value: unknown): PersistedConfig { remoteWorkspaceProfiles, windowState: normalizeWindowState(candidate.windowState), zoomFactor, + hideNativeTitleBar: candidate.hideNativeTitleBar !== false, quickCaptureHotkey, quickCapturePinned } diff --git a/apps/desktop/src/preload/index.ts b/apps/desktop/src/preload/index.ts index c8432050..6eabffc0 100644 --- a/apps/desktop/src/preload/index.ts +++ b/apps/desktop/src/preload/index.ts @@ -456,6 +456,10 @@ const api: ZenBridge = { ipcRenderer.invoke(IPC.APP_MOVE_EXTERNAL_FILE_TO_VAULT), openMarkdownFile: (absPath: string): Promise => ipcRenderer.invoke(IPC.APP_OPEN_MARKDOWN_FILE, absPath), + getHideNativeTitleBar: (): Promise => + ipcRenderer.invoke(IPC.APP_GET_WINDOW_CHROME_SETTINGS), + setHideNativeTitleBar: (hide: boolean): Promise => + ipcRenderer.invoke(IPC.APP_SET_WINDOW_CHROME_SETTINGS, hide), toggleQuickCapture: (): Promise => ipcRenderer.invoke(IPC.WINDOW_TOGGLE_QUICK_CAPTURE), getQuickCaptureHotkey: (): Promise => diff --git a/packages/app-core/src/App.tsx b/packages/app-core/src/App.tsx index cfd4402c..e3615f4c 100644 --- a/packages/app-core/src/App.tsx +++ b/packages/app-core/src/App.tsx @@ -25,6 +25,7 @@ const EDITOR_MODULE_WARMUP_GRACE_MS = 40 let searchPaletteModulePromise: Promise | null = null const SEARCH_PALETTE_MODULE_WARMUP_DELAY_MS = 40 const ASSET_UNDO_SHORTCUT_GRACE_MS = 30_000 +const USE_NATIVE_TITLE_BAR = new URLSearchParams(window.location.search).get('nativeTitleBar') === '1' function loadEditorModule(): Promise { editorModulePromise ??= import('./components/Editor') @@ -731,7 +732,7 @@ function App(): JSX.Element { if (!hasCompletedOnboarding) { return (
- {!zenMode && } + {!zenMode && !USE_NATIVE_TITLE_BAR && } }> @@ -746,7 +747,7 @@ function App(): JSX.Element { if (!vault) { return (
- {!zenMode && } + {!zenMode && !USE_NATIVE_TITLE_BAR && } }> @@ -760,7 +761,7 @@ function App(): JSX.Element { return (
- {!zenMode && } + {!zenMode && !USE_NATIVE_TITLE_BAR && }
{!zenMode && sidebarOpen && } {!zenMode && noteListOpen && !unifiedSidebar && } diff --git a/packages/app-core/src/components/SettingsModal.tsx b/packages/app-core/src/components/SettingsModal.tsx index 8176b8c9..01f687da 100644 --- a/packages/app-core/src/components/SettingsModal.tsx +++ b/packages/app-core/src/components/SettingsModal.tsx @@ -306,6 +306,7 @@ function formatReleaseNotesForDisplay(notes: string | null): string | null { export function SettingsModal(): JSX.Element { const zenBridge = getZenBridge() const appInfo = zenBridge.getAppInfo() + const isLinuxDesktop = appInfo.runtime === 'desktop' && zenBridge.platformSync() === 'linux' const supportsRemoteWorkspace = appInfo.runtime === 'desktop' && zenBridge.getCapabilities().supportsRemoteWorkspace const setSettingsOpen = useStore((s) => s.setSettingsOpen) @@ -471,6 +472,7 @@ export function SettingsModal(): JSX.Element { const setDarkSidebar = useStore((s) => s.setDarkSidebar) const showSidebarChevrons = useStore((s) => s.showSidebarChevrons) const setShowSidebarChevrons = useStore((s) => s.setShowSidebarChevrons) + const [hideNativeTitleBar, setHideNativeTitleBar] = useState(true) const appUpdateState = useAppUpdateState() const [editingRemoteProfile, setEditingRemoteProfile] = useState<{ mode: 'create' | 'edit' @@ -500,6 +502,20 @@ export function SettingsModal(): JSX.Element { } }, []) + useEffect(() => { + if (!isLinuxDesktop || !zenBridge.getHideNativeTitleBar) return + void zenBridge.getHideNativeTitleBar().then( + (hide) => setHideNativeTitleBar(hide), + () => setHideNativeTitleBar(true) + ) + }, [isLinuxDesktop, zenBridge]) + + const persistHideNativeTitleBar = (hideNativeTitleBar: boolean): void => { + setHideNativeTitleBar(hideNativeTitleBar) + if (!zenBridge.setHideNativeTitleBar) return + void zenBridge.setHideNativeTitleBar(hideNativeTitleBar).then(setHideNativeTitleBar) + } + useEffect(() => { let cancelled = false if (typeof window.zen.getVaultTextSearchCapabilities !== 'function') { @@ -875,6 +891,13 @@ export function SettingsModal(): JSX.Element { title: 'Sidebar arrows', description: 'Show disclosure arrows for collapsible folders and sidebar sections.', keywords: ['chevrons', 'disclosure'] + }, + { + id: 'native-linux-title-bar', + title: 'Native Linux title bar', + description: 'Use the GTK title bar instead of ZenNotes custom window chrome after restart.', + keywords: ['linux', 'gtk', 'title bar', 'header bar', 'native', 'window chrome'], + available: isLinuxDesktop } ], content: ( @@ -976,6 +999,15 @@ export function SettingsModal(): JSX.Element { settingId="sidebar-arrows" onChange={setShowSidebarChevrons} /> + {isLinuxDesktop && ( + persistHideNativeTitleBar(!useNative)} + /> + )}
) diff --git a/packages/bridge-contract/src/bridge.ts b/packages/bridge-contract/src/bridge.ts index 9ec74637..ca878ef9 100644 --- a/packages/bridge-contract/src/bridge.ts +++ b/packages/bridge-contract/src/bridge.ts @@ -80,6 +80,8 @@ export interface ZenBridge { zoomInApp(): Promise zoomOutApp(): Promise resetAppZoom(): Promise + getHideNativeTitleBar?(): Promise + setHideNativeTitleBar?(hide: boolean): Promise getAppUpdateState(): Promise checkForAppUpdates(): Promise checkForAppUpdatesWithUi(): Promise diff --git a/packages/bridge-contract/src/ipc.ts b/packages/bridge-contract/src/ipc.ts index c4393471..6466a20f 100644 --- a/packages/bridge-contract/src/ipc.ts +++ b/packages/bridge-contract/src/ipc.ts @@ -83,6 +83,8 @@ export const IPC = { APP_ZOOM_IN: 'app:zoom-in', APP_ZOOM_OUT: 'app:zoom-out', APP_ZOOM_RESET: 'app:zoom-reset', + APP_GET_WINDOW_CHROME_SETTINGS: 'app:get-window-chrome-settings', + APP_SET_WINDOW_CHROME_SETTINGS: 'app:set-window-chrome-settings', APP_UPDATER_GET_STATE: 'app-updater:get-state', APP_UPDATER_CHECK: 'app-updater:check', APP_UPDATER_CHECK_WITH_UI: 'app-updater:check-with-ui',