diff --git a/.gitignore b/.gitignore index 4687a6a..678b832 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ node_modules/ # OS-specific files .DS_Store Thumbs.db + +# VSCode workspace +*.code-workspace \ No newline at end of file diff --git a/README.md b/README.md index 9389035..f171a12 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,25 @@ - ***THIS IS AN UNOFFICIAL BUILD SCRIPT!*** If you run into an issue with this build script, make an issue here. Don't bug Anthropic about it - they already have enough on their plates. +# Claude Desktop for Fedora 43 + +This is a modified version of the Claude Desktop build script, fixed to work on **Fedora 43** (Wayland/KDE Plasma). + +**Fixes in this version:** +- **Fedora 43 Support**: Updated dependencies and build process +- **Google Sign-In Fixed**: Implemented a native module stub to allow Google authentication to work +- **Layout Fixes**: Fixed window scaling and maximizing glitches +- **Titlebar Gap Fixed (v8)**: Root cause fix - patches titlebar height constants from 36px to 0 +- **Auto-Update**: Script automatically downloads the latest Claude Desktop version + # Claude Desktop for Linux This project was inspired by [k3d3's claude-desktop-linux-flake](https://github.com/k3d3/claude-desktop-linux-flake) and their [Reddit post](https://www.reddit.com/r/ClaudeAI/comments/1hgsmpq/i_successfully_ran_claude_desktop_natively_on/) about running Claude Desktop natively on Linux. Their work provided valuable insights into the application's structure and the native bindings implementation. -And now by me, via [Aaddrick's claude-desktop-debian](https://github.com/aaddrick/claude-desktop-debian), modified to work for Fedora 41. +And now by me, via [Aaddrick's claude-desktop-debian](https://github.com/aaddrick/claude-desktop-debian), modified to work for Fedora 43. Supports MCP! ![image](https://github.com/user-attachments/assets/93080028-6f71-48bd-8e59-5149d148cd45) @@ -22,77 +32,60 @@ Supports the Ctrl+Alt+Space popup! Supports the Tray menu! (Screenshot of running on KDE) ![image](https://github.com/user-attachments/assets/ba209824-8afb-437c-a944-b53fd9ecd559) -# Installation Options +# Installation + +## Fedora Package (Updated for latest version!) + +For Fedora-based distributions you can build and install Claude Desktop using the provided build script. -## 1. Fedora Package (New!) +**What's New (v8):** -For Fedora-based distributions you can build and install Claude Desktop using the provided build script (updated): +- **Titlebar gap eliminated (ROOT CAUSE FIX)** - Patches height constants (`?0:36` -> `?0:0`) in the bundled JavaScript +- **Why v8 works**: Claude Desktop reserves 36px for titlebar via ternary patterns. Patching these to 0 eliminates the gap at source. +- **Auto-download latest version** - Script fetches the latest Claude Desktop automatically +- **Native title bar support** - No more double title bar issue +- **Fedora 43 Wayland/KDE compatibility** - Automatic environment configuration ```bash -sudo dnf install rpm-build +# Install build dependencies +sudo dnf install rpm-build p7zip nodejs npm + # Clone this repository -git clone https://github.com/bsneed/claude-desktop-fedora.git +git clone https://github.com/boujuan/claude-desktop-fedora.git cd claude-desktop-fedora -# Build the package +# Build the package (downloads Electron 37 automatically) +chmod +x build-fedora.sh sudo ./build-fedora.sh # Install the package -# Check what file was actually created -ls build/electron-app/$(uname -m)/ - -# Install using the correct filename (example - your version may differ) -sudo dnf install build/electron-app/$(uname -m)/claude-desktop-*.rpm - -# Download standalone Electron -# The installed Claude Desktop will have GTK conflicts with your system Electron. Download a clean Electron binary -cd /tmp -wget https://github.com/electron/electron/releases/download/v37.0.0/electron-v37.0.0-linux-x64.zip - -# Extract into /opt -sudo unzip electron-v37.0.0-linux-x64.zip -d /opt -sudo mv /opt/electron-v37.0.0-linux-x64 /opt/electron -sudo chmod +x /opt/electron/electron - -# Fix the Claude Desktop Script The default script will try to use your system Electron. Replace it with one that uses the standalone version: - -# Backup the original script -sudo cp /usr/bin/claude-desktop /usr/bin/claude-desktop.backup - -# Create the fixed script -sudo tee /usr/bin/claude-desktop << 'EOF' -#!/usr/bin/bash -LOG_FILE="$HOME/claude-desktop-launcher.log" - -# Set environment to avoid GTK conflicts -export GDK_BACKEND=x11 -export GTK_USE_PORTAL=0 -export ELECTRON_DISABLE_SECURITY_WARNINGS=true - -# Use the standalone electron installation -/opt/electron/electron /usr/lib64/claude-desktop/app.asar --ozone-platform-hint=auto --enable-logging=file --log-file=$LOG_FILE --log-level=INFO --disable-gpu-sandbox --no-sandbox "$@" -EOF - -# Make it executable -sudo chmod +x /usr/bin/claude-desktop +sudo dnf install $(uname -m)/claude-desktop-*.rpm # Launch Claude Desktop claude-desktop - ``` -Installation video here : https://youtu.be/dvU1yJsyJ5k +**That's it!** The launcher script includes all necessary fixes: +- Native KDE/GNOME title bar (no double title bar) +- GTK conflict prevention for Fedora 42+ +- Proper electron path for app drawer compatibility +- All necessary sandbox and logging flags + +Installation video here: https://youtu.be/dvU1yJsyJ5k -Requirements: -- Fedora 41 Linux distribution +**Requirements:** +- Fedora 41+ Linux distribution (tested on Fedora 43) - Node.js >= 12.0.0 and npm - Root/sudo access for dependency installation -## 2. Debian Package (New!) +**Known Issues:** +- If upgrading from an older build, you may need to uninstall the old package first: `sudo dnf remove claude-desktop` -For Debian users, please refer to [Aaddrick's claude-desktop-debian](https://github.com/aaddrick/claude-desktop-debian) repository. Their implementation is specifically designed for Debian and provides the original build script that inspired THIS project. +## Debian Package -## 3. NixOS Implementation +For Debian users, please refer to [Aaddrick's claude-desktop-debian](https://github.com/aaddrick/claude-desktop-debian) repository. Their implementation is specifically designed for Debian and provides the original build script that inspired THIS project. + +## NixOS Implementation For NixOS users, please refer to [k3d3's claude-desktop-linux-flake](https://github.com/k3d3/claude-desktop-linux-flake) repository. Their implementation is specifically designed for NixOS and provides the original Nix flake that inspired this project. @@ -103,7 +96,8 @@ Claude Desktop is an Electron application packaged as a Windows executable. Our 1. Downloads and extracts the Windows installer 2. Unpacks the app.asar archive containing the application code 3. Replaces the Windows-specific native module with a Linux-compatible implementation -4. Repackages everything into a proper RPM package +4. Applies titlebar and frame fixes for proper Linux desktop integration +5. Repackages everything into a proper RPM package The process works because Claude Desktop is largely cross-platform, with only one platform-specific component that needs replacement. @@ -132,11 +126,12 @@ The replacement module is carefully designed to match the original API while pro The build script (`build-fedora.sh`) handles the entire process: 1. Checks for a Fedora-based system and required dependencies -2. Downloads the official Windows installer +2. Downloads the official Windows installer (auto-fetches latest version) 3. Extracts the application resources 4. Processes icons for Linux desktop integration 5. Unpacks and modifies the app.asar: - Replaces the native module with our Linux version + - Applies titlebar fixes (height constant patching, native frame, CSS backup) - Updates keyboard key mappings - Preserves all other functionality 6. Creates a proper RPM package with: @@ -145,13 +140,20 @@ The build script (`build-fedora.sh`) handles the entire process: - Proper dependency management - Post-install configuration -## Updating the Build Script +## Updating + +The script automatically downloads the latest version of Claude Desktop from the official Anthropic servers using their redirect API. Simply re-run the build script to update: + +```bash +sudo ./build-fedora.sh +sudo dnf install $(uname -m)/claude-desktop-*.rpm +``` -When a new version of Claude Desktop is released, simply update the `CLAUDE_DOWNLOAD_URL` constant at the top of `build-deb.sh` to point to the new installer. The script will handle everything else automatically. +**Manual Version Override:** If you need to build a specific version (for testing or compatibility), you can edit the `CLAUDE_DOWNLOAD_URL` at the top of `build-fedora.sh` to point to a specific installer URL. # License -The build scripts in this repository, are dual-licensed under the terms of the MIT license and the Apache License (Version 2.0). +The build scripts in this repository are dual-licensed under the terms of the MIT license and the Apache License (Version 2.0). See [LICENSE-MIT](LICENSE-MIT) and [LICENSE-APACHE](LICENSE-APACHE) for details. @@ -162,4 +164,3 @@ The Claude Desktop application, not included in this repository, is likely cover Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -# claude-desktop-fedora diff --git a/build-fedora.sh b/build-fedora.sh index f7fd953..9e1805c 100755 --- a/build-fedora.sh +++ b/build-fedora.sh @@ -1,150 +1,108 @@ #!/bin/bash set -e -# Update this URL when a new version of Claude Desktop is released -CLAUDE_DOWNLOAD_URL="https://storage.googleapis.com/osprey-downloads-c02f6a0d-347c-492b-a752-3e0651722e97/nest-win-x64/Claude-Setup-x64.exe" +# ============================================================================= +# Claude Desktop for Fedora 43+ - Complete Build Script +# +# Fixes included: +# - Auto-download latest version from redirect URL +# - Layout scaling (main_window.tgz patch) +# - Native window frame (no custom titlebar issues) +# - Menu bar FULLY removed +# - Window maximize/resize fix (forced relayout) +# - Google Sign-In (native module stub) +# - Origin validation bypass (IPC security fix for file:// URLs) +# - GPU/Wayland compatibility (software rendering fallback) +# - Titlebar gap fix via height constant patching (v8) +# +# v8 Changelog: +# - ROOT CAUSE FIX: Patch titlebar height constants from 36px to 0 +# - The app reserves 36px via ?0:36 ternary patterns (e.g., oR=hn?0:36) +# - sed patches these constants at source, eliminating gap permanently +# - Simplified CSS backup with negative margin approach +# - Removed complex DOM manipulation (no longer needed) +# ============================================================================= + +# Use redirect URL for always-latest version +CLAUDE_DOWNLOAD_URL="https://claude.ai/api/desktop/win32/x64/exe/latest/redirect" +ELECTRON_VERSION=${ELECTRON_VERSION:-"37.0.0"} +ELECTRON_DOWNLOAD_URL=${ELECTRON_DOWNLOAD_URL:-"https://github.com/electron/electron/releases/download/v${ELECTRON_VERSION}/electron-v${ELECTRON_VERSION}-linux-x64.zip"} +MAIN_WINDOW_FIX_URL="https://github.com/emsi/claude-desktop/raw/refs/heads/main/assets/main_window.tgz" -# Inclusive check for Fedora-based system is_fedora_based() { - if [ -f "/etc/fedora-release" ]; then - return 0 - fi - - if [ -f "/etc/os-release" ]; then - grep -qi "fedora" /etc/os-release && return 0 - fi - - # Not a Fedora-based system + [ -f "/etc/fedora-release" ] && return 0 + [ -f "/etc/os-release" ] && grep -qi "fedora" /etc/os-release && return 0 return 1 } if ! is_fedora_based; then - echo "❌ This script requires a Fedora-based Linux distribution" + echo "This script requires a Fedora-based Linux distribution" exit 1 fi -# Check for root/sudo -IS_SUDO=false -if [ "$EUID" -eq 0 ]; then - IS_SUDO=true - # Check if running via sudo (and not directly as root) - if [ -n "$SUDO_USER" ]; then - ORIGINAL_USER="$SUDO_USER" - ORIGINAL_HOME=$(eval echo ~$ORIGINAL_USER) - else - # Running directly as root, no original user context - ORIGINAL_USER="root" - ORIGINAL_HOME="/root" - fi -else - echo "Please run with sudo to install dependencies" +if [ "$EUID" -ne 0 ]; then + echo "Please run with sudo" exit 1 fi -# Preserve NVM path if running under sudo and NVM exists for the original user -if [ "$IS_SUDO" = true ] && [ "$ORIGINAL_USER" != "root" ] && [ -d "$ORIGINAL_HOME/.nvm" ]; then - echo "Found NVM installation for user $ORIGINAL_USER, attempting to preserve npm/npx path..." - # Source NVM script to set up NVM environment variables temporarily - export NVM_DIR="$ORIGINAL_HOME/.nvm" - [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm - - # Find the path to the currently active or default Node version's bin directory - # nvm_find_node_version might not be available, try finding the latest installed version - NODE_BIN_PATH=$(find "$NVM_DIR/versions/node" -maxdepth 2 -type d -name 'bin' | sort -V | tail -n 1) +if [ -n "$SUDO_USER" ]; then + ORIGINAL_USER="$SUDO_USER" + ORIGINAL_HOME=$(eval echo ~$ORIGINAL_USER) +else + ORIGINAL_USER="root" + ORIGINAL_HOME="/root" +fi - if [ -n "$NODE_BIN_PATH" ] && [ -d "$NODE_BIN_PATH" ]; then - echo "Adding $NODE_BIN_PATH to PATH" - export PATH="$NODE_BIN_PATH:$PATH" - else - echo "Warning: Could not determine NVM Node bin path. npm/npx might not be found." - fi +# Preserve NVM path +if [ "$ORIGINAL_USER" != "root" ] && [ -d "$ORIGINAL_HOME/.nvm" ]; then + export NVM_DIR="$ORIGINAL_HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + NODE_BIN_PATH=$(find "$NVM_DIR/versions/node" -maxdepth 2 -type d -name 'bin' 2>/dev/null | sort -V | tail -n 1) + [ -n "$NODE_BIN_PATH" ] && [ -d "$NODE_BIN_PATH" ] && export PATH="$NODE_BIN_PATH:$PATH" fi -# Print system information -echo "System Information:" +echo "============================================================================" +echo "Claude Desktop for Fedora - Build Script v8" +echo "============================================================================" echo "Distribution: $(cat /etc/os-release | grep "PRETTY_NAME" | cut -d'"' -f2)" -echo "Fedora version: $(cat /etc/fedora-release)" +echo "============================================================================" -# Function to check if a command exists +# Check dependencies check_command() { - if ! command -v "$1" &> /dev/null; then - echo "❌ $1 not found" - return 1 - else - echo "✓ $1 found" - return 0 - fi + command -v "$1" &> /dev/null && echo "✓ $1" && return 0 + echo "✗ $1 not found" && return 1 } -# Check and install dependencies -echo "Checking dependencies..." DEPS_TO_INSTALL="" - -# Check system package dependencies -for cmd in sqlite3 7z wget wrestool icotool convert npx rpm rpmbuild; do +for cmd in sqlite3 7z wget curl unzip wrestool icotool convert npx rpmbuild file; do if ! check_command "$cmd"; then case "$cmd" in - "sqlite3") - DEPS_TO_INSTALL="$DEPS_TO_INSTALL sqlite3" - ;; - "7z") - DEPS_TO_INSTALL="$DEPS_TO_INSTALL p7zip-plugins" - ;; - "wget") - DEPS_TO_INSTALL="$DEPS_TO_INSTALL wget" - ;; - "wrestool"|"icotool") - DEPS_TO_INSTALL="$DEPS_TO_INSTALL icoutils" - ;; - "convert") - DEPS_TO_INSTALL="$DEPS_TO_INSTALL ImageMagick" - ;; - "npx") - DEPS_TO_INSTALL="$DEPS_TO_INSTALL nodejs npm" - ;; - "rpm") - DEPS_TO_INSTALL="$DEPS_TO_INSTALL rpm" - ;; - "rpmbuild") - DEPS_TO_INSTALL="$DEPS_TO_INSTALL rpmbuild" - ;; - "curl") - DEPS_TO_INSTALL="$DEPS_TO_INSTALL curl" + "sqlite3") DEPS_TO_INSTALL="$DEPS_TO_INSTALL sqlite3" ;; + "7z") DEPS_TO_INSTALL="$DEPS_TO_INSTALL p7zip-plugins" ;; + "wget") DEPS_TO_INSTALL="$DEPS_TO_INSTALL wget" ;; + "curl") DEPS_TO_INSTALL="$DEPS_TO_INSTALL curl" ;; + "unzip") DEPS_TO_INSTALL="$DEPS_TO_INSTALL unzip" ;; + "wrestool"|"icotool") DEPS_TO_INSTALL="$DEPS_TO_INSTALL icoutils" ;; + "convert") DEPS_TO_INSTALL="$DEPS_TO_INSTALL ImageMagick" ;; + "npx") DEPS_TO_INSTALL="$DEPS_TO_INSTALL nodejs npm" ;; + "rpmbuild") DEPS_TO_INSTALL="$DEPS_TO_INSTALL rpm-build" ;; + "file") DEPS_TO_INSTALL="$DEPS_TO_INSTALL file" ;; esac fi done -# Install system dependencies if any -if [ ! -z "$DEPS_TO_INSTALL" ]; then - echo "Installing system dependencies: $DEPS_TO_INSTALL" - dnf install -y $DEPS_TO_INSTALL - echo "System dependencies installed successfully" -fi - -# Install electron globally via npm if not present -if ! check_command "electron"; then - echo "Installing electron via npm..." - npm install -g electron - if ! check_command "electron"; then - echo "Failed to install electron. Please install it manually:" - echo "sudo npm install -g electron" - exit 1 - fi - echo "Electron installed successfully" -fi +[ -n "$DEPS_TO_INSTALL" ] && dnf install -y $DEPS_TO_INSTALL PACKAGE_NAME="claude-desktop" ARCHITECTURE=$(uname -m) DISTRIBUTION=$(rpm --eval %{?dist}) MAINTAINER="Claude Desktop Linux Maintainers" -DESCRIPTION="Claude Desktop for Linux" # Create working directories WORK_DIR="$(pwd)/build" FEDORA_ROOT="$WORK_DIR/fedora-package" INSTALL_DIR="$FEDORA_ROOT/usr" -# Clean previous build rm -rf "$WORK_DIR" mkdir -p "$WORK_DIR" mkdir -p "$FEDORA_ROOT/FEDORA" @@ -155,58 +113,64 @@ mkdir -p "$INSTALL_DIR/bin" # Install asar if needed if ! command -v asar > /dev/null 2>&1; then - echo "Installing asar package globally..." npm install -g asar fi -# Download Claude Windows installer -echo "📥 Downloading Claude Desktop installer..." +# Download Claude with Cloudflare bypass +echo "📥 Downloading Claude Desktop (latest version)..." CLAUDE_EXE="$WORK_DIR/Claude-Setup-x64.exe" -if ! curl -o "$CLAUDE_EXE" "$CLAUDE_DOWNLOAD_URL"; then - echo "❌ Failed to download Claude Desktop installer" +USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + +if ! curl --fail -L --retry 3 \ + -H "User-Agent: ${USER_AGENT}" \ + -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" \ + -H "Accept-Language: en-US,en;q=0.5" \ + -H "Sec-Fetch-Dest: document" \ + -H "Sec-Fetch-Mode: navigate" \ + --compressed \ + -o "$CLAUDE_EXE" \ + "$CLAUDE_DOWNLOAD_URL"; then + echo "Download failed" exit 1 fi -echo "✓ Download complete" -# Extract resources -echo "📦 Extracting resources..." -cd "$WORK_DIR" -if ! 7z x -y "$CLAUDE_EXE"; then - echo "❌ Failed to extract installer" +# Verify we downloaded an actual executable, not an error page +if ! file "$CLAUDE_EXE" | grep -q "PE32\|executable"; then + echo "❌ Downloaded file is not a valid Windows executable" + echo " File type: $(file "$CLAUDE_EXE")" + echo " This usually means Cloudflare blocked the download." + echo "" + echo " Workaround: Manually download the installer from https://claude.ai/download" + echo " Then update CLAUDE_DOWNLOAD_URL in this script to point to your local file." exit 1 fi -# Find the nupkg file +echo "✓ Download complete" + +# Extract +echo "📦 Extracting..." +cd "$WORK_DIR" +7z x -y "$CLAUDE_EXE" || { echo "Extract failed"; exit 1; } + NUPKG_FILE=$(find . -name "AnthropicClaude-*-full.nupkg" | head -1) -if [ -z "$NUPKG_FILE" ]; then - echo "❌ Could not find AnthropicClaude nupkg file" - exit 1 -fi +[ -z "$NUPKG_FILE" ] && { echo "nupkg not found"; exit 1; } -# Extract the version from the nupkg filename VERSION=$(echo "$NUPKG_FILE" | grep -oP 'AnthropicClaude-\K[0-9]+\.[0-9]+\.[0-9]+(?=-full\.nupkg)') echo "📋 Detected Claude version: $VERSION" -if ! 7z x -y "$NUPKG_FILE"; then - echo "❌ Failed to extract nupkg" - exit 1 -fi -echo "✓ Resources extracted" +7z x -y "$NUPKG_FILE" || { echo "nupkg extract failed"; exit 1; } -# Extract and convert icons -echo "🎨 Processing icons..." -if ! wrestool -x -t 14 "lib/net45/claude.exe" -o claude.ico; then - echo "❌ Failed to extract icons from exe" - exit 1 -fi +# Download Electron +echo "📥 Downloading Electron v${ELECTRON_VERSION}..." +curl -L -o "$WORK_DIR/electron.zip" "$ELECTRON_DOWNLOAD_URL" || { echo "Electron download failed"; exit 1; } +unzip -q "$WORK_DIR/electron.zip" -d "$WORK_DIR/electron-dist" +echo "✓ Electron ready" -if ! icotool -x claude.ico; then - echo "❌ Failed to convert icons" - exit 1 -fi -echo "✓ Icons processed" +# Extract icons +echo "🎨 Processing icons..." +wrestool -x -t 14 "lib/net45/claude.exe" -o claude.ico || { echo "Icon extract failed"; exit 1; } +icotool -x claude.ico || { echo "Icon convert failed"; exit 1; } -# Map icon sizes to their corresponding extracted files declare -A icon_files=( ["16"]="claude_13_16x16x32.png" ["24"]="claude_11_24x24x32.png" @@ -216,16 +180,10 @@ declare -A icon_files=( ["256"]="claude_6_256x256x32.png" ) -# Install icons for size in 16 24 32 48 64 256; do icon_dir="$INSTALL_DIR/share/icons/hicolor/${size}x${size}/apps" mkdir -p "$icon_dir" - if [ -f "${icon_files[$size]}" ]; then - echo "Installing ${size}x${size} icon..." - install -Dm 644 "${icon_files[$size]}" "$icon_dir/claude-desktop.png" - else - echo "Warning: Missing ${size}x${size} icon" - fi + [ -f "${icon_files[$size]}" ] && install -Dm 644 "${icon_files[$size]}" "$icon_dir/claude-desktop.png" done # Process app.asar @@ -236,38 +194,211 @@ cp -r "lib/net45/resources/app.asar.unpacked" electron-app/ cd electron-app npx asar extract app.asar app.asar.contents || { echo "asar extract failed"; exit 1; } -echo "Attempting to set frame:true and remove titleBarStyle/titleBarOverlay in index.js..." +# ============================================================================= +# FIX 1: Apply main_window patch (optional layout fix) +# ============================================================================= +echo "🔧 Applying main_window patch..." +wget -O- "$MAIN_WINDOW_FIX_URL" 2>/dev/null | tar -zxvf - -C app.asar.contents/ 2>/dev/null || echo "⚠️ main_window.tgz patch skipped" + +# ============================================================================= +# FIX 2: Stable titleBarStyle sed (Electron API - reliable across versions) +# ============================================================================= +echo "🔧 Applying stable titleBarStyle fix..." +TARGET_FILE="app.asar.contents/.vite/build/index.js" + +if [ -f "$TARGET_FILE" ]; then + if grep -qF 'titleBarStyle:"hidden"' "$TARGET_FILE"; then + sed -i 's/titleBarStyle:"hidden"/titleBarStyle:"default"/g' "$TARGET_FILE" + echo "✓ Native title bar enabled (titleBarStyle:default)" + else + echo "⚠ titleBarStyle pattern not found (may be different in this version)" + fi +fi -sed -i 's/height:e\.height,titleBarStyle:"default",titleBarOverlay:[^,]\+,/height:e.height,frame:true,/g' app.asar.contents/.vite/build/index.js || echo "Warning: sed command failed to modify index.js" +# Apply to all JS files for broader coverage +find app.asar.contents -name "*.js" -type f 2>/dev/null | while read -r jsfile; do + sed -i 's/frame:false/frame:true/g' "$jsfile" 2>/dev/null || true + sed -i 's/frame:!1/frame:!0/g' "$jsfile" 2>/dev/null || true + sed -i 's/titleBarStyle:"hidden",//g' "$jsfile" 2>/dev/null || true + sed -i 's/,titleBarStyle:"hidden"//g' "$jsfile" 2>/dev/null || true + sed -i 's/titleBarStyle:"hiddenInset",//g' "$jsfile" 2>/dev/null || true + sed -i 's/titleBarOverlay:[^,}]*,//g' "$jsfile" 2>/dev/null || true + sed -i 's/autoHideMenuBar:false/autoHideMenuBar:true/g' "$jsfile" 2>/dev/null || true + sed -i 's/autoHideMenuBar:!1/autoHideMenuBar:!0/g' "$jsfile" 2>/dev/null || true +done -# Replace native module with stub implementation -echo "Creating stub native module..." -cat > app.asar.contents/node_modules/claude-native/index.js << EOF -// Stub implementation of claude-native using KeyboardKey enum values +# ============================================================================= +# FIX 2.5: Patch titlebar height constants (v8 - ROOT CAUSE FIX) +# The app reserves 36px for titlebar via ternary patterns like: +# - Main process: oR=hn?0:36 (36px on Linux, 0 on macOS) +# - Renderer: k1=Yu?0:36, C1=Yu?28:36 +# Patching ?0:36 to ?0:0 eliminates the space reservation entirely. +# ============================================================================= +echo "🔧 Patching titlebar height constants (v8 root cause fix)..." +PATCHED_COUNT=0 +find app.asar.contents -name "*.js" -type f 2>/dev/null | while read -r jsfile; do + # Pattern 1: ?0:36 -> ?0:0 (main titlebar height on Linux) + if grep -q '?0:36' "$jsfile" 2>/dev/null; then + sed -i 's/?0:36/?0:0/g' "$jsfile" 2>/dev/null && echo " Patched ?0:36 in $(basename "$jsfile")" + fi + # Pattern 2: ?28:36 -> ?28:0 (secondary height pattern) + if grep -q '?28:36' "$jsfile" 2>/dev/null; then + sed -i 's/?28:36/?28:0/g' "$jsfile" 2>/dev/null && echo " Patched ?28:36 in $(basename "$jsfile")" + fi +done +echo "✓ Titlebar height constants patched to 0" + +# ============================================================================= +# FIX 3: Inject Linux fixes (v8 - simplified CSS + minimal JS) +# ============================================================================= +echo "🔧 Injecting Linux-specific fixes (v8 - simplified with height constants already patched)..." +MAIN_ENTRY=$(grep -o '"main"[[:space:]]*:[[:space:]]*"[^"]*"' app.asar.contents/package.json 2>/dev/null | sed 's/"main"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' || true) +if [ -n "$MAIN_ENTRY" ] && [ -f "app.asar.contents/$MAIN_ENTRY" ]; then + TEMP_FILE=$(mktemp) + cat > "$TEMP_FILE" << 'INJECT_EOF' +// ============================================================================= +// Linux Fixes - Injected by build script v8 +// Strategy: Height constants patched to 0 at source + CSS backup + menu removal +// ============================================================================= +(function() { + if (process.platform !== 'linux') return; + + const { app, Menu, BrowserWindow } = require('electron'); + + // Remove application menu + const removeMenu = () => { + try { Menu.setApplicationMenu(null); } catch(e) {} + }; + removeMenu(); + app.on('ready', removeMenu); + + app.on('browser-window-created', (e, win) => { + removeMenu(); + win.removeMenu(); + win.setMenu(null); + win.setMenuBarVisibility(false); + win.setAutoHideMenuBar(false); + + // Force relayout on window state changes (backup for edge cases) + const forceRelayout = () => { + if (!win || win.isDestroyed()) return; + try { + const [width, height] = win.getSize(); + win.setSize(width, height + 1); + setTimeout(() => { + if (win && !win.isDestroyed()) win.setSize(width, height); + }, 50); + } catch(e) {} + }; + + win.on('maximize', () => setTimeout(forceRelayout, 100)); + win.on('unmaximize', () => setTimeout(forceRelayout, 100)); + win.on('enter-full-screen', () => setTimeout(forceRelayout, 100)); + win.on('leave-full-screen', () => setTimeout(forceRelayout, 100)); + + win.webContents.on('did-finish-load', () => { + // v8: Simplified CSS - main fix is height constants patched to 0 + // This CSS is backup in case any residual gap remains + win.webContents.insertCSS(` + /* v8: Reset any residual spacing */ + html, body { + margin: 0 !important; + padding: 0 !important; + overflow: hidden !important; + } + /* v8: Pull content up by 36px to cover any residual gap */ + body > div:first-child { + margin-top: -36px !important; + padding-top: 0 !important; + height: calc(100% + 36px) !important; + } + /* v8: Alternative - collapse first row if grid layout */ + body > div:first-child { + grid-template-rows: 0 1fr !important; + } + body > div:first-child > div:first-child { + display: none !important; + height: 0 !important; + } + /* v8: Hide drag-region elements */ + [style*="-webkit-app-region: drag"]:not(body):not(html), + [style*="-webkit-app-region:drag"]:not(body):not(html), + .nc-drag:not(body):not(html) { + display: none !important; + height: 0 !important; + } + `).catch(() => {}); + + // v8: Minimal JS - just log for debugging, main fix is in constants + win.webContents.executeJavaScript(` + console.log('[LinuxFix v8] Height constants patched to 0, CSS backup applied'); + `).catch(() => {}); + }); + }); + + // Intercept menu creation + const originalSetMenu = Menu.setApplicationMenu; + Menu.setApplicationMenu = function(menu) { + return originalSetMenu.call(this, null); + }; +})(); +// ============================================================================= +INJECT_EOF + cat "app.asar.contents/$MAIN_ENTRY" >> "$TEMP_FILE" + mv "$TEMP_FILE" "app.asar.contents/$MAIN_ENTRY" + echo "✓ Linux fixes injected (v8)" +fi + +# ============================================================================= +# FIX 4: CSS file patching (minimal backup - main fix is height constants) +# ============================================================================= +echo "🔧 Patching CSS files (v8 backup)..." +# Add backup CSS with negative margin approach +find app.asar.contents -name "*.css" -type f 2>/dev/null | head -3 | while read -r cssfile; do + TEMP_CSS=$(mktemp) + cat > "$TEMP_CSS" << 'CSSEOF' +/* Linux v8: Backup CSS - main fix is height constants patched to 0 */ +body>div:first-child{margin-top:-36px!important;height:calc(100% + 36px)!important;} +body>div:first-child>div:first-child{display:none!important;height:0!important;} +.nc-drag:not(body){display:none!important;height:0!important;} +CSSEOF + cat "$cssfile" >> "$TEMP_CSS" + mv "$TEMP_CSS" "$cssfile" +done +echo "✓ CSS patched (v8)" + +# ============================================================================= +# FIX 5: Origin validation bypass for file:// URLs +# ============================================================================= +echo "🔧 Patching origin validation..." +find app.asar.contents -name "*.js" -type f 2>/dev/null | while read -r jsfile; do + if grep -q "startsWith" "$jsfile" 2>/dev/null; then + sed -i 's/\.startsWith("https:\/\/claude\.ai")/.startsWith("https:\/\/claude.ai")||e.startsWith("file:\/\/")/g' "$jsfile" 2>/dev/null || true + sed -i 's/\.startsWith("https:\/\/")/.startsWith("https:\/\/")||e.startsWith("file:\/\/")/g' "$jsfile" 2>/dev/null || true + fi + sed -i 's/\["https:"\]/["https:","file:"]/g' "$jsfile" 2>/dev/null || true + sed -i "s/\['https:'\]/['https:','file:']/g" "$jsfile" 2>/dev/null || true +done + +MAIN_BUILD_JS=$(find app.asar.contents -path "*/.vite/build/*.js" -name "*.js" | head -1) +if [ -n "$MAIN_BUILD_JS" ] && [ -f "$MAIN_BUILD_JS" ]; then + sed -i 's/throw new Error(`Incoming/console.warn(`[Linux] Incoming/g' "$MAIN_BUILD_JS" 2>/dev/null || true +fi +echo "✓ Origin validation patched" + +# ============================================================================= +# FIX 6: Native module stub for Google Sign-In +# ============================================================================= +echo "🔧 Creating native module stub..." +mkdir -p app.asar.contents/node_modules/claude-native +cat > app.asar.contents/node_modules/claude-native/index.js << 'EOF' const KeyboardKey = { - Backspace: 43, - Tab: 280, - Enter: 261, - Shift: 272, - Control: 61, - Alt: 40, - CapsLock: 56, - Escape: 85, - Space: 276, - PageUp: 251, - PageDown: 250, - End: 83, - Home: 154, - LeftArrow: 175, - UpArrow: 282, - RightArrow: 262, - DownArrow: 81, - Delete: 79, - Meta: 187 + Backspace: 43, Tab: 280, Enter: 261, Shift: 272, Control: 61, Alt: 40, + CapsLock: 56, Escape: 85, Space: 276, PageUp: 251, PageDown: 250, + End: 83, Home: 154, LeftArrow: 175, UpArrow: 282, RightArrow: 262, + DownArrow: 81, Delete: 79, Meta: 187 }; - Object.freeze(KeyboardKey); - module.exports = { getWindowsVersion: () => "10.0.0", setWindowEffect: () => {}, @@ -286,47 +417,25 @@ EOF # Copy Tray icons mkdir -p app.asar.contents/resources -cp ../lib/net45/resources/Tray* app.asar.contents/resources/ +cp ../lib/net45/resources/Tray* app.asar.contents/resources/ 2>/dev/null || true -# Repackage app.asar +# Copy i18n mkdir -p app.asar.contents/resources/i18n/ -cp ../lib/net45/resources/*.json app.asar.contents/resources/i18n/ - -echo "Downloading Main Window Fix Assets" -cd app.asar.contents -wget -O- https://github.com/emsi/claude-desktop/raw/refs/heads/main/assets/main_window.tgz | tar -zxvf - -cd .. +cp ../lib/net45/resources/*.json app.asar.contents/resources/i18n/ 2>/dev/null || true +# Repackage app.asar npx asar pack app.asar.contents app.asar || { echo "asar pack failed"; exit 1; } -# Create native module with keyboard constants +# Create native module in unpacked mkdir -p "$INSTALL_DIR/lib/$PACKAGE_NAME/app.asar.unpacked/node_modules/claude-native" -cat > "$INSTALL_DIR/lib/$PACKAGE_NAME/app.asar.unpacked/node_modules/claude-native/index.js" << EOF -// Stub implementation of claude-native using KeyboardKey enum values +cat > "$INSTALL_DIR/lib/$PACKAGE_NAME/app.asar.unpacked/node_modules/claude-native/index.js" << 'EOF' const KeyboardKey = { - Backspace: 43, - Tab: 280, - Enter: 261, - Shift: 272, - Control: 61, - Alt: 40, - CapsLock: 56, - Escape: 85, - Space: 276, - PageUp: 251, - PageDown: 250, - End: 83, - Home: 154, - LeftArrow: 175, - UpArrow: 282, - RightArrow: 262, - DownArrow: 81, - Delete: 79, - Meta: 187 + Backspace: 43, Tab: 280, Enter: 261, Shift: 272, Control: 61, Alt: 40, + CapsLock: 56, Escape: 85, Space: 276, PageUp: 251, PageDown: 250, + End: 83, Home: 154, LeftArrow: 175, UpArrow: 282, RightArrow: 262, + DownArrow: 81, Delete: 79, Meta: 187 }; - Object.freeze(KeyboardKey); - module.exports = { getWindowsVersion: () => "10.0.0", setWindowEffect: () => {}, @@ -347,6 +456,9 @@ EOF cp app.asar "$INSTALL_DIR/lib/$PACKAGE_NAME/" cp -r app.asar.unpacked "$INSTALL_DIR/lib/$PACKAGE_NAME/" +# Copy Electron +cp -r "$WORK_DIR/electron-dist"/* "$INSTALL_DIR/lib/$PACKAGE_NAME/" + # Create desktop entry cat > "$INSTALL_DIR/share/applications/claude-desktop.desktop" << EOF [Desktop Entry] @@ -360,12 +472,37 @@ MimeType=x-scheme-handler/claude; StartupWMClass=Claude EOF -# Create launcher script with Wayland flags and logging -cat > "$INSTALL_DIR/bin/claude-desktop" << EOF +# ============================================================================= +# LAUNCHER with Wayland/KDE compatibility +# ============================================================================= +cat > "$INSTALL_DIR/bin/claude-desktop" << 'LAUNCHER_EOF' #!/bin/bash -LOG_FILE="\$HOME/claude-desktop-launcher.log" -electron /usr/lib64/claude-desktop/app.asar --ozone-platform-hint=auto --enable-logging=file --log-file=\$LOG_FILE --log-level=INFO "\$@" -EOF +LOG_FILE="$HOME/.claude-desktop.log" + +# Environment for Wayland/KDE compatibility +export GDK_BACKEND=x11 +export GTK_USE_PORTAL=0 +export QT_QPA_PLATFORM=xcb +export ELECTRON_DISABLE_SECURITY_WARNINGS=true + +# Detect session type +SESSION_TYPE="${XDG_SESSION_TYPE:-x11}" + +# Find electron +ELECTRON_BIN="/usr/lib64/claude-desktop/electron" +APP_PATH="/usr/lib64/claude-desktop/app.asar" + +# Flags for stability +FLAGS=( + "--no-sandbox" + "--ozone-platform=x11" + "--disable-gpu-sandbox" +) + +echo "[$(date)] Starting Claude Desktop v8 (session: $SESSION_TYPE)" >> "$LOG_FILE" + +exec "$ELECTRON_BIN" "$APP_PATH" "${FLAGS[@]}" "$@" 2>> "$LOG_FILE" +LAUNCHER_EOF chmod +x "$INSTALL_DIR/bin/claude-desktop" # Create RPM spec file @@ -373,15 +510,15 @@ cat > "$WORK_DIR/claude-desktop.spec" << EOF Name: claude-desktop Version: ${VERSION} Release: 1%{?dist} -Summary: Claude Desktop for Linux +Summary: Claude Desktop for Linux (Fedora 43+) License: Proprietary URL: https://www.anthropic.com BuildArch: ${ARCHITECTURE} -Requires: nodejs >= 12.0.0, npm, p7zip +Requires: nodejs >= 12.0.0 %description -Claude is an AI assistant from Anthropic. -This package provides the desktop interface for Claude. +Claude AI assistant desktop application. +Built with v8 titlebar height constant patch for KDE/Wayland compatibility. %install mkdir -p %{buildroot}/usr/lib64/%{name} @@ -389,7 +526,6 @@ mkdir -p %{buildroot}/usr/bin mkdir -p %{buildroot}/usr/share/applications mkdir -p %{buildroot}/usr/share/icons -# Copy files from the INSTALL_DIR cp -r ${INSTALL_DIR}/lib/%{name}/* %{buildroot}/usr/lib64/%{name}/ cp -r ${INSTALL_DIR}/bin/* %{buildroot}/usr/bin/ cp -r ${INSTALL_DIR}/share/applications/* %{buildroot}/usr/share/applications/ @@ -402,52 +538,18 @@ cp -r ${INSTALL_DIR}/share/icons/* %{buildroot}/usr/share/icons/ %{_datadir}/icons/hicolor/*/apps/claude-desktop.png %post -# Update icon caches gtk-update-icon-cache -f -t %{_datadir}/icons/hicolor || : -# Force icon theme cache rebuild -touch -h %{_datadir}/icons/hicolor >/dev/null 2>&1 || : update-desktop-database %{_datadir}/applications || : -# Set correct permissions for chrome-sandbox -echo "Setting chrome-sandbox permissions..." -SANDBOX_PATH="" -# Check for sandbox in locally packaged electron first -if [ -f "/usr/lib64/claude-desktop/app.asar.unpacked/node_modules/electron/dist/chrome-sandbox" ]; then - SANDBOX_PATH="/usr/lib64/claude-desktop/app.asar.unpacked/node_modules/electron/dist/chrome-sandbox" - -elif [ -n "$SUDO_USER" ]; then - # Running via sudo: try to get electron from the invoking user's environment - if su - "$SUDO_USER" -c "command -v electron >/dev/null 2>&1"; then - ELECTRON_PATH=$(su - "$SUDO_USER" -c "command -v electron") - - POTENTIAL_SANDBOX="\$(dirname "\$(dirname "\$ELECTRON_PATH")")/lib/node_modules/electron/dist/chrome-sandbox" - if [ -f "\$POTENTIAL_SANDBOX" ]; then - SANDBOX_PATH="\$POTENTIAL_SANDBOX" - fi - fi -else - # Running directly as root (no SUDO_USER); attempt to find electron in root's PATH - if command -v electron >/dev/null 2>&1; then - ELECTRON_PATH=$(command -v electron) - POTENTIAL_SANDBOX="\$(dirname "\$(dirname "\$ELECTRON_PATH")")/lib/node_modules/electron/dist/chrome-sandbox" - if [ -f "\$POTENTIAL_SANDBOX" ]; then - SANDBOX_PATH="\$POTENTIAL_SANDBOX" - fi - fi -fi - -if [ -n "\$SANDBOX_PATH" ] && [ -f "\$SANDBOX_PATH" ]; then - echo "Found chrome-sandbox at: \$SANDBOX_PATH" - chown root:root "\$SANDBOX_PATH" || echo "Warning: Failed to chown chrome-sandbox" - chmod 4755 "\$SANDBOX_PATH" || echo "Warning: Failed to chmod chrome-sandbox" - echo "Permissions set for \$SANDBOX_PATH" -else - echo "Warning: chrome-sandbox binary not found. Sandbox may not function correctly." +# Set sandbox permissions +if [ -f "/usr/lib64/claude-desktop/chrome-sandbox" ]; then + chown root:root "/usr/lib64/claude-desktop/chrome-sandbox" || : + chmod 4755 "/usr/lib64/claude-desktop/chrome-sandbox" || : fi %changelog * $(date '+%a %b %d %Y') ${MAINTAINER} ${VERSION}-1 -- Initial package +- v8: Titlebar height constant patch (root cause fix) for Fedora 43 EOF # Build RPM package @@ -459,9 +561,12 @@ if rpmbuild -bb \ --define "_topdir ${WORK_DIR}" \ --define "_rpmdir $(pwd)" \ "${WORK_DIR}/claude-desktop.spec"; then - echo "✓ RPM package built successfully at: $RPM_FILE" - echo "🎉 Done! You can now install the RPM with: dnf install $RPM_FILE" + echo "============================================================================" + echo "✓ RPM package built successfully!" + echo "============================================================================" + echo "Install with: sudo dnf install $RPM_FILE" + echo "============================================================================" else - echo "❌ Failed to build RPM package" + echo "RPM build failed" exit 1 fi