Files
claude-desktop-debian/scripts/patches/quick-window.sh
aaddrick ff4821e087 refactor: split build.sh into topical modules under scripts/
Splits the 2124-line build.sh into a 318-line orchestrator plus
16 topical modules, grouped so CODEOWNERS can assign per-subsystem
reviewers:

    scripts/_common.sh              shared shell utilities
    scripts/setup/                  host detection, deps, download
    scripts/patches/                regex patches on minified JS
      _common.sh                    extract_electron_variable etc.
      app-asar.sh                   wrapper injection
      titlebar.sh
      tray.sh                       menu handler + icon selection
      quick-window.sh
      claude-code.sh
      cowork.sh                     cowork linux patching (largest)
    scripts/staging/                post-patch file staging

build.sh now sources each module in dependency order and retains
only run_packaging, cleanup_build, print_next_steps, and main.
All globals stay at the top of build.sh and are read by sourced
modules; each module's header documents which globals it reads and
mutates (implicit-contract documentation).

This is a pure-move refactor. Function bodies were copied verbatim
— verified by byte-identical diff of the function set vs the
pre-split build.sh (34 functions, all present with identical bodies).

Note: .github/workflows/shellcheck.yml may benefit from a '-x' flag
so shellcheck follows the new '# shellcheck source=' directives, but
that CI tweak is left as a separate concern.

Co-Authored-By: Claude <claude@anthropic.com>
2026-04-20 07:12:22 -04:00

144 lines
5.8 KiB
Bash

#===============================================================================
# Quick-window patches: KDE-gated blur/focus workarounds for the pop-up menu
# so the main window reappears after quick-entry submit.
#
# Sourced by: build.sh
# Sourced globals: (none — all context is captured from index.js at runtime)
# Modifies globals: (none)
#===============================================================================
patch_quick_window() {
echo 'Patching quick window for Linux...'
local index_js='app.asar.contents/.vite/build/index.js'
# Extract the quick window variable name from the unique "pop-up-menu"
# setAlwaysOnTop call, e.g.: Sa.setAlwaysOnTop(!0,"pop-up-menu")
local quick_var
quick_var=$(grep -oP '\w+(?=\.setAlwaysOnTop\(\s*!0\s*,\s*"pop-up-menu"\))' \
"$index_js" | head -1)
if [[ -z $quick_var ]]; then
echo 'WARNING: Could not extract quick window variable name'
echo '##############################################################'
return
fi
echo " Found quick window variable: $quick_var"
local quick_var_re="${quick_var//\$/\\$}"
# Part 1: Add blur() before hide() on the quick window so that
# isFocused() returns false after hiding (Electron Linux bug on KDE).
# The hide call sits after || (e.g. GUARD()||VAR.hide()), so both
# calls must be wrapped in parens to preserve short-circuit semantics.
# Gated to KDE only: on GNOME/Ubuntu the blur() regresses quick entry
# (see #393), and the focus-stale bug doesn't manifest there.
local de_check='(process.env.XDG_CURRENT_DESKTOP||"")'
de_check+='.toLowerCase().includes("kde")'
if grep -qF "${quick_var}.blur(),${quick_var}.hide()" "$index_js"; then
echo ' Quick window blur already patched'
elif grep -qP "\|\|${quick_var_re}\.hide\(\)" "$index_js"; then
sed -i \
"s/||${quick_var_re}\.hide()/||(${de_check}?(${quick_var}.blur(),${quick_var}.hide()):${quick_var}.hide())/g" \
"$index_js"
echo ' Added KDE-gated blur() before hide() on quick window'
else
echo ' WARNING: Could not find quick window hide() call'
fi
# Part 2: Fix main window not appearing after quick entry submit.
# On KDE, isFocused() can return stale true after hiding, causing
# FOCUS_CHECK()||Lt.show() to skip the show. Gate the visibility-check
# replacement to KDE only: on GNOME, the original focus check works
# and replacing it regresses quick entry (see #393).
if INDEX_JS="$index_js" node << 'QUICK_WINDOW_PATCH'
const fs = require('fs');
const indexJs = process.env.INDEX_JS;
let code = fs.readFileSync(indexJs, 'utf8');
let patchCount = 0;
// Find the minified isWindowFocused function via its named property
// export: isWindowFocused: () => !!NAME()
const focusedPropRe = /isWindowFocused:\s*\(\)\s*=>\s*!!(\w+)\(\)/;
const focusedMatch = code.match(focusedPropRe);
if (!focusedMatch) {
console.log(' WARNING: Could not find isWindowFocused function');
process.exit(0);
}
const focusFn = focusedMatch[1];
console.log(' Found focus check function: ' + focusFn);
// Find the sibling isVisible function defined near the focus function
const focusFnIdx = code.indexOf('function ' + focusFn + '(');
const nearbyCode = code.substring(focusFnIdx, focusFnIdx + 500);
const visFnRe = /function (\w+)\(\)\{return!\w+\|\|\w+\.isDestroyed\(\)\?!1:\w+\.isVisible\(\)/;
const visMatch = nearbyCode.match(visFnRe);
if (!visMatch) {
console.log(' WARNING: Could not find visibility function near ' +
focusFn);
process.exit(0);
}
const visFn = visMatch[1];
console.log(' Found visibility check function: ' + visFn);
// Anchor on unique QuickEntry log strings to patch only the right sites
const anchors = [
'Navigating to existing chat',
'Creating new chat with submit_quick_entry',
];
for (const anchor of anchors) {
const anchorIdx = code.indexOf(anchor);
if (anchorIdx === -1) {
console.log(' WARNING: anchor not found: ' + anchor);
continue;
}
// Search region after anchor (1500 chars covers promise chains)
const region = code.substring(anchorIdx, anchorIdx + 1500);
// Idempotency: if region already contains the DE gate, skip
if (region.indexOf('XDG_CURRENT_DESKTOP') !== -1) {
console.log(' Quick entry show() already patched near "' +
anchor.substring(0, 30) + '..."');
continue;
}
const showRe = new RegExp(
focusFn.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
'\\(\\)\\|\\|(\\w+)\\.show\\(\\)'
);
const showMatch = region.match(showRe);
if (showMatch) {
const oldStr = showMatch[0];
const mainWin = showMatch[1];
// Gate the visibility check to KDE only; fall back to original
// focus check on GNOME/other so #390 doesn't regress them (#393).
const deCheck = '(process.env.XDG_CURRENT_DESKTOP||"")' +
'.toLowerCase().includes("kde")';
const newStr = '(' + deCheck + '?' + visFn + '():' +
focusFn + '())||' + mainWin + '.show()';
if (oldStr !== newStr) {
const absIdx = anchorIdx + region.indexOf(oldStr);
code = code.substring(0, absIdx) + newStr +
code.substring(absIdx + oldStr.length);
console.log(' KDE-gated ' + focusFn + '()/' + visFn +
'() for show() near "' + anchor.substring(0, 30) + '..."');
patchCount++;
}
} else {
console.log(' WARNING: show() pattern not found near "' +
anchor + '"');
}
}
if (patchCount > 0) {
fs.writeFileSync(indexJs, code);
console.log(' Patched ' + patchCount +
' quick entry show() calls to use visibility check');
} else {
console.log(' WARNING: No quick entry show() calls patched');
}
QUICK_WINDOW_PATCH
then
echo 'Quick window patches applied'
else
echo 'WARNING: Quick window show patch failed' >&2
fi
echo '##############################################################'
}