Skip to content

Commit d611a6a

Browse files
Apply PR #38793: fix(desktop): remove titlebar inset in fullscreen
2 parents 7345596 + 04d287b commit d611a6a

9 files changed

Lines changed: 54 additions & 90 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { macTitlebarLeftPadding } from "./titlebar-padding"
3+
4+
describe("macOS titlebar padding", () => {
5+
test("reserves traffic light space while windowed", () => {
6+
expect(macTitlebarLeftPadding(1, false)).toBe("84px")
7+
expect(macTitlebarLeftPadding(2, false)).toBe("42px")
8+
})
9+
10+
test("does not reserve traffic light space in fullscreen", () => {
11+
expect(macTitlebarLeftPadding(1, true)).toBe("0px")
12+
})
13+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const macTrafficLightsWidth = 84
2+
3+
export function macTitlebarLeftPadding(zoom: number, fullscreen: boolean) {
4+
if (fullscreen) return "0px"
5+
return `${macTrafficLightsWidth / zoom}px`
6+
}

packages/app/src/components/titlebar.tsx

Lines changed: 7 additions & 90 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"
@@ -28,28 +27,8 @@ import { tabKey, useTabs } from "@/context/tabs"
2827
import type { PromptSession } from "@/context/prompt"
2928
import "./titlebar.css"
3029
import { newTabTooltipKeybind } from "./command-tooltip-keybind"
30+
import { macTitlebarLeftPadding } from "./titlebar-padding"
3131

32-
type TauriDesktopWindow = {
33-
startDragging?: () => Promise<void>
34-
toggleMaximize?: () => Promise<void>
35-
}
36-
37-
type TauriThemeWindow = {
38-
setTheme?: (theme?: "light" | "dark" | null) => Promise<void>
39-
}
40-
41-
type TauriApi = {
42-
window?: {
43-
getCurrentWindow?: () => TauriDesktopWindow
44-
}
45-
webviewWindow?: {
46-
getCurrentWebviewWindow?: () => TauriThemeWindow
47-
}
48-
}
49-
50-
const tauriApi = () => (window as unknown as { __TAURI__?: TauriApi }).__TAURI__
51-
const currentDesktopWindow = () => tauriApi()?.window?.getCurrentWindow?.()
52-
const currentThemeWindow = () => tauriApi()?.webviewWindow?.getCurrentWebviewWindow?.()
5332
const legacyTitlebarHeight = 40
5433
const v2TitlebarHeight = 36
5534
const minTitlebarZoom = 0.25
@@ -73,7 +52,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
7352
const command = useCommand()
7453
const language = useLanguage()
7554
const settings = useSettings()
76-
const theme = useTheme()
7755
const server = useServer()
7856
const navigate = useNavigate()
7957
const location = useLocation()
@@ -84,7 +62,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
8462

8563
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
8664
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
87-
const electronWindows = createMemo(() => windows() && !tauriApi())
8865
const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux")
8966
const web = createMemo(() => platform.platform === "web")
9067
const zoom = () => platform.webviewZoom?.() ?? 1
@@ -175,56 +152,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
175152
},
176153
])
177154

178-
const getWin = () => {
179-
if (platform.platform !== "desktop") return
180-
return currentDesktopWindow()
181-
}
182-
183-
createEffect(() => {
184-
if (platform.platform !== "desktop") return
185-
186-
const scheme = theme.colorScheme()
187-
const value = scheme === "system" ? null : scheme
188-
189-
const win = currentThemeWindow()
190-
if (!win?.setTheme) return
191-
192-
void win.setTheme(value).catch(() => undefined)
193-
})
194-
195-
const interactive = (target: EventTarget | null) => {
196-
if (!(target instanceof Element)) return false
197-
198-
const selector =
199-
"button, a, input, textarea, select, option, [role='button'], [role='menuitem'], [contenteditable='true'], [contenteditable='']"
200-
201-
return !!target.closest(selector)
202-
}
203-
204-
const drag = (e: MouseEvent) => {
205-
if (platform.platform !== "desktop") return
206-
if (e.buttons !== 1) return
207-
if (interactive(e.target)) return
208-
209-
const win = getWin()
210-
if (!win?.startDragging) return
211-
212-
e.preventDefault()
213-
void win.startDragging().catch(() => undefined)
214-
}
215-
216-
const maximize = (e: MouseEvent) => {
217-
if (platform.platform !== "desktop") return
218-
if (interactive(e.target)) return
219-
if (e.target instanceof Element && e.target.closest("[data-tauri-decorum-tb]")) return
220-
221-
const win = getWin()
222-
if (!win?.toggleMaximize) return
223-
224-
e.preventDefault()
225-
void win.toggleMaximize().catch(() => undefined)
226-
}
227-
228155
return (
229156
<header
230157
data-slot={useV2Titlebar() ? "titlebar-v2" : undefined}
@@ -236,17 +163,13 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
236163
}}
237164
style={{
238165
"min-height": minHeight(),
239-
// Keep native macOS traffic lights clear even when the desktop window is narrow.
240-
"padding-left": mac() ? `${84 / zoom()}px` : 0,
241-
width: electronWindows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined,
242-
"max-width": electronWindows()
243-
? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))`
244-
: undefined,
245-
"align-self": electronWindows() ? "flex-start" : undefined,
166+
// Keep visible native macOS traffic lights clear even when the desktop window is narrow.
167+
"padding-left": mac() ? macTitlebarLeftPadding(zoom(), platform.windowFullscreen?.() ?? false) : 0,
168+
width: windows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined,
169+
"max-width": windows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined,
170+
"align-self": windows() ? "flex-start" : undefined,
246171
}}
247172
data-tauri-drag-region
248-
onMouseDown={drag}
249-
onDblClick={maximize}
250173
>
251174
<Switch>
252175
<Match when={useV2Titlebar()}>
@@ -527,9 +450,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
527450
</Show>
528451
<div class="flex-1" />
529452
<TitlebarV2Right state={v2RightState()} />
530-
<Show when={windows() && !electronWindows()}>
531-
<div data-tauri-decorum-tb class="flex flex-row" />
532-
</Show>
533453
</div>
534454
)
535455
}}
@@ -549,7 +469,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
549469
<WindowsAppMenu command={command} platform={platform} />
550470
</Show>
551471
<Show when={mac()}>
552-
{/*<div class="h-full shrink-0" style={{ width: `${72 / zoom()}px` }} />*/}
553472
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
554473
<IconButton
555474
icon="menu"
@@ -678,12 +597,10 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
678597
"pr-2": !windows(),
679598
}}
680599
data-tauri-drag-region
681-
onMouseDown={drag}
682600
>
683601
<div id="opencode-titlebar-right" class="flex items-center gap-1 shrink-0 justify-end" />
684602
<Show when={windows()}>
685-
{!tauriApi() && <div class="shrink-0" style={{ width: windowsControlsWidth() }} />}
686-
<div data-tauri-decorum-tb class="flex flex-row" />
603+
<div class="shrink-0" style={{ width: windowsControlsWidth() }} />
687604
</Show>
688605
</div>
689606
</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: 6 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,11 @@ function wireZoom(win: BrowserWindow) {
472473
})
473474
}
474475

476+
function wireFullscreen(win: BrowserWindow) {
477+
win.on("enter-full-screen", () => win.webContents.send("window-fullscreen-changed", true))
478+
win.on("leave-full-screen", () => win.webContents.send("window-fullscreen-changed", false))
479+
}
480+
475481
function clampZoom(value: number) {
476482
return Math.min(Math.max(value, minZoomLevel), maxZoomLevel)
477483
}

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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ import "./styles.css"
3030
import { Splash } from "@opencode-ai/ui/logo"
3131
import { useTheme } from "@opencode-ai/ui/theme/context"
3232

33+
const [windowFullscreen, setWindowFullscreen] = createSignal(false)
34+
window.api.onWindowFullscreenChanged(setWindowFullscreen)
35+
void window.api.getWindowFullscreen().then(setWindowFullscreen)
36+
3337
const root = document.getElementById("root")
3438
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
3539
throw new Error(t("error.dev.rootNotFound"))
@@ -294,6 +298,8 @@ const createPlatform = (windowState: DesktopWindowState): Platform => {
294298

295299
webviewZoom,
296300

301+
windowFullscreen,
302+
297303
getPinchZoomEnabled: () => window.api.getPinchZoomEnabled(),
298304

299305
setPinchZoomEnabled,

0 commit comments

Comments
 (0)