diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..a860f13 --- /dev/null +++ b/build.bat @@ -0,0 +1,110 @@ +@echo off +setlocal EnableDelayedExpansion +cd /d "%~dp0" + +REM ───────────────────────────────────────────────────────────────────────────── +REM ScrcpyGUI — Windows build script +REM Requires: Node.js, npm, Rust (rustup), Tauri CLI (installed via npm) +REM Usage: double-click this file, or run it from any terminal. +REM ───────────────────────────────────────────────────────────────────────────── + +REM ── 1. Inject Cargo into PATH for this session ─────────────────────────────── +set "CARGO_BIN=%USERPROFILE%\.cargo\bin" +if exist "%CARGO_BIN%\cargo.exe" ( + set "PATH=%CARGO_BIN%;%PATH%" +) else ( + echo. + echo ERROR: Rust ^(cargo.exe^) was not found. + echo Expected location: %CARGO_BIN% + echo Install Rust from: https://rustup.rs + echo After installing, re-run this script. + echo. + pause + exit /b 1 +) + +REM ── 2. Verify required tools are reachable ─────────────────────────────────── +echo Checking prerequisites... +for %%T in (node npm cargo) do ( + where %%T >nul 2>&1 + if !ERRORLEVEL! neq 0 ( + echo [FAIL] %%T not found in PATH. + echo Install %%T and restart your terminal, then re-run this script. + pause + exit /b 1 + ) + echo [OK] %%T +) + +REM ── 3. Install npm dependencies ────────────────────────────────────────────── +echo. +echo npm install... +call npm install +if !ERRORLEVEL! neq 0 ( + echo. + echo ERROR: npm install failed ^(exit code !ERRORLEVEL!^). + pause + exit /b !ERRORLEVEL! +) + +REM ── 4. Build with Tauri ─────────────────────────────────────────────────────── +REM NOTE: Tauri's beforeBuildCommand in tauri.conf.json already runs +REM "npm run build" ^(tsc + Vite^) internally — no need to call it separately. +REM NOTE: First Rust compilation can take 5-15 minutes. +echo. +echo npm run tauri build... +echo ^(First Rust compilation may take 5-15 minutes — please wait.^) +echo. +call npm run tauri build +if !ERRORLEVEL! neq 0 ( + echo. + echo ERROR: Tauri build failed ^(exit code !ERRORLEVEL!^). + echo Scroll up to read the Rust/Tauri compiler error. + pause + exit /b !ERRORLEVEL! +) + +REM ── 5. Locate installer artifacts ──────────────────────────────────────────── +set "NSIS_DIR=src-tauri\target\release\bundle\nsis" +set "MSI_DIR=src-tauri\target\release\bundle\msi" +set "INSTALLER_PATH=" +set "FOUND=0" +echo. +echo Build succeeded. Checking for installer artifacts... + +if exist "%NSIS_DIR%\*.exe" ( + echo. + echo [NSIS installer] + for %%F in ("%NSIS_DIR%\*.exe") do ( + echo %%~nxF --^> %%~fF + set "INSTALLER_PATH=%%~fF" + ) + set "FOUND=1" +) +if exist "%MSI_DIR%\*.msi" ( + echo. + echo [MSI installer] + for %%F in ("%MSI_DIR%\*.msi") do echo %%~nxF --^> %%~fF + if "!INSTALLER_PATH!"=="" ( + for %%F in ("%MSI_DIR%\*.msi") do set "INSTALLER_PATH=%%~fF" + ) + set "FOUND=1" +) +if "!FOUND!"=="0" ( + echo No installer bundle found. Tauri may require NSIS or WiX Toolset. + echo - NSIS ^(free^): https://nsis.sourceforge.io/Download + echo - WiX Toolset ^(free^): https://wixtoolset.org/ + echo. + echo Raw portable binary: src-tauri\target\release\scrcpy-gui-v4.exe +) + +REM ── 6. Launch installer ──────────────────────────────────────────────────────── +if defined INSTALLER_PATH ( + echo. + echo Launching installer: !INSTALLER_PATH! + start "" "!INSTALLER_PATH!" + echo Installer window opened. +) + +echo. +pause diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..a7eb71d --- /dev/null +++ b/build.ps1 @@ -0,0 +1,115 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Builds ScrcpyGUI via Tauri and produces a Windows installer bundle. +.DESCRIPTION + - Ensures Rust/Cargo is on PATH (rustup default location). + - Validates node, npm, and cargo before starting. + - Runs: npm install → npm run tauri build + (Tauri's beforeBuildCommand in tauri.conf.json runs the Vite frontend build automatically.) + - Saves full output to build.log in the project root. + - Pauses with a clear message on any failure. +#> + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# ── Paths ───────────────────────────────────────────────────────────────────── +$Root = $PSScriptRoot +$LogFile = Join-Path $Root 'build.log' +$BundleDir = Join-Path $Root 'src-tauri\target\release\bundle\windows' + +# ── Logging ─────────────────────────────────────────────────────────────────── +'' | Set-Content $LogFile # truncate / create +function Log { + param([string]$Msg, [ConsoleColor]$Color = 'Cyan') + $line = "[$(Get-Date -Format 'HH:mm:ss')] $Msg" + Write-Host $line -ForegroundColor $Color + Add-Content -Path $LogFile -Value $line +} + +function Fail { + param([string]$Msg) + Log "ERROR: $Msg" -Color Red + Log "Full log saved to: $LogFile" -Color Yellow + Write-Host '' + Write-Host 'Press Enter to close...' -ForegroundColor Yellow + $null = Read-Host + exit 1 +} + +# ── Ensure Cargo is on PATH ─────────────────────────────────────────────────── +$CargoBin = Join-Path $env:USERPROFILE '.cargo\bin' +if (Test-Path (Join-Path $CargoBin 'cargo.exe')) { + if ($env:PATH -notlike "*$CargoBin*") { + $env:PATH = "$CargoBin;$env:PATH" + Log "Added $CargoBin to PATH for this session." + } +} else { + Fail "Rust/Cargo not found at $CargoBin.`nInstall Rust from: https://rustup.rs" +} + +# ── Verify required tools ───────────────────────────────────────────────────── +Log '── Checking prerequisites ──────────────────────────────────────' +foreach ($tool in @('node', 'npm', 'cargo')) { + $cmd = Get-Command $tool -ErrorAction SilentlyContinue + if ($cmd) { + $ver = & $tool --version 2>&1 + Log " $tool → $($cmd.Source) ($($ver -replace "`n",''))" + } else { + Fail "$tool not found in PATH. Ensure it is installed and restart VS Code." + } +} + +# ── Change into project root ────────────────────────────────────────────────── +Set-Location $Root + +# ── npm install ─────────────────────────────────────────────────────────────── +Log '' +Log '── npm install ─────────────────────────────────────────────────' +& npm install 2>&1 | Tee-Object -FilePath $LogFile -Append +if ($LASTEXITCODE -ne 0) { + Fail "npm install failed (exit code $LASTEXITCODE). See $LogFile for details." +} + +# ── Tauri build ─────────────────────────────────────────────────────────────── +# Tauri's beforeBuildCommand ("npm run build") runs tsc + vite automatically. +# No need to run "npm run build" separately. +Log '' +Log '── npm run tauri build ──────────────────────────────────────────' +Log ' (this compiles Rust + bundles the frontend — first run takes 5-15 min)' +& npm run tauri build 2>&1 | Tee-Object -FilePath $LogFile -Append +if ($LASTEXITCODE -ne 0) { + Fail "tauri build failed (exit code $LASTEXITCODE). See $LogFile for details." +} + +# ── Locate artifacts ────────────────────────────────────────────────────────── +Log '' +Log '── Build complete — locating installer artifacts ────────────────' -Color Green + +if (Test-Path $BundleDir) { + $exes = @(Get-ChildItem $BundleDir -Filter '*.exe' -ErrorAction SilentlyContinue) + $msis = @(Get-ChildItem $BundleDir -Filter '*.msi' -ErrorAction SilentlyContinue) + + if ($exes.Count -gt 0) { + Log " Installer EXE(s) in $BundleDir :" -Color Green + $exes | ForEach-Object { Log " $($_.Name) ($([math]::Round($_.Length/1MB,1)) MB)" -Color Green } + } + if ($msis.Count -gt 0) { + Log " MSI(s) in $BundleDir :" -Color Green + $msis | ForEach-Object { Log " $($_.Name) ($([math]::Round($_.Length/1MB,1)) MB)" -Color Green } + } + if ($exes.Count -eq 0 -and $msis.Count -eq 0) { + Log " No installer found in $BundleDir" -Color Yellow + Log " The app binary is at: src-tauri\target\release\scrcpy-gui-v4.exe" -Color Yellow + } +} else { + Log " Bundle directory not found: $BundleDir" -Color Yellow + Log " The app binary may be at: src-tauri\target\release\scrcpy-gui-v4.exe" -Color Yellow +} + +Log '' +Log "Full build log saved to: $LogFile" -Color Green +Write-Host '' +Write-Host 'Press Enter to close...' -ForegroundColor Cyan +$null = Read-Host diff --git a/build_Portable.bat b/build_Portable.bat new file mode 100644 index 0000000..75c8651 --- /dev/null +++ b/build_Portable.bat @@ -0,0 +1,15 @@ +@echo off +REM Build ScrcpyGUI and generate a Windows executable bundle. +cd /d "%~dp0" + +echo Installing npm dependencies... +call npm install +if errorlevel 1 exit /b %errorlevel% + +echo Building the application with Tauri... +call npm run tauri build +if errorlevel 1 exit /b %errorlevel% + +echo Build complete. +echo Output bundle should be available under src-tauri\target\release\bundle\windows +pause diff --git a/flake.nix b/flake.nix index 2479cea..ded7481 100644 --- a/flake.nix +++ b/flake.nix @@ -82,9 +82,9 @@ desktopItems = [ (pkgs.makeDesktopItem { - name = "scrcpy-gui-v3"; - exec = "scrcpy-gui-v3"; - icon = "scrcpy-gui-v3"; + name = "scrcpy-gui-v4"; + exec = "scrcpy-gui-v4"; + icon = "scrcpy-gui-v4"; desktopName = "ScrcpyGUI"; comment = "A modern GUI for Scrcpy written in React and Rust"; categories = [ @@ -97,15 +97,15 @@ postInstall = '' # Install the standard sized icons for size in 32 64 128; do - install -Dm644 icons/''${size}x''${size}.png $out/share/icons/hicolor/''${size}x''${size}/apps/scrcpy-gui-v3.png + install -Dm644 icons/''${size}x''${size}.png $out/share/icons/hicolor/''${size}x''${size}/apps/scrcpy-gui-v4.png done # Install high-resolution icons for GNOME/modern desktops - install -Dm644 icons/128x128@2x.png $out/share/icons/hicolor/256x256/apps/scrcpy-gui-v3.png - install -Dm644 icons/icon.png $out/share/icons/hicolor/512x512/apps/scrcpy-gui-v3.png + install -Dm644 icons/128x128@2x.png $out/share/icons/hicolor/256x256/apps/scrcpy-gui-v4.png + install -Dm644 icons/icon.png $out/share/icons/hicolor/512x512/apps/scrcpy-gui-v4.png # Install a fallback pixmap just in case - install -Dm644 icons/icon.png $out/share/pixmaps/scrcpy-gui-v3.png + install -Dm644 icons/icon.png $out/share/pixmaps/scrcpy-gui-v4.png ''; preFixup = '' diff --git a/package-lock.json b/package-lock.json index 5322832..01b157e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "scrcpy-gui-v3", + "name": "scrcpy-gui-v4", "version": "4.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "scrcpy-gui-v3", + "name": "scrcpy-gui-v4", "version": "4.0.1", "dependencies": { "@tailwindcss/postcss": "4.2.4", @@ -32,7 +32,7 @@ "postcss": "8.5.12", "tailwindcss": "4.2.4", "typescript": "6.0.3", - "vite": "8.0.10", + "vite": "8.0.16", "vitest": "4.1.5" } }, @@ -486,9 +486,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", "funding": { @@ -496,9 +496,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "cpu": [ "arm64" ], @@ -513,9 +513,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "cpu": [ "x64" ], @@ -547,9 +547,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "cpu": [ "x64" ], @@ -564,9 +564,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "cpu": [ "arm" ], @@ -581,9 +581,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "cpu": [ "arm64" ], @@ -598,9 +598,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "cpu": [ "arm64" ], @@ -615,9 +615,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "cpu": [ "ppc64" ], @@ -632,9 +632,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "cpu": [ "s390x" ], @@ -649,9 +649,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "cpu": [ "x64" ], @@ -666,9 +666,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "cpu": [ "x64" ], @@ -683,9 +683,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "cpu": [ "arm64" ], @@ -700,9 +700,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "cpu": [ "wasm32" ], @@ -719,9 +719,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "cpu": [ "arm64" ], @@ -736,9 +736,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "cpu": [ "x64" ], @@ -2717,9 +2717,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "funding": [ { "type": "github", @@ -2916,14 +2916,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -2932,27 +2932,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -3098,9 +3098,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -3239,17 +3239,17 @@ } }, "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", - "tinyglobby": "^0.2.16" + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -3265,7 +3265,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -3316,6 +3316,35 @@ } } }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/vitest": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", diff --git a/package.json b/package.json index a86e72c..788fe9f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "scrcpy-gui-v3", + "name": "scrcpy-gui-v4", "private": true, "version": "4.0.1", "type": "module", @@ -37,7 +37,7 @@ "postcss": "8.5.12", "tailwindcss": "4.2.4", "typescript": "6.0.3", - "vite": "8.0.10", + "vite": "8.0.16", "vitest": "4.1.5" } } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3968c30..b14c2e2 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -202,6 +202,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -1573,7 +1579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" dependencies = [ "byteorder", - "png", + "png 0.17.16", ] [[package]] @@ -1691,6 +1697,19 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.1", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2097,6 +2116,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "muda" version = "0.17.2" @@ -2112,7 +2141,7 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation", "once_cell", - "png", + "png 0.17.16", "serde", "thiserror 2.0.18", "windows-sys 0.60.2", @@ -2717,6 +2746,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -2831,6 +2873,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + [[package]] name = "quick-xml" version = "0.39.2" @@ -3208,7 +3256,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scrcpy-gui-v3" -version = "4.0.0" +version = "4.0.1" dependencies = [ "chrono", "flate2", @@ -3829,6 +3877,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", + "image", "jni", "libc", "log", @@ -3896,7 +3945,7 @@ dependencies = [ "ico", "json-patch", "plist", - "png", + "png 0.17.16", "proc-macro2", "quote", "semver", @@ -4473,7 +4522,7 @@ dependencies = [ "objc2-core-graphics", "objc2-foundation", "once_cell", - "png", + "png 0.17.16", "serde", "thiserror 2.0.18", "windows-sys 0.60.2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index eeaf411..82c96f5 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } +tauri = { version = "2", features = ["tray-icon", "image-png", "image-ico"] } serde = { version = "1", features = ["derive"] } serde_json = "1" reqwest = { version = "0.13", default-features = false, features = ["json", "native-tls"] } @@ -29,4 +29,3 @@ tokio = { version = "1.49.0", features = ["full"] } tauri-plugin-shell = "2.3.5" tauri-plugin-dialog = "2.7.0" chrono = "0.4.43" - diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 36fc42c..11a12aa 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -674,6 +674,41 @@ pub struct ScrcpyConfig { camera_zoom: Option, background_color: Option, keep_active: Option, + shortcut_mod: Option, +} + +/// Maps a modifier name to the scrcpy flag value that accepts both left and right variants. +/// Returns None if any component is unrecognised, so the caller skips the flag entirely. +fn modifier_to_scrcpy_flag(modifier: &str) -> Option { + // Each named key expands to "lkey,rkey" so either physical key works. + // For combos (joined with '+'), we emit every L/R combination separated by ',' + // so any mix of left/right keys triggers the shortcut. + let keys: Option>> = modifier.split('+') + .map(|m| match m.trim() { + "Alt" => Some(vec!["lalt", "ralt"]), + "Ctrl" => Some(vec!["lctrl", "rctrl"]), + _ => None, + }) + .collect(); + + keys.map(|groups| { + // Cartesian product of the groups to get all valid combinations. + let mut combos: Vec = vec![String::new()]; + for group in &groups { + let mut next = Vec::new(); + for combo in &combos { + for key in group { + if combo.is_empty() { + next.push(key.to_string()); + } else { + next.push(format!("{}+{}", combo, key)); + } + } + } + combos = next; + } + combos.join(",") + }) } fn resolve_audio_codec_flag<'a>(config: &'a ScrcpyConfig, audio_codec_override: Option<&'a str>) -> Option<&'a str> { @@ -859,8 +894,15 @@ fn build_scrcpy_args(config: &ScrcpyConfig, video_dir_fallback: Option, args.push(format!("--background-color={}", trimmed)); } } + + if let Some(ref shortcut_mod) = config.shortcut_mod { + let trimmed = shortcut_mod.trim(); + if let Some(flag) = modifier_to_scrcpy_flag(trimmed) { + args.push(format!("--shortcut-mod={}", flag)); + } + } } - + args } @@ -1171,6 +1213,7 @@ mod tests { camera_zoom: None, background_color: None, keep_active: None, + shortcut_mod: None, } } @@ -1396,7 +1439,7 @@ pub async fn download_scrcpy(window: Window) -> Result<(), String> { downloaded += chunk.len() as u64; if total_size > 0 { let percent = (downloaded * 100) / total_size; - let _ = window.emit("download-progress", json!({ "percent": percent })); + let _ = window.emit("scrcpy-status", json!({ "type": "download-progress", "percent": percent })); } } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2ad505d..1c5db77 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,10 +7,73 @@ use tokio::process::Child; #[cfg(target_os = "linux")] use std::os::unix::process::CommandExt; +#[cfg(desktop)] +use tauri::{ + image::Image, + menu::{Menu, MenuItem}, + tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, +}; + +#[cfg(desktop)] +const TRAY_ICON_BYTES: &[u8] = include_bytes!("../icons/icon.png"); + pub struct ScrcpyState { pub processes: Mutex>, } +#[cfg(desktop)] +fn restore_main_window(app: &tauri::AppHandle) { + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_skip_taskbar(false); + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + } +} + +#[cfg(desktop)] +fn hide_main_window_to_tray(app: &tauri::AppHandle) { + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_skip_taskbar(true); + let _ = window.hide(); + } +} + +#[cfg(desktop)] +fn setup_tray(app: &mut tauri::App) -> tauri::Result<()> { + let open_item = MenuItem::with_id(app, "tray-open", "Open / Restore", true, None::<&str>)?; + let quit_item = MenuItem::with_id(app, "tray-exit", "Exit", true, None::<&str>)?; + let menu = Menu::with_items(app, &[&open_item, &quit_item])?; + + let _tray = TrayIconBuilder::new() + .icon(Image::from_bytes(TRAY_ICON_BYTES)?) + .menu(&menu) + .show_menu_on_left_click(false) + .on_tray_icon_event(|tray, event| match event { + TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } + | TrayIconEvent::DoubleClick { + button: MouseButton::Left, + .. + } => restore_main_window(tray.app_handle()), + _ => {} + }) + .build(app)?; + + app.on_menu_event(|app, event| { + if event.id() == "tray-open" { + restore_main_window(app); + } else if event.id() == "tray-exit" { + app.exit(0); + } + }); + + Ok(()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { // Fix for white screen on Linux (Wayland/NVIDIA) @@ -56,6 +119,12 @@ pub fn run() { } tauri::Builder::default() + .on_window_event(|window, event| { + if let tauri::WindowEvent::CloseRequested { api, .. } = event { + api.prevent_close(); + hide_main_window_to_tray(window.app_handle()); + } + }) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) .setup(|app| { @@ -63,9 +132,9 @@ pub fn run() { processes: Mutex::new(HashMap::new()), }); - // Show splashscreen instantly - if let Some(splash_window) = app.get_webview_window("splashscreen") { - splash_window.show().unwrap(); + #[cfg(desktop)] + { + setup_tray(app)?; } Ok(()) @@ -91,7 +160,6 @@ pub fn run() { commands::get_scrcpy_bin_dir, commands::run_terminal_command, commands::check_scrcpy_update, - close_splashscreen, get_app_version ]) .run(tauri::generate_context!()) @@ -102,16 +170,3 @@ pub fn run() { fn get_app_version(app: tauri::AppHandle) -> String { app.package_info().version.to_string() } - -#[tauri::command] -async fn close_splashscreen(window: tauri::Window) { - // Get the main window - if let Some(main_window) = window.get_webview_window("main") { - // Show the main window - main_window.show().unwrap(); - } - // Close the splashscreen window - if let Some(splash_window) = window.get_webview_window("splashscreen") { - splash_window.close().unwrap(); - } -} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index dbf9d52..1396923 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -16,22 +16,11 @@ "title": "ScrcpyGUI", "width": 1200, "height": 890, - "visible": false, + "visible": true, "resizable": false, "center": true, "transparent": false, "decorations": true - }, - { - "label": "splashscreen", - "url": "splashscreen.html", - "width": 500, - "height": 400, - "visible": false, - "decorations": false, - "transparent": true, - "center": true, - "alwaysOnTop": true } ], "security": { diff --git a/src/App.tsx b/src/App.tsx index 4105b89..75a41e9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { open } from "@tauri-apps/plugin-dialog"; import Sidebar from "./components/Sidebar"; @@ -78,6 +78,7 @@ function App() { const [appVersion, setAppVersion] = useState("3.3.0"); const [lastCheckedPath, setLastCheckedPath] = useState(undefined); const [hasCheckedUpdate, setHasCheckedUpdate] = useState(false); + const trayHiddenRef = useRef(false); const showAlert = ( title: string, @@ -103,21 +104,19 @@ function App() { }; useEffect(() => { - // Initial setup: fetch version and close splashscreen + // Initial setup: fetch version for the header const initApp = async () => { try { + const startedAt = performance.now(); const v = await getVersion(); setAppVersion(v); - - const { invoke } = await import('@tauri-apps/api/core'); - await invoke('close_splashscreen'); + console.info(`[startup] version loaded in ${Math.round(performance.now() - startedAt)}ms`); } catch (e) { console.error("Initialization failed:", e); } }; - const timer = setTimeout(initApp, 500); - return () => clearTimeout(timer); + void initApp(); }, []); useEffect(() => { @@ -183,18 +182,63 @@ function App() { }; }, [activeDevice]); + useEffect(() => { + const appWindow = getCurrentWindow(); + let cancelled = false; + + const syncTrayState = async () => { + try { + const minimized = await appWindow.isMinimized(); + if (cancelled) { + return; + } + + if (minimized && !trayHiddenRef.current) { + trayHiddenRef.current = true; + await appWindow.hide(); + await appWindow.setSkipTaskbar(true); + } else if (!minimized && trayHiddenRef.current) { + trayHiddenRef.current = false; + await appWindow.setSkipTaskbar(false); + } + } catch (error) { + console.error("Failed to sync tray state:", error); + } + }; + + void syncTrayState(); + + const unlistenResized = appWindow.onResized(() => { + void syncTrayState(); + }); + + const pollTimer = window.setInterval(() => { + void syncTrayState(); + }, 500); + + return () => { + cancelled = true; + window.clearInterval(pollTimer); + void unlistenResized.then((unlisten) => unlisten()); + }; + }, []); + useEffect(() => { if (activeDevice) { setConfig(prev => ({ ...prev, device: activeDevice })); } }, [activeDevice]); + const VALID_SHORTCUT_MODIFIERS = ['Alt', 'Ctrl', 'Ctrl+Alt']; + const handleStart = async () => { if (!activeDevice) { showAlert(t('alerts.noDeviceSelectedTitle'), t('alerts.noDeviceSelectedMessage'), "warning"); return; } - await runScrcpy(config); + const stored = localStorage.getItem('scrcpy_shortcut_modifier') ?? 'Alt'; + const shortcutMod = VALID_SHORTCUT_MODIFIERS.includes(stored) ? stored : 'Alt'; + await runScrcpy({ ...config, shortcutMod }); }; const handleStop = async () => { @@ -288,7 +332,7 @@ function App() { return ( -
+