mirror of
https://github.com/archlinux/aur.git
synced 2026-03-14 23:16:48 +01:00
Update to version 1.0.1217
This commit is contained in:
parent
f9b4838cbf
commit
6445d7eb78
1 changed files with 565 additions and 62 deletions
627
PKGBUILD
627
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]} <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() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue