From 6445d7eb78229f13bd12a0d3cd03e31bc603ae05 Mon Sep 17 00:00:00 2001 From: patrickjaja Date: Tue, 25 Nov 2025 10:46:38 +0000 Subject: [PATCH] Update to version 1.0.1217 --- PKGBUILD | 627 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 565 insertions(+), 62 deletions(-) diff --git a/PKGBUILD b/PKGBUILD index 0038112c94977..94d9b861cbdfe 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -80,6 +80,21 @@ const KeyboardKey = { }; Object.freeze(KeyboardKey); +// AuthRequest stub - not available on Linux, will cause fallback to system browser +class AuthRequest { + static isAvailable() { + return false; + } + + async start(url, scheme, windowHandle) { + throw new Error('AuthRequest not available on Linux'); + } + + cancel() { + // no-op + } +} + let tray = null; function createTray() { @@ -120,13 +135,14 @@ module.exports = { clearOverlayIcon: () => {}, createTray, getTray: () => tray, - KeyboardKey + KeyboardKey, + AuthRequest }; claude_native_js_EOF # Applying patch: fix_claude_code.py echo "Applying patch: fix_claude_code.py..." - python3 - "app.asar.contents/.vite/build/index.js" << 'fix_claude_code_py_EOF' + if ! python3 - "app.asar.contents/.vite/build/index.js" << 'fix_claude_code_py_EOF' #!/usr/bin/env python3 # @patch-target: app.asar.contents/.vite/build/index.js # @patch-type: python @@ -147,46 +163,57 @@ import os def patch_claude_code(filepath): """Patch the Claude Code downloader to use system binary on Linux.""" - print(f"Patching Claude Code support in: {filepath}") + print(f"=== Patch: fix_claude_code ===") + print(f" Target: {filepath}") + + if not os.path.exists(filepath): + print(f" [FAIL] File not found: {filepath}") + return False with open(filepath, 'rb') as f: content = f.read() original_content = content - patches_applied = 0 + failed = False # Patch 1: getBinaryPathIfReady() - Check /usr/bin/claude first on Linux - # Original: async getBinaryPathIfReady(){return await this.binaryExists(this.requiredVersion)?this.getBinaryPath(this.requiredVersion):null} old_binary_ready = b'async getBinaryPathIfReady(){return await this.binaryExists(this.requiredVersion)?this.getBinaryPath(this.requiredVersion):null}' new_binary_ready = b'async getBinaryPathIfReady(){console.log("[ClaudeCode] getBinaryPathIfReady called, platform:",process.platform);if(process.platform==="linux"){try{const fs=require("fs");const exists=fs.existsSync("/usr/bin/claude");console.log("[ClaudeCode] /usr/bin/claude exists:",exists);if(exists)return"/usr/bin/claude"}catch(e){console.log("[ClaudeCode] error checking /usr/bin/claude:",e)}}return await this.binaryExists(this.requiredVersion)?this.getBinaryPath(this.requiredVersion):null}' - if old_binary_ready in content: + count1 = content.count(old_binary_ready) + if count1 >= 1: content = content.replace(old_binary_ready, new_binary_ready) - patches_applied += 1 - print(" ✓ getBinaryPathIfReady() patched") + print(f" [OK] getBinaryPathIfReady(): {count1} match(es)") else: - print(" ⚠ getBinaryPathIfReady() pattern not found") + print(f" [FAIL] getBinaryPathIfReady(): 0 matches, expected >= 1") + failed = True # Patch 2: getStatus() - Return Ready if system binary exists on Linux old_status = b'async getStatus(){if(await this.binaryExists(this.requiredVersion))' new_status = b'async getStatus(){console.log("[ClaudeCode] getStatus called, platform:",process.platform);if(process.platform==="linux"){try{const fs=require("fs");const exists=fs.existsSync("/usr/bin/claude");console.log("[ClaudeCode] /usr/bin/claude exists:",exists);if(exists){console.log("[ClaudeCode] returning Ready");return Rv.Ready}}catch(e){console.log("[ClaudeCode] error:",e)}}if(await this.binaryExists(this.requiredVersion))' - if old_status in content: + count2 = content.count(old_status) + if count2 >= 1: content = content.replace(old_status, new_status) - patches_applied += 1 - print(" ✓ getStatus() patched") + print(f" [OK] getStatus(): {count2} match(es)") else: - print(" ⚠ getStatus() pattern not found") + print(f" [FAIL] getStatus(): 0 matches, expected >= 1") + failed = True + + # Check results + if failed: + print(" [FAIL] Some patterns did not match") + return False # Write back if changed if content != original_content: with open(filepath, 'wb') as f: f.write(content) - print(f"Claude Code patches applied: {patches_applied}/2") + print(" [PASS] All patterns matched and applied") return True else: - print("Warning: No Claude Code patches applied") - return False + print(" [WARN] No changes made (patterns may have already been applied)") + return True if __name__ == "__main__": @@ -194,18 +221,18 @@ if __name__ == "__main__": print(f"Usage: {sys.argv[0]} ") sys.exit(1) - filepath = sys.argv[1] - if not os.path.exists(filepath): - print(f"Error: File not found: {filepath}") - sys.exit(1) - - success = patch_claude_code(filepath) + success = patch_claude_code(sys.argv[1]) sys.exit(0 if success else 1) fix_claude_code_py_EOF + then + echo "ERROR: Patch fix_claude_code.py FAILED - patterns did not match" + echo "Please check if upstream changed the target file structure" + exit 1 + fi # Applying patch: fix_locale_paths.py echo "Applying patch: fix_locale_paths.py..." - python3 - "app.asar.contents/.vite/build/index.js" << 'fix_locale_paths_py_EOF' + if ! python3 - "app.asar.contents/.vite/build/index.js" << 'fix_locale_paths_py_EOF' #!/usr/bin/env python3 # @patch-target: app.asar.contents/.vite/build/index.js # @patch-type: python @@ -226,40 +253,54 @@ import re def patch_locale_paths(filepath): """Patch locale file paths to use Linux install location.""" - print(f"Patching locale paths in: {filepath}") + print(f"=== Patch: fix_locale_paths ===") + print(f" Target: {filepath}") + + if not os.path.exists(filepath): + print(f" [FAIL] File not found: {filepath}") + return False with open(filepath, 'rb') as f: content = f.read() original_content = content - patches_applied = 0 + failed = False # Replace process.resourcesPath with our locale path old_resource_path = b'process.resourcesPath' new_resource_path = b'"/usr/lib/claude-desktop-bin/locales"' - if old_resource_path in content: - count = content.count(old_resource_path) + count1 = content.count(old_resource_path) + if count1 >= 1: content = content.replace(old_resource_path, new_resource_path) - patches_applied += count - print(f" Replaced process.resourcesPath: {count} occurrence(s)") + print(f" [OK] process.resourcesPath: {count1} match(es)") + else: + print(f" [FAIL] process.resourcesPath: 0 matches, expected >= 1") + failed = True - # Also replace any hardcoded electron paths + # Also replace any hardcoded electron paths (optional - may not exist) pattern = rb'/usr/lib/electron\d+/resources' replacement = b'/usr/lib/claude-desktop-bin/locales' - content, count = re.subn(pattern, replacement, content) - if count > 0: - patches_applied += count - print(f" Replaced hardcoded electron paths: {count} occurrence(s)") + content, count2 = re.subn(pattern, replacement, content) + if count2 > 0: + print(f" [OK] hardcoded electron paths: {count2} match(es)") + else: + print(f" [INFO] hardcoded electron paths: 0 matches (optional)") + # Check results + if failed: + print(" [FAIL] Required patterns did not match") + return False + + # Write back if changed if content != original_content: with open(filepath, 'wb') as f: f.write(content) - print(f"Locale path patches applied: {patches_applied} total") + print(" [PASS] All required patterns matched and applied") return True else: - print("Warning: No locale path changes made") - return False + print(" [WARN] No changes made (patterns may have already been applied)") + return True if __name__ == "__main__": @@ -267,28 +308,234 @@ if __name__ == "__main__": print(f"Usage: {sys.argv[0]} ") sys.exit(1) - filepath = sys.argv[1] - if not os.path.exists(filepath): - print(f"Error: File not found: {filepath}") - sys.exit(1) - - success = patch_locale_paths(filepath) + success = patch_locale_paths(sys.argv[1]) sys.exit(0 if success else 1) fix_locale_paths_py_EOF + then + echo "ERROR: Patch fix_locale_paths.py FAILED - patterns did not match" + echo "Please check if upstream changed the target file structure" + exit 1 + fi + + # Applying patch: fix_native_frame.py + echo "Applying patch: fix_native_frame.py..." + if ! python3 - "app.asar.contents/.vite/build/index.js" << 'fix_native_frame_py_EOF' +#!/usr/bin/env python3 +# @patch-target: app.asar.contents/.vite/build/index.js +# @patch-type: python +""" +Patch Claude Desktop to use native window frames on Linux. + +On Linux/XFCE, frame:false doesn't work properly - the WM still adds decorations. +So we use frame:true for native window management, but also show Claude's internal +title bar for the hamburger menu and Claude icon. + +IMPORTANT: Quick Entry window needs frame:false for transparency - we preserve that. + +Usage: python3 fix_native_frame.py +""" + +import sys +import os +import re + + +def patch_native_frame(filepath): + """Patch BrowserWindow to use native frames on Linux (main window only).""" + + print(f"=== Patch: fix_native_frame ===") + print(f" Target: {filepath}") + + if not os.path.exists(filepath): + print(f" [FAIL] File not found: {filepath}") + return False + + with open(filepath, 'rb') as f: + content = f.read() + + original_content = content + failed = False + + # Step 1: Check if transparent window pattern exists (Quick Entry) + quick_entry_pattern = rb'transparent:!0,frame:!1' + has_quick_entry = quick_entry_pattern in content + if has_quick_entry: + print(f" [OK] Quick Entry pattern found (will preserve)") + else: + print(f" [INFO] Quick Entry pattern not found (may be already patched)") + + # Step 2: Temporarily mark the Quick Entry pattern + marker = b'__QUICK_ENTRY_FRAME_PRESERVE__' + if has_quick_entry: + content = content.replace(quick_entry_pattern, b'transparent:!0,' + marker) + + # Step 3: Replace frame:!1 (false) with frame:true for main window + pattern = rb'frame\s*:\s*!1' + replacement = b'frame:true' + content, count = re.subn(pattern, replacement, content) + if count > 0: + print(f" [OK] frame:!1 -> frame:true: {count} match(es)") + else: + print(f" [FAIL] frame:!1: 0 matches, expected >= 1") + failed = True + + # Step 4: Restore Quick Entry frame setting + if has_quick_entry: + content = content.replace(b'transparent:!0,' + marker, quick_entry_pattern) + print(f" [OK] Restored Quick Entry frame:!1 (transparent)") + + # Check results + if failed: + print(" [FAIL] Required patterns did not match") + return False + + # Write back if changed + if content != original_content: + with open(filepath, 'wb') as f: + f.write(content) + print(" [PASS] Native frame patched successfully") + return True + else: + print(" [WARN] No changes made (patterns may have already been applied)") + return True + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + success = patch_native_frame(sys.argv[1]) + sys.exit(0 if success else 1) +fix_native_frame_py_EOF + then + echo "ERROR: Patch fix_native_frame.py FAILED - patterns did not match" + echo "Please check if upstream changed the target file structure" + exit 1 + fi + + # Applying patch: fix_quick_entry_position.py + echo "Applying patch: fix_quick_entry_position.py..." + if ! python3 - "app.asar.contents/.vite/build/index.js" << 'fix_quick_entry_position_py_EOF' +#!/usr/bin/env python3 +# @patch-target: app.asar.contents/.vite/build/index.js +# @patch-type: python +""" +Patch Claude Desktop Quick Entry to spawn on the monitor where the cursor is. + +The original code uses getPrimaryDisplay() for the fallback position, which +always spawns the Quick Entry window on the primary monitor (usually the +laptop screen). This patch changes it to use getDisplayNearestPoint() with +the cursor position, so the window appears on the monitor where the user +is currently working. + +Usage: python3 fix_quick_entry_position.py +""" + +import sys +import os +import re + + +def patch_quick_entry_position(filepath): + """Patch Quick Entry to spawn on cursor's monitor instead of primary display.""" + + print(f"=== Patch: fix_quick_entry_position ===") + print(f" Target: {filepath}") + + if not os.path.exists(filepath): + print(f" [FAIL] File not found: {filepath}") + return False + + with open(filepath, 'rb') as f: + content = f.read() + + original_content = content + failed = False + + # Patch 1: In pTe() function - the fallback position generator + pattern1 = rb'(function pTe\(\)\{const t=ce\.screen\.)getPrimaryDisplay\(\)' + replacement1 = rb'\1getDisplayNearestPoint(ce.screen.getCursorScreenPoint())' + content, count1 = re.subn(pattern1, replacement1, content) + if count1 > 0: + print(f" [OK] pTe() function: {count1} match(es)") + else: + print(f" [FAIL] pTe() function: 0 matches, expected >= 1") + failed = True + + # Patch 2: In dTe() function - the fallback display lookup + pattern2 = rb'r\|\|\(r=ce\.screen\.getPrimaryDisplay\(\)\)' + replacement2 = rb'r||(r=ce.screen.getDisplayNearestPoint(ce.screen.getCursorScreenPoint()))' + content, count2 = re.subn(pattern2, replacement2, content) + if count2 > 0: + print(f" [OK] dTe() fallback: {count2} match(es)") + else: + print(f" [FAIL] dTe() fallback: 0 matches, expected >= 1") + failed = True + + # Patch 3: Override dTe to always use cursor position (optional enhancement) + pattern3 = rb'function dTe\(\)\{const t=hn\.get\("quickWindowPosition",null\),e=ce\.screen\.getAllDisplays\(\);if\(!\(t&&t\.absolutePointInWorkspace&&t\.monitor&&t\.relativePointFromMonitor\)\)return pTe\(\)' + replacement3 = rb'function dTe(){return pTe()/*patched to always use cursor position*/;const t=hn.get("quickWindowPosition",null),e=ce.screen.getAllDisplays();if(!(t&&t.absolutePointInWorkspace&&t.monitor&&t.relativePointFromMonitor))return pTe()' + content, count3 = re.subn(pattern3, replacement3, content) + if count3 > 0: + print(f" [OK] dTe() override: {count3} match(es)") + else: + print(f" [INFO] dTe() override: 0 matches (optional)") + + # Check results + if failed: + print(" [FAIL] Required patterns did not match") + return False + + # Write back if changed + if content != original_content: + with open(filepath, 'wb') as f: + f.write(content) + print(" [PASS] Quick Entry position patched successfully") + return True + else: + print(" [WARN] No changes made (patterns may have already been applied)") + return True + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + success = patch_quick_entry_position(sys.argv[1]) + sys.exit(0 if success else 1) +fix_quick_entry_position_py_EOF + then + echo "ERROR: Patch fix_quick_entry_position.py FAILED - patterns did not match" + echo "Please check if upstream changed the target file structure" + exit 1 + fi # Applying patch: fix_title_bar.py echo "Applying patch: fix_title_bar.py..." local target_file=$(find app.asar.contents/.vite/renderer/main_window/assets -name "MainWindowPage-*.js" 2>/dev/null | head -1) if [ -n "$target_file" ]; then - python3 - "$target_file" << 'fix_title_bar_py_EOF' + if ! python3 - "$target_file" << 'fix_title_bar_py_EOF' #!/usr/bin/env python3 # @patch-target: app.asar.contents/.vite/renderer/main_window/assets/MainWindowPage-*.js # @patch-type: python """ -Patch Claude Desktop title bar detection issue on Linux. +Patch Claude Desktop to show internal title bar on Linux. -The original code has a negated condition that causes issues on Linux. -This patch fixes: if(!var1 && var2) -> if(var1 && var2) +The original code has: if(!B&&e)return null +Where B = process.platform==="win32" (true on Windows) + +Original behavior: +- Windows: !true=false, condition fails → title bar SHOWS +- Linux: !false=true, condition true → returns null → title bar HIDDEN + +This patch changes: if(!B&&e) -> if(B&&e) +New behavior: +- Windows: true&&e=true → returns null → title bar hidden (uses native) +- Linux: false&&e=false → condition fails → title bar SHOWS + +This gives Linux the same internal title bar that Windows has. Usage: python3 fix_title_bar.py """ @@ -299,9 +546,14 @@ import re def patch_title_bar(filepath): - """Patch the title bar detection logic.""" + """Patch the title bar detection to show on Linux.""" - print(f"Patching title bar in: {filepath}") + print(f"=== Patch: fix_title_bar ===") + print(f" Target: {filepath}") + + if not os.path.exists(filepath): + print(f" [FAIL] File not found: {filepath}") + return False with open(filepath, 'rb') as f: content = f.read() @@ -309,21 +561,26 @@ def patch_title_bar(filepath): original_content = content # Fix: if(!B&&e) -> if(B&&e) - removes the negation - # The title bar check has a negated condition that fails on Linux - # Pattern: if(!X&&Y) where X and Y are variable names (minified, no spaces) pattern = rb'if\(!([a-zA-Z_][a-zA-Z0-9_]*)\s*&&\s*([a-zA-Z_][a-zA-Z0-9_]*)\)' replacement = rb'if(\1&&\2)' content, count = re.subn(pattern, replacement, content) + if count >= 1: + print(f" [OK] title bar condition: {count} match(es)") + else: + print(f" [FAIL] title bar condition: 0 matches, expected >= 1") + return False + + # Write back if changed if content != original_content: with open(filepath, 'wb') as f: f.write(content) - print(f"Title bar patch applied: {count} replacement(s)") + print(" [PASS] Title bar patched successfully") return True else: - print("Warning: No title bar patterns found to patch") - return False + print(" [WARN] No changes made (patterns may have already been applied)") + return True if __name__ == "__main__": @@ -331,17 +588,259 @@ if __name__ == "__main__": print(f"Usage: {sys.argv[0]} ") sys.exit(1) - filepath = sys.argv[1] + success = patch_title_bar(sys.argv[1]) + sys.exit(0 if success else 1) +fix_title_bar_py_EOF + then + echo "ERROR: Patch fix_title_bar.py FAILED - patterns did not match" + echo "Please check if upstream changed the target file structure" + exit 1 + fi + else + echo "ERROR: Target not found for pattern: app.asar.contents/.vite/renderer/main_window/assets/MainWindowPage-*.js" + exit 1 + fi + + # Applying patch: fix_tray_dbus.py + echo "Applying patch: fix_tray_dbus.py..." + if ! python3 - "app.asar.contents/.vite/build/index.js" << 'fix_tray_dbus_py_EOF' +#!/usr/bin/env python3 +# @patch-target: app.asar.contents/.vite/build/index.js +# @patch-type: python +""" +Patch Claude Desktop tray menu handler to prevent DBus race conditions. + +The tray icon setup can be called multiple times concurrently, causing DBus +"already exported" errors. This patch: +1. Makes the tray function async +2. Adds a mutex guard to prevent concurrent calls +3. Adds a delay after Tray.destroy() to allow DBus cleanup + +Based on: https://github.com/aaddrick/claude-desktop-debian/blob/main/build.sh + +Usage: python3 fix_tray_dbus.py +""" + +import sys +import os +import re + + +def patch_tray_dbus(filepath): + """Patch the tray menu handler to prevent DBus race conditions.""" + + print(f"=== Patch: fix_tray_dbus ===") + print(f" Target: {filepath}") + if not os.path.exists(filepath): - print(f"Error: File not found: {filepath}") + print(f" [FAIL] File not found: {filepath}") + return False + + with open(filepath, 'rb') as f: + content = f.read() + + original_content = content + failed = False + + # Step 1: Find the tray function name from menuBarEnabled listener + match = re.search(rb'on\("menuBarEnabled",\(\)=>\{(\w+)\(\)\}\)', content) + if not match: + print(" [FAIL] menuBarEnabled listener: 0 matches, expected >= 1") + failed = True + tray_func = None + else: + tray_func = match.group(1) + print(f" [OK] menuBarEnabled listener: found tray function '{tray_func.decode()}'") + + # Step 2: Find tray variable name + tray_var = None + if tray_func: + pattern = rb'\}\);let (\w+)=null;(?:async )?function ' + tray_func + match = re.search(pattern, content) + if not match: + print(" [FAIL] tray variable: 0 matches, expected >= 1") + failed = True + else: + tray_var = match.group(1) + print(f" [OK] tray variable: found '{tray_var.decode()}'") + + # Step 3: Make the function async (if not already) + if tray_func: + old_func = b'function ' + tray_func + b'(){' + new_func = b'async function ' + tray_func + b'(){' + if old_func in content and b'async function ' + tray_func not in content: + content = content.replace(old_func, new_func) + print(f" [OK] async conversion: made {tray_func.decode()}() async") + elif b'async function ' + tray_func in content: + print(f" [INFO] async conversion: already async") + else: + print(f" [FAIL] async conversion: function pattern not found") + failed = True + + # Step 4: Find first const variable in the function + first_const = None + if tray_func: + pattern = rb'async function ' + tray_func + rb'\(\)\{(?:if\(' + tray_func + rb'\._running\)[^}]*?)?const (\w+)=' + match = re.search(pattern, content) + if not match: + print(" [FAIL] first const in function: 0 matches") + failed = True + else: + first_const = match.group(1) + print(f" [OK] first const in function: found '{first_const.decode()}'") + + # Step 5: Add mutex guard (if not already present) + if tray_func and first_const: + mutex_check = tray_func + b'._running' + if mutex_check not in content: + old_start = b'async function ' + tray_func + b'(){const ' + first_const + b'=' + mutex_code = ( + b'async function ' + tray_func + b'(){if(' + tray_func + b'._running)return;' + + tray_func + b'._running=true;setTimeout(()=>' + tray_func + b'._running=false,500);const ' + + first_const + b'=' + ) + if old_start in content: + content = content.replace(old_start, mutex_code) + print(f" [OK] mutex guard: added") + else: + print(f" [FAIL] mutex guard: insertion point not found") + failed = True + else: + print(f" [INFO] mutex guard: already present") + + # Step 6: Add delay after Tray.destroy() for DBus cleanup + if tray_var: + old_destroy = tray_var + b'&&(' + tray_var + b'.destroy(),' + tray_var + b'=null)' + new_destroy = tray_var + b'&&(' + tray_var + b'.destroy(),' + tray_var + b'=null,await new Promise(r=>setTimeout(r,50)))' + + if old_destroy in content and b'await new Promise' not in content: + content = content.replace(old_destroy, new_destroy) + print(f" [OK] DBus cleanup delay: added after {tray_var.decode()}.destroy()") + elif b'await new Promise' in content: + print(f" [INFO] DBus cleanup delay: already present") + else: + print(f" [FAIL] DBus cleanup delay: destroy pattern not found") + failed = True + + # Check results + if failed: + print(" [FAIL] Some required patterns did not match") + return False + + # Write back if changed + if content != original_content: + with open(filepath, 'wb') as f: + f.write(content) + print(" [PASS] All required patterns matched and applied") + return True + else: + print(" [WARN] No changes made (patterns may have already been applied)") + return True + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} ") sys.exit(1) - # Always exit 0 - patch is optional (some versions may not need it) - patch_title_bar(filepath) - sys.exit(0) -fix_title_bar_py_EOF - else - echo "Warning: Target not found for pattern: app.asar.contents/.vite/renderer/main_window/assets/MainWindowPage-*.js" + success = patch_tray_dbus(sys.argv[1]) + sys.exit(0 if success else 1) +fix_tray_dbus_py_EOF + then + echo "ERROR: Patch fix_tray_dbus.py FAILED - patterns did not match" + echo "Please check if upstream changed the target file structure" + exit 1 + fi + + # Applying patch: fix_tray_path.py + echo "Applying patch: fix_tray_path.py..." + if ! python3 - "app.asar.contents/.vite/build/index.js" << 'fix_tray_path_py_EOF' +#!/usr/bin/env python3 +# @patch-target: app.asar.contents/.vite/build/index.js +# @patch-type: python +""" +Patch Claude Desktop to fix tray icon path on Linux. + +On Linux, process.resourcesPath points to the Electron resources directory +(e.g., /usr/lib/electron/resources/) but our tray icons are installed to +/usr/lib/claude-desktop-bin/locales/. This patch redirects the tray icon +path lookup to use our package's directory. + +The wTe() function is used to get the resources path for tray icons. +We patch it to return our locales directory on Linux. + +Usage: python3 fix_tray_path.py +""" + +import sys +import os +import re + + +def patch_tray_path(filepath): + """Patch the tray icon resources path to use our package directory.""" + + print(f"=== Patch: fix_tray_path ===") + print(f" Target: {filepath}") + + if not os.path.exists(filepath): + print(f" [FAIL] File not found: {filepath}") + return False + + with open(filepath, 'rb') as f: + content = f.read() + + original_content = content + patches_applied = 0 + + # Pattern 1: Generic function pattern + # Pattern: function FUNCNAME(){return ce.app.isPackaged?pn.resourcesPath:...} + pattern1 = rb'(function \w+\(\)\{return ce\.app\.isPackaged\?)pn\.resourcesPath(:[^}]+\})' + replacement1 = rb'\1(pn.platform==="linux"?"/usr/lib/claude-desktop-bin/locales":pn.resourcesPath)\2' + + content, count1 = re.subn(pattern1, replacement1, content) + if count1 > 0: + patches_applied += count1 + print(f" [OK] generic resources path function: {count1} match(es)") + + # Pattern 2: Specific wTe function pattern (alternative) + if patches_applied == 0: + pattern2 = rb'function wTe\(\)\{return ce\.app\.isPackaged\?pn\.resourcesPath:' + replacement2 = rb'function wTe(){return ce.app.isPackaged?(pn.platform==="linux"?"/usr/lib/claude-desktop-bin/locales":pn.resourcesPath):' + + content, count2 = re.subn(pattern2, replacement2, content) + if count2 > 0: + patches_applied += count2 + print(f" [OK] specific wTe function: {count2} match(es)") + + # Check results - at least one pattern must match + if patches_applied == 0: + print(f" [FAIL] No patterns matched (tried 2 alternatives), expected >= 1") + return False + + # Write back if changed + if content != original_content: + with open(filepath, 'wb') as f: + f.write(content) + print(" [PASS] Tray path patched successfully") + return True + else: + print(" [WARN] No changes made (patterns may have already been applied)") + return True + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + success = patch_tray_path(sys.argv[1]) + sys.exit(0 if success else 1) +fix_tray_path_py_EOF + then + echo "ERROR: Patch fix_tray_path.py FAILED - patterns did not match" + echo "Please check if upstream changed the target file structure" + exit 1 fi @@ -354,6 +853,10 @@ fix_title_bar_py_EOF # Copy locales mkdir -p "$srcdir/app/locales" cp "$srcdir/extract/lib/net45/resources/"*.json "$srcdir/app/locales/" 2>/dev/null || true + + # Copy tray icons (must be in filesystem, not inside asar, for Electron Tray API) + echo "Copying tray icon files..." + cp "$srcdir/extract/lib/net45/resources/TrayIconTemplate"*.png "$srcdir/app/locales/" 2>/dev/null || true } package() {