Update to version 1.0.1217

This commit is contained in:
patrickjaja 2025-11-25 10:46:38 +00:00
parent f9b4838cbf
commit 6445d7eb78

627
PKGBUILD
View file

@ -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]} <path_to_index.js>")
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]} <path_to_index.js>")
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 <path_to_index.js>
"""
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]} <path_to_index.js>")
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 <path_to_index.js>
"""
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]} <path_to_index.js>")
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 <path_to_MainWindowPage-*.js>
"""
@ -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]} <path_to_MainWindowPage-*.js>")
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 <path_to_index.js>
"""
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]} <path_to_index.js>")
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 <path_to_index.js>
"""
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]} <path_to_index.js>")
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() {