Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions apps/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -882,6 +883,7 @@ async function createWindow(options: CreateWindowOptions = {}): Promise<BrowserW
const createWindowStartedAt = performance.now()
const mac = isMac()
const cfg = await loadConfig()
const useNativeTitleBar = process.platform === 'linux' && cfg.hideNativeTitleBar === false
const restoredState = sanitizeWindowState(cfg.windowState)
currentZoomFactor = normalizeZoomFactor(cfg.zoomFactor)
const win = new BrowserWindow({
Expand All @@ -891,9 +893,11 @@ async function createWindow(options: CreateWindowOptions = {}): Promise<BrowserW
minWidth: MIN_WINDOW_WIDTH,
minHeight: MIN_WINDOW_HEIGHT,
show: false,
title: 'ZenNotes',
autoHideMenuBar: true,
titleBarStyle: mac ? 'hiddenInset' : 'hidden',
trafficLightPosition: { x: 16, y: 16 },
...(useNativeTitleBar
? {}
: ({ titleBarStyle: mac ? 'hiddenInset' : 'hidden', trafficLightPosition: { x: 16, y: 16 } } as const)),
...(mac
? {
// The renderer now runs fully opaque, so keeping the
Expand Down Expand Up @@ -1001,9 +1005,11 @@ async function createWindow(options: CreateWindowOptions = {}): Promise<BrowserW

const devServerUrl = process.env['ELECTRON_RENDERER_URL']
if (devServerUrl) {
void win.loadURL(devServerUrl)
const url = new URL(devServerUrl)
if (useNativeTitleBar) url.searchParams.set('nativeTitleBar', '1')
void win.loadURL(url.toString())
} else {
void win.loadFile(path.join(__dirname, '../renderer/index.html'))
void win.loadFile(path.join(__dirname, '../renderer/index.html'), useNativeTitleBar ? { query: { nativeTitleBar: '1' } } : undefined)
}

return win
Expand Down Expand Up @@ -2659,6 +2665,20 @@ function registerIpc(): void {
return await openMarkdownFileFromOS(path.resolve(rawPath), false)
})

handle(IPC.APP_GET_WINDOW_CHROME_SETTINGS, async (): Promise<boolean> => {
const cfg = await loadConfig()
return process.platform === 'linux' ? cfg.hideNativeTitleBar !== false : true
})

handle(
IPC.APP_SET_WINDOW_CHROME_SETTINGS,
async (_e, hide: boolean): Promise<boolean> => {
const hideNativeTitleBar = hide !== false
await updateConfig((cfg) => ({ ...cfg, hideNativeTitleBar }))
return process.platform === 'linux' ? hideNativeTitleBar : true
}
)

handle(IPC.WINDOW_TOGGLE_QUICK_CAPTURE, async () => {
await toggleQuickCaptureWindow()
})
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/main/vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -289,6 +291,7 @@ const DEFAULT_CONFIG: PersistedConfig = {
remoteWorkspaceProfiles: [],
windowState: null,
zoomFactor: 1,
hideNativeTitleBar: true,
quickCaptureHotkey: DEFAULT_QUICK_CAPTURE_HOTKEY,
quickCapturePinned: false
}
Expand Down Expand Up @@ -425,6 +428,7 @@ function normalizePersistedConfig(value: unknown): PersistedConfig {
remoteWorkspaceProfiles,
windowState: normalizeWindowState(candidate.windowState),
zoomFactor,
hideNativeTitleBar: candidate.hideNativeTitleBar !== false,
quickCaptureHotkey,
quickCapturePinned
}
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@ const api: ZenBridge = {
ipcRenderer.invoke(IPC.APP_MOVE_EXTERNAL_FILE_TO_VAULT),
openMarkdownFile: (absPath: string): Promise<boolean> =>
ipcRenderer.invoke(IPC.APP_OPEN_MARKDOWN_FILE, absPath),
getHideNativeTitleBar: (): Promise<boolean> =>
ipcRenderer.invoke(IPC.APP_GET_WINDOW_CHROME_SETTINGS),
setHideNativeTitleBar: (hide: boolean): Promise<boolean> =>
ipcRenderer.invoke(IPC.APP_SET_WINDOW_CHROME_SETTINGS, hide),
toggleQuickCapture: (): Promise<void> =>
ipcRenderer.invoke(IPC.WINDOW_TOGGLE_QUICK_CAPTURE),
getQuickCaptureHotkey: (): Promise<string> =>
Expand Down
7 changes: 4 additions & 3 deletions packages/app-core/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const EDITOR_MODULE_WARMUP_GRACE_MS = 40
let searchPaletteModulePromise: Promise<typeof import('./components/SearchPalette')> | 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<typeof import('./components/Editor')> {
editorModulePromise ??= import('./components/Editor')
Expand Down Expand Up @@ -731,7 +732,7 @@ function App(): JSX.Element {
if (!hasCompletedOnboarding) {
return (
<div className="zn-app-shell w-screen bg-paper-100 text-ink-900">
{!zenMode && <TitleBar />}
{!zenMode && !USE_NATIVE_TITLE_BAR && <TitleBar />}
<Suspense fallback={<div className="flex-1" />}>
<OnboardingWizard />
</Suspense>
Expand All @@ -746,7 +747,7 @@ function App(): JSX.Element {
if (!vault) {
return (
<div className="zn-app-shell w-screen bg-paper-100 text-ink-900">
{!zenMode && <TitleBar />}
{!zenMode && !USE_NATIVE_TITLE_BAR && <TitleBar />}
<Suspense fallback={<div className="flex-1" />}>
<EmptyVault />
</Suspense>
Expand All @@ -760,7 +761,7 @@ function App(): JSX.Element {

return (
<div className="zn-app-shell flex w-screen flex-col bg-paper-100 text-ink-900">
{!zenMode && <TitleBar />}
{!zenMode && !USE_NATIVE_TITLE_BAR && <TitleBar />}
<div className="flex min-h-0 flex-1">
{!zenMode && sidebarOpen && <Sidebar />}
{!zenMode && noteListOpen && !unifiedSidebar && <NoteList />}
Expand Down
32 changes: 32 additions & 0 deletions packages/app-core/src/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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: (
Expand Down Expand Up @@ -976,6 +999,15 @@ export function SettingsModal(): JSX.Element {
settingId="sidebar-arrows"
onChange={setShowSidebarChevrons}
/>
{isLinuxDesktop && (
<ToggleRow
label="Native Linux title bar"
description="Use the GTK title bar instead of ZenNotes custom window chrome. Applies after restart or when opening a new workspace window."
value={!hideNativeTitleBar}
settingId="native-linux-title-bar"
onChange={(useNative) => persistHideNativeTitleBar(!useNative)}
/>
)}
</Section>
</div>
)
Expand Down
2 changes: 2 additions & 0 deletions packages/bridge-contract/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ export interface ZenBridge {
zoomInApp(): Promise<number>
zoomOutApp(): Promise<number>
resetAppZoom(): Promise<number>
getHideNativeTitleBar?(): Promise<boolean>
setHideNativeTitleBar?(hide: boolean): Promise<boolean>
getAppUpdateState(): Promise<AppUpdateState>
checkForAppUpdates(): Promise<AppUpdateState>
checkForAppUpdatesWithUi(): Promise<void>
Expand Down
2 changes: 2 additions & 0 deletions packages/bridge-contract/src/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down