Skip to content

Commit a9afed0

Browse files
authored
fix(desktop): remove titlebar inset in fullscreen (#38793)
1 parent 09afffe commit a9afed0

8 files changed

Lines changed: 48 additions & 92 deletions

File tree

packages/app/src/components/titlebar.tsx

Lines changed: 10 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
55
import { Icon } from "@opencode-ai/ui/icon"
66
import { Button } from "@opencode-ai/ui/button"
77
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
8-
import { useTheme } from "@opencode-ai/ui/theme/context"
98
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
109
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
1110
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
@@ -30,31 +29,11 @@ import "./titlebar.css"
3029
import { newTabTooltipKeybind } from "./command-tooltip-keybind"
3130
import { normalizeSessionInfo } from "@/utils/session"
3231

33-
type TauriDesktopWindow = {
34-
startDragging?: () => Promise<void>
35-
toggleMaximize?: () => Promise<void>
36-
}
37-
38-
type TauriThemeWindow = {
39-
setTheme?: (theme?: "light" | "dark" | null) => Promise<void>
40-
}
41-
42-
type TauriApi = {
43-
window?: {
44-
getCurrentWindow?: () => TauriDesktopWindow
45-
}
46-
webviewWindow?: {
47-
getCurrentWebviewWindow?: () => TauriThemeWindow
48-
}
49-
}
50-
51-
const tauriApi = () => (window as unknown as { __TAURI__?: TauriApi }).__TAURI__
52-
const currentDesktopWindow = () => tauriApi()?.window?.getCurrentWindow?.()
53-
const currentThemeWindow = () => tauriApi()?.webviewWindow?.getCurrentWebviewWindow?.()
5432
const legacyTitlebarHeight = 40
5533
const v2TitlebarHeight = 36
5634
const minTitlebarZoom = 0.25
5735
const windowsControlsBaseWidth = 138 // 3 native Windows caption buttons at 46px each.
36+
const macTrafficLightsBaseWidth = 84
5837

5938
export type TitlebarUpdate = {
6039
version: () => string | undefined
@@ -74,7 +53,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
7453
const command = useCommand()
7554
const language = useLanguage()
7655
const settings = useSettings()
77-
const theme = useTheme()
7856
const server = useServer()
7957
const navigate = useNavigate()
8058
const location = useLocation()
@@ -85,9 +63,9 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
8563

8664
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
8765
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
88-
const electronWindows = createMemo(() => windows() && !tauriApi())
8966
const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux")
9067
const web = createMemo(() => platform.platform === "web")
68+
const macTrafficLights = createMemo(() => mac() && !platform.windowFullscreen?.())
9169
const zoom = () => platform.webviewZoom?.() ?? 1
9270
const titlebarZoom = () => (windows() ? Math.max(zoom(), minTitlebarZoom) : zoom())
9371
const counterZoom = () => (windows() && titlebarZoom() < 1 ? 1 / titlebarZoom() : 1)
@@ -176,56 +154,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
176154
},
177155
])
178156

179-
const getWin = () => {
180-
if (platform.platform !== "desktop") return
181-
return currentDesktopWindow()
182-
}
183-
184-
createEffect(() => {
185-
if (platform.platform !== "desktop") return
186-
187-
const scheme = theme.colorScheme()
188-
const value = scheme === "system" ? null : scheme
189-
190-
const win = currentThemeWindow()
191-
if (!win?.setTheme) return
192-
193-
void win.setTheme(value).catch(() => undefined)
194-
})
195-
196-
const interactive = (target: EventTarget | null) => {
197-
if (!(target instanceof Element)) return false
198-
199-
const selector =
200-
"button, a, input, textarea, select, option, [role='button'], [role='menuitem'], [contenteditable='true'], [contenteditable='']"
201-
202-
return !!target.closest(selector)
203-
}
204-
205-
const drag = (e: MouseEvent) => {
206-
if (platform.platform !== "desktop") return
207-
if (e.buttons !== 1) return
208-
if (interactive(e.target)) return
209-
210-
const win = getWin()
211-
if (!win?.startDragging) return
212-
213-
e.preventDefault()
214-
void win.startDragging().catch(() => undefined)
215-
}
216-
217-
const maximize = (e: MouseEvent) => {
218-
if (platform.platform !== "desktop") return
219-
if (interactive(e.target)) return
220-
if (e.target instanceof Element && e.target.closest("[data-tauri-decorum-tb]")) return
221-
222-
const win = getWin()
223-
if (!win?.toggleMaximize) return
224-
225-
e.preventDefault()
226-
void win.toggleMaximize().catch(() => undefined)
227-
}
228-
229157
return (
230158
<header
231159
data-slot={useV2Titlebar() ? "titlebar-v2" : undefined}
@@ -238,16 +166,12 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
238166
style={{
239167
"min-height": minHeight(),
240168
// Keep native macOS traffic lights clear even when the desktop window is narrow.
241-
"padding-left": mac() ? `${84 / zoom()}px` : 0,
242-
width: electronWindows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined,
243-
"max-width": electronWindows()
244-
? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))`
245-
: undefined,
246-
"align-self": electronWindows() ? "flex-start" : undefined,
169+
"padding-left": macTrafficLights() ? `${macTrafficLightsBaseWidth / zoom()}px` : 0,
170+
width: windows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined,
171+
"max-width": windows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined,
172+
"align-self": windows() ? "flex-start" : undefined,
247173
}}
248174
data-tauri-drag-region
249-
onMouseDown={drag}
250-
onDblClick={maximize}
251175
>
252176
<Switch>
253177
<Match when={useV2Titlebar()}>
@@ -459,8 +383,8 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
459383
classList={{
460384
"pt-2": !bottom(),
461385
"pb-2": bottom(),
462-
"md:pl-2": mac(),
463-
"md:pl-4": !mac(),
386+
"md:pl-2": macTrafficLights(),
387+
"md:pl-4": !macTrafficLights(),
464388
}}
465389
>
466390
<ChannelIndicator debugTools={props.debugTools} />
@@ -528,9 +452,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
528452
</Show>
529453
<div class="flex-1" />
530454
<TitlebarV2Right state={v2RightState()} />
531-
<Show when={windows() && !electronWindows()}>
532-
<div data-tauri-decorum-tb class="flex flex-row" />
533-
</Show>
534455
</div>
535456
)
536457
}}
@@ -543,14 +464,13 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
543464
<div
544465
classList={{
545466
"flex items-center min-w-0": true,
546-
"pl-2": !mac(),
467+
"pl-2": !macTrafficLights(),
547468
}}
548469
>
549470
<Show when={windows() || linux()}>
550471
<WindowsAppMenu command={command} platform={platform} />
551472
</Show>
552473
<Show when={mac()}>
553-
{/*<div class="h-full shrink-0" style={{ width: `${72 / zoom()}px` }} />*/}
554474
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
555475
<IconButton
556476
icon="menu"
@@ -680,12 +600,10 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
680600
"pr-2": !windows(),
681601
}}
682602
data-tauri-drag-region
683-
onMouseDown={drag}
684603
>
685604
<div id="opencode-titlebar-right" class="flex items-center gap-1 shrink-0 justify-end" />
686605
<Show when={windows()}>
687-
{!tauriApi() && <div class="shrink-0" style={{ width: windowsControlsWidth() }} />}
688-
<div data-tauri-decorum-tb class="flex flex-row" />
606+
<div class="shrink-0" style={{ width: windowsControlsWidth() }} />
689607
</Show>
690608
</div>
691609
</div>

packages/app/src/context/platform.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ type PlatformBase = {
9797
/** Webview zoom level (desktop only) */
9898
webviewZoom?: Accessor<number>
9999

100+
/** Whether the native desktop window is fullscreen */
101+
windowFullscreen?: Accessor<boolean>
102+
100103
/** Get whether native pinch/Ctrl-scroll zoom gestures are enabled (desktop only) */
101104
getPinchZoomEnabled?(): Promise<boolean> | boolean
102105

packages/desktop/src/main/ipc.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ export function registerIpcHandlers(deps: Deps) {
227227
return win?.isFocused() ?? false
228228
})
229229

230+
ipcMain.handle("get-window-fullscreen", (event: IpcMainInvokeEvent) => {
231+
const win = BrowserWindow.fromWebContents(event.sender)
232+
return win?.isFullScreen() ?? false
233+
})
234+
230235
ipcMain.handle("set-window-focus", (event: IpcMainInvokeEvent) => {
231236
const win = BrowserWindow.fromWebContents(event.sender)
232237
win?.focus()

packages/desktop/src/main/windows.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ export function createMainWindow(id: string = randomUUID()) {
219219

220220
state.manage(win)
221221
registerWindow(win, id)
222+
wireFullscreen(win)
222223
loadWindow(win, "index.html")
223224
wireZoom(win)
224225

@@ -472,6 +473,16 @@ function wireZoom(win: BrowserWindow) {
472473
})
473474
}
474475

476+
function wireFullscreen(win: BrowserWindow) {
477+
const send = (fullscreen: boolean) => {
478+
if (win.isDestroyed() || win.webContents.isDestroyed()) return
479+
win.webContents.send("window-fullscreen-changed", fullscreen)
480+
}
481+
482+
win.on("enter-full-screen", () => send(true))
483+
win.on("leave-full-screen", () => send(false))
484+
}
485+
475486
function clampZoom(value: number) {
476487
return Math.min(Math.max(value, minZoomLevel), maxZoomLevel)
477488
}

packages/desktop/src/preload/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ const api: ElectronAPI = {
100100
readClipboardImage: () => ipcRenderer.invoke("read-clipboard-image"),
101101
showNotification: (title, body) => ipcRenderer.send("show-notification", title, body),
102102
getWindowFocused: () => ipcRenderer.invoke("get-window-focused"),
103+
getWindowFullscreen: () => ipcRenderer.invoke("get-window-fullscreen"),
104+
onWindowFullscreenChanged: (cb) => {
105+
const handler = (_: unknown, fullscreen: boolean) => cb(fullscreen)
106+
ipcRenderer.on("window-fullscreen-changed", handler)
107+
return () => ipcRenderer.removeListener("window-fullscreen-changed", handler)
108+
},
103109
setWindowFocus: () => ipcRenderer.invoke("set-window-focus"),
104110
showWindow: () => ipcRenderer.invoke("show-window"),
105111
relaunch: () => ipcRenderer.send("relaunch"),

packages/desktop/src/preload/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ export type ElectronAPI = {
9191
readClipboardImage: () => Promise<{ buffer: ArrayBuffer; width: number; height: number } | null>
9292
showNotification: (title: string, body?: string) => void
9393
getWindowFocused: () => Promise<boolean>
94+
getWindowFullscreen: () => Promise<boolean>
95+
onWindowFullscreenChanged: (cb: (fullscreen: boolean) => void) => () => void
9496
setWindowFocus: () => Promise<void>
9597
showWindow: () => Promise<void>
9698
relaunch: () => void

packages/desktop/src/renderer/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { initI18n, t } from "./i18n"
2525
import { initializationData, initializationReady } from "./initialization"
2626
import { DesktopFirstLaunchOnboarding } from "./onboarding"
2727
import { resetZoom, setPinchZoomEnabled, webviewZoom, zoomIn, zoomOut } from "./webview-zoom"
28+
import { windowFullscreen } from "./window-fullscreen"
2829
import { availableStartupServer, readyWslConnections } from "./wsl/connections"
2930
import "./styles.css"
3031
import { Splash } from "@opencode-ai/ui/logo"
@@ -294,6 +295,8 @@ const createPlatform = (windowState: DesktopWindowState): Platform => {
294295

295296
webviewZoom,
296297

298+
windowFullscreen,
299+
297300
getPinchZoomEnabled: () => window.api.getPinchZoomEnabled(),
298301

299302
setPinchZoomEnabled,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createSignal } from "solid-js"
2+
3+
const [windowFullscreen, setWindowFullscreen] = createSignal(false)
4+
5+
window.api.onWindowFullscreenChanged(setWindowFullscreen)
6+
void window.api.getWindowFullscreen().then(setWindowFullscreen)
7+
8+
export { windowFullscreen }

0 commit comments

Comments
 (0)