Files
claude-desktop-debian/scripts/patches/wco-shim.sh
Aaddrick 5c8191e82f feat(linux): hybrid titlebar mode for clickable in-app topbar (#538)
* feat(linux): hybrid titlebar mode for clickable in-app topbar

Default `CLAUDE_TITLEBAR_STYLE` is now `hybrid`: native OS frame
plus a BrowserView preload shim that convinces claude.ai's bundle
to render its in-app topbar (hamburger / sidebar / search / nav /
Cowork ghost). Stacked layout instead of Windows's combined bar,
but every button is clickable.

Why not the upstream `frame:false` + WCO config: investigation
(see docs/learnings/linux-topbar-shim.md) ruled out
`titleBarOverlay`, `titleBarStyle:'hidden'`, and the `.draggable`
CSS class as the source of the topbar click-eating drag region.
The remaining cause is a Chromium-level implicit drag region for
`frame:false` windows that exists on both X11 and Wayland and has
no Electron-API knob. With `frame:true` the OS handles dragging
and Chromium pushes no drag-region map, so the buttons receive
mouse events normally.

Modes:
- `hybrid` (default) — system frame + shim, topbar visible and
  clickable
- `native` — system frame, no shim, no in-app topbar
- `hidden` — frameless + WCO config, matches Windows/macOS
  upstream; topbar visible but not clickable on Linux. Kept for
  Wayland comparison and future investigation

Tests: tests/launcher-common.bats grew 16 cases covering
`_resolve_titlebar_style`, `build_electron_args` flag selection
per mode, and `setup_electron_env` env-var wiring per mode.
`claude-desktop --doctor` now reports the resolved mode and
warns when `hidden` is set.

Co-Authored-By: Claude <claude@anthropic.com>

* docs(learnings): add hybrid-mode screenshot

Visual reference of the stacked layout: DE-drawn titlebar on top
with native window controls, claude.ai's in-app topbar
(hamburger / search / back-forward) immediately below it.

Co-Authored-By: Claude <claude@anthropic.com>

* docs(learnings): fix codespell hit (Pre-emptive → Preemptive)

Codespell flags hyphenated "Pre-emptive" as a misspelling of
"Preemptive". Drops the hyphen to clear the spellcheck CI gate
on PR #538.

Co-Authored-By: Claude <claude@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
2026-05-01 02:47:16 -04:00

41 lines
1.5 KiB
Bash

#===============================================================================
# Inject Window Controls Overlay shim into the BrowserView preload.
#
# Sourced by: build.sh
# Sourced globals: source_dir
# Modifies globals: (none)
#===============================================================================
patch_wco_shim() {
echo '##############################################################'
echo 'Inlining WCO shim into mainView.js (Linux topbar workaround)'
local main_view='app.asar.contents/.vite/build/mainView.js'
if [[ ! -f $main_view ]]; then
echo "Error: mainView.js not found at $main_view." >&2
exit 1
fi
if grep -q '__claude_wco_shim' "$main_view"; then
echo 'mainView.js already has WCO shim, skipping inject'
echo '##############################################################'
return 0
fi
# Sandboxed preloads can only require a fixed allowlist of modules
# (electron, ipcRenderer, contextBridge, webFrame…). A relative
# require to a sibling file fails with "module not found" and
# aborts the entire preload — taking desktopBootFeatures and the
# rest of mainView's exposeInMainWorld surface down with it.
# So we inline the shim source directly at the top of mainView.js
# instead of pulling it in via require.
local shim_content
shim_content=$(cat "$source_dir/scripts/wco-shim.js")
local original
original=$(cat "$main_view")
printf '%s\n%s' "$shim_content" "$original" > "$main_view"
echo 'Inlined WCO shim at top of mainView.js'
echo '##############################################################'
}