From ca710e617577302f457380e61d2f7d340abee53e Mon Sep 17 00:00:00 2001 From: alexma233 Date: Sun, 14 Jun 2026 03:39:41 +0800 Subject: [PATCH 1/5] feat(nix): add desktop flake support --- CONTRIBUTING.md | 8 + README.md | 9 + flake.lock | 27 ++ flake.nix | 374 +++++++++++++++++++++++ src/apps/desktop/src/main/app-updater.ts | 23 +- 5 files changed, 437 insertions(+), 4 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c43d434f..3d72521bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,14 @@ Thank you for considering a contribution to Arkloop. This document covers the pr - Docker and Docker Compose - PostgreSQL 16+ (or use `docker compose up postgres`) +Alternatively, if you use Nix with flakes enabled, enter the pinned development shell: + +```bash +nix develop +``` + +The flake provides Go 1.26, Node.js, Corepack-managed pnpm, Docker Compose, PostgreSQL/Redis clients, and the local CI helper dependencies. Docker still needs a running host daemon. + ### Local Development Setup ```bash diff --git a/README.md b/README.md index ea73439c8..fdc991277 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,15 @@ Homebrew installs the Arkloop CLI only: brew install qqqqqf-q/arkloop/arkloop && ark web ``` +### Desktop via Nix + +Linux users with flakes enabled can run or install the desktop app from the flake: + +```bash +nix run github:qqqqqf-q/Arkloop +nix profile install github:qqqqqf-q/Arkloop +``` + For a headless Linux machine, use one command: ```bash diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..e6a89c438 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1781268102, + "narHash": "sha256-Zn5KTggEmUB3lXn/ccERNcBdddE6IaOFber9dWViWDg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "49a4bd0573c376468dd7996ddb6f9fa31d8c4d97", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..b4d59bca9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,374 @@ +{ + description = "Arkloop desktop application and development environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = + { self, nixpkgs, ... }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + linuxSystems = [ + "x86_64-linux" + "aarch64-linux" + ]; + + forAllSystems = + f: + nixpkgs.lib.genAttrs systems ( + system: + f ( + import nixpkgs { + inherit system; + } + ) + ); + + forLinuxSystems = + f: + nixpkgs.lib.genAttrs linuxSystems ( + system: + f ( + import nixpkgs { + inherit system; + } + ) + ); + + desktopVersion = (builtins.fromJSON (builtins.readFile ./src/apps/desktop/package.json)).version; + + sourceFilter = + lib: path: type: + let + root = toString ./.; + rel = lib.removePrefix "${root}/" (toString path); + excluded = [ + ".cache" + ".data" + ".env" + ".envrc" + ".git" + ".gstack" + ".mypy_cache" + ".pytest_cache" + ".ruff_cache" + ".vscode" + "AGENTS.md" + "CLAUDE.md" + "data" + "mcp.config.json" + "node_modules" + "result" + "result-arkloop-desktop" + "config/openviking/ov.conf" + "src/apps/desktop/cli-bin" + "src/apps/desktop/dist" + "src/apps/desktop/release" + "src/apps/desktop/sidecar-bin" + "src/apps/web/dist" + ]; + in + !(lib.any (prefix: rel == prefix || lib.hasPrefix "${prefix}/" rel) excluded) + && !(lib.hasPrefix ".env." rel); + + cleanSource = + pkgs: + pkgs.lib.cleanSourceWith { + src = ./.; + filter = sourceFilter pkgs.lib; + }; + + electronArchFor = pkgs: if pkgs.stdenv.hostPlatform.isAarch64 then "arm64" else "x64"; + + buildGo126 = pkgs: pkgs.buildGoModule.override { go = pkgs.go_1_26; }; + + goBinaries = + pkgs: + (buildGo126 pkgs) { + pname = "arkloop-desktop-go-binaries"; + version = desktopVersion; + src = cleanSource pkgs; + modRoot = "./"; + subPackages = [ + "src/services/activity-record/cmd/activity-record" + "src/services/cli/cmd/ark" + "src/services/desktop/cmd/desktop" + ]; + tags = [ "desktop" ]; + ldflags = [ + "-s" + "-w" + "-X main.version=v${desktopVersion}" + ]; + vendorHash = "sha256-HSP4IMyafSLyMI7bt7VKqOU2EHDkB/PNdELhoSSfjUI="; + env.CGO_ENABLED = "0"; + doCheck = false; + modBuildPhase = '' + runHook preBuild + go work vendor + mkdir -p vendor + runHook postBuild + ''; + }; + + arkloopDesktop = + pkgs: + let + lib = pkgs.lib; + pnpm = pkgs.pnpm_10_29_2; + electronArch = electronArchFor pkgs; + goBin = goBinaries pkgs; + runtimePath = lib.makeBinPath [ + pkgs.bash + pkgs.coreutils + pkgs.docker-client + pkgs.docker-compose + pkgs.git + pkgs.openssl + pkgs.xdg-utils + ]; + in + pkgs.stdenv.mkDerivation (finalAttrs: { + pname = "arkloop-desktop"; + version = desktopVersion; + src = cleanSource pkgs; + + pnpmDeps = pkgs.fetchPnpmDeps { + inherit (finalAttrs) pname version src; + pnpm = pnpm; + fetcherVersion = 4; + hash = "sha256-ycM0fYo1O3exXS3jMgarfXei4mMWEFx7nhw1X6UOR5Y="; + }; + + nativeBuildInputs = [ + pkgs.go_1_26 + pkgs.jq + pkgs.makeWrapper + pkgs.nodejs_24 + pkgs.pnpmConfigHook + pnpm + ]; + + env = { + ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; + GOTOOLCHAIN = "local"; + }; + + postPatch = '' + substituteInPlace package.json src/apps/desktop/package.json \ + --replace-fail '"packageManager": "pnpm@10.29.3"' '"packageManager": "pnpm@${pnpm.version}"' + ''; + + preBuild = '' + export HOME="$TMPDIR/home" + export XDG_CACHE_HOME="$TMPDIR/xdg-cache" + export ELECTRON_BUILDER_CACHE="$TMPDIR/electron-builder-cache" + export npm_config_fund=false + export npm_config_update_notifier=false + export pnpm_config_manage_package_manager_versions=false + export ARKLOOP_RELEASE_VERSION="v${desktopVersion}" + + mkdir -p "$HOME" "$XDG_CACHE_HOME" "$ELECTRON_BUILDER_CACHE" "$TMPDIR/nix-bin" + cat > "$TMPDIR/nix-bin/pnpm" <<'EOF' + #!${pkgs.runtimeShell} + exec ${pnpm}/bin/pnpm --config.manage-package-manager-versions=false "$@" + EOF + chmod +x "$TMPDIR/nix-bin/pnpm" + export PATH="$TMPDIR/nix-bin:$PATH" + + mkdir -p src/apps/desktop/sidecar-bin src/apps/desktop/cli-bin + cp ${goBin}/bin/desktop src/apps/desktop/sidecar-bin/desktop-linux-${electronArch} + cp ${goBin}/bin/ark src/apps/desktop/cli-bin/ark-linux-${electronArch} + ''; + + buildPhase = '' + runHook preBuild + + pnpm --config.manage-package-manager-versions=false --filter @arkloop/desktop run build:web + pnpm --config.manage-package-manager-versions=false --filter @arkloop/desktop run build:electron + pnpm --config.manage-package-manager-versions=false --dir src/apps/desktop exec electron-builder \ + --linux dir \ + -c.electronDist=${pkgs.electron_41.dist} \ + -c.electronVersion=${pkgs.electron_41.version} \ + -c.extraMetadata.version=${desktopVersion} + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + release_dir="src/apps/desktop/release/linux-unpacked" + if [ ! -d "$release_dir" ]; then + echo "missing electron-builder output: $release_dir" >&2 + exit 1 + fi + + mkdir -p "$out/opt/arkloop" "$out/bin" "$out/share/applications" "$out/share/icons/hicolor/512x512/apps" + cp -R "$release_dir/." "$out/opt/arkloop/" + + mkdir -p "$out/opt/arkloop/resources/activity-record/bin" + cp ${goBin}/bin/activity-record "$out/opt/arkloop/resources/activity-record/bin/activity-record" + + app_bin="" + for candidate in arkloop Arkloop @arkloopdesktop; do + if [ -x "$out/opt/arkloop/$candidate" ]; then + app_bin="$candidate" + break + fi + done + if [ -z "$app_bin" ]; then + echo "missing Arkloop executable in $release_dir" >&2 + exit 1 + fi + + makeWrapper "$out/opt/arkloop/$app_bin" "$out/bin/arkloop" \ + --set ARKLOOP_DISABLE_APP_UPDATER 1 \ + --set ARKLOOP_ACTIVITY_RECORD_BIN "$out/opt/arkloop/resources/activity-record/bin/activity-record" \ + --prefix PATH : ${lib.escapeShellArg runtimePath} + + makeWrapper "$out/opt/arkloop/resources/cli/ark" "$out/bin/ark" \ + --prefix PATH : ${lib.escapeShellArg runtimePath} + + install -Dm644 src/apps/desktop/resources/icon.png "$out/share/icons/hicolor/512x512/apps/arkloop.png" + cat > "$out/share/applications/arkloop.desktop" < { const devMockPhase = readDevMockPhase() if (devMockPhase) return buildDevMockState(devMockPhase) + if (isUpdaterDisabled()) { + return { + supported: false, + phase: 'unsupported', + currentVersion: app.getVersion(), + latestVersion: null, + progressPercent: 0, + error: null, + } + } return { supported: app.isPackaged, phase: app.isPackaged ? 'idle' : 'unsupported', @@ -111,7 +126,7 @@ export async function checkForAppUpdates(): Promise { return getAppUpdaterState() } - if (!app.isPackaged) { + if (isUpdaterDisabled() || !app.isPackaged) { patchState({ phase: 'unsupported', latestVersion: null, progressPercent: 0, error: null }) return getAppUpdaterState() } @@ -135,7 +150,7 @@ export async function downloadAppUpdate(): Promise { return getAppUpdaterState() } - if (!app.isPackaged) { + if (isUpdaterDisabled() || !app.isPackaged) { patchState({ phase: 'unsupported', latestVersion: null, progressPercent: 0, error: null }) return getAppUpdaterState() } @@ -159,7 +174,7 @@ export function installAppUpdate(): void { return } - if (!app.isPackaged) { + if (isUpdaterDisabled() || !app.isPackaged) { patchState({ phase: 'unsupported', latestVersion: null, progressPercent: 0, error: null }) return } @@ -183,7 +198,7 @@ export function setupAppUpdater( } initialized = true - if (!app.isPackaged) { + if (isUpdaterDisabled() || !app.isPackaged) { patchState({}) if (readDevMockPhase() && options.autoCheck) { void checkForAppUpdates().catch(() => {}) From 7b57294609dc32b20f6fb64aa330f514f5bd5301 Mon Sep 17 00:00:00 2001 From: alexma233 Date: Sun, 14 Jun 2026 10:12:41 +0800 Subject: [PATCH 2/5] fix(nix): wrap desktop electron environment --- flake.nix | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/flake.nix b/flake.nix index b4d59bca9..45e2ceed3 100644 --- a/flake.nix +++ b/flake.nix @@ -152,9 +152,19 @@ pkgs.makeWrapper pkgs.nodejs_24 pkgs.pnpmConfigHook + pkgs.wrapGAppsHook3 pnpm ]; + buildInputs = [ + pkgs.glib + pkgs.gsettings-desktop-schemas + pkgs.gtk3 + pkgs.gtk4 + ]; + + dontWrapGApps = true; + env = { ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; GOTOOLCHAIN = "local"; @@ -228,9 +238,14 @@ exit 1 fi + gappsWrapperArgsHook makeWrapper "$out/opt/arkloop/$app_bin" "$out/bin/arkloop" \ + "''${gappsWrapperArgs[@]}" \ --set ARKLOOP_DISABLE_APP_UPDATER 1 \ --set ARKLOOP_ACTIVITY_RECORD_BIN "$out/opt/arkloop/resources/activity-record/bin/activity-record" \ + --set CHROME_DEVEL_SANDBOX ${pkgs.electron_41}/libexec/electron/chrome-sandbox \ + --add-flags "--disable-gpu" \ + --add-flags "--disable-gpu-compositing" \ --prefix PATH : ${lib.escapeShellArg runtimePath} makeWrapper "$out/opt/arkloop/resources/cli/ark" "$out/bin/ark" \ From 614d0208bcff1fece26295cb31f9b1ad52828e75 Mon Sep 17 00:00:00 2001 From: alexma233 Date: Sun, 14 Jun 2026 10:25:39 +0800 Subject: [PATCH 3/5] fix(nix): run desktop with nixpkgs electron --- flake.nix | 37 ++++++++++++++------------- src/apps/desktop/src/main/cli-tool.ts | 4 +-- src/apps/desktop/src/main/config.ts | 8 ++++++ src/apps/desktop/src/main/index.ts | 16 ++++++------ src/apps/desktop/src/main/ipc.ts | 18 ++++++------- src/apps/desktop/src/main/sidecar.ts | 5 ++-- src/apps/desktop/src/main/tray.ts | 5 ++-- 7 files changed, 52 insertions(+), 41 deletions(-) diff --git a/flake.nix b/flake.nix index 45e2ceed3..0d2c41fac 100644 --- a/flake.nix +++ b/flake.nix @@ -220,35 +220,36 @@ exit 1 fi - mkdir -p "$out/opt/arkloop" "$out/bin" "$out/share/applications" "$out/share/icons/hicolor/512x512/apps" - cp -R "$release_dir/." "$out/opt/arkloop/" - - mkdir -p "$out/opt/arkloop/resources/activity-record/bin" - cp ${goBin}/bin/activity-record "$out/opt/arkloop/resources/activity-record/bin/activity-record" - - app_bin="" - for candidate in arkloop Arkloop @arkloopdesktop; do - if [ -x "$out/opt/arkloop/$candidate" ]; then - app_bin="$candidate" - break - fi - done - if [ -z "$app_bin" ]; then - echo "missing Arkloop executable in $release_dir" >&2 + if [ ! -d "$release_dir/resources" ]; then + echo "missing electron-builder resources: $release_dir/resources" >&2 exit 1 fi + if [ ! -f "$release_dir/resources/app.asar" ]; then + echo "missing electron app archive: $release_dir/resources/app.asar" >&2 + exit 1 + fi + + resources_dir="$out/share/arkloop/resources" + mkdir -p "$resources_dir" "$out/bin" "$out/share/applications" "$out/share/icons/hicolor/512x512/apps" + cp -R "$release_dir/resources/." "$resources_dir/" + + mkdir -p "$resources_dir/activity-record/bin" + cp ${goBin}/bin/activity-record "$resources_dir/activity-record/bin/activity-record" gappsWrapperArgsHook - makeWrapper "$out/opt/arkloop/$app_bin" "$out/bin/arkloop" \ + makeWrapper "${pkgs.electron_41}/bin/electron" "$out/bin/arkloop" \ "''${gappsWrapperArgs[@]}" \ + --set ELECTRON_FORCE_IS_PACKAGED 1 \ + --set ARKLOOP_RESOURCES_PATH "$resources_dir" \ --set ARKLOOP_DISABLE_APP_UPDATER 1 \ - --set ARKLOOP_ACTIVITY_RECORD_BIN "$out/opt/arkloop/resources/activity-record/bin/activity-record" \ + --set ARKLOOP_ACTIVITY_RECORD_BIN "$resources_dir/activity-record/bin/activity-record" \ --set CHROME_DEVEL_SANDBOX ${pkgs.electron_41}/libexec/electron/chrome-sandbox \ --add-flags "--disable-gpu" \ --add-flags "--disable-gpu-compositing" \ + --add-flags "$resources_dir/app.asar" \ --prefix PATH : ${lib.escapeShellArg runtimePath} - makeWrapper "$out/opt/arkloop/resources/cli/ark" "$out/bin/ark" \ + makeWrapper "$resources_dir/cli/ark" "$out/bin/ark" \ --prefix PATH : ${lib.escapeShellArg runtimePath} install -Dm644 src/apps/desktop/resources/icon.png "$out/share/icons/hicolor/512x512/apps/arkloop.png" diff --git a/src/apps/desktop/src/main/cli-tool.ts b/src/apps/desktop/src/main/cli-tool.ts index 7b17218c6..b076dfec5 100644 --- a/src/apps/desktop/src/main/cli-tool.ts +++ b/src/apps/desktop/src/main/cli-tool.ts @@ -3,7 +3,7 @@ import * as fs from 'fs' import * as os from 'os' import * as path from 'path' import { execFileSync } from 'child_process' -import { loadConfig, saveConfig } from './config' +import { loadConfig, resourcePath, saveConfig } from './config' export type CommandLineToolStatus = { available: boolean @@ -36,7 +36,7 @@ function fileExists(pathname: string): boolean { } function bundledCliPath(): string { - return path.join(process.resourcesPath, 'cli', cliBinaryName()) + return resourcePath('cli', cliBinaryName()) } function bundledWebRoot(sourcePath: string): string { diff --git a/src/apps/desktop/src/main/config.ts b/src/apps/desktop/src/main/config.ts index 4ed2e2460..369e3f466 100644 --- a/src/apps/desktop/src/main/config.ts +++ b/src/apps/desktop/src/main/config.ts @@ -31,6 +31,14 @@ const VERSIONS_FILE = path.join(CONFIG_DIR, 'versions.json') const LEGACY_SIDECAR_VERSION_FILE = path.join(CONFIG_DIR, 'bin', 'sidecar.version.json') const LEGACY_OPENCLI_VERSION_FILE = path.join(CONFIG_DIR, 'bin', 'opencli.version.json') +export function getResourcesPath(): string { + return process.env.ARKLOOP_RESOURCES_PATH || process.resourcesPath +} + +export function resourcePath(...segments: string[]): string { + return path.join(getResourcesPath(), ...segments) +} + export type VersionsState = { sidecar?: { version: string; updated_at: string } openviking?: { version: string; updated_at: string } diff --git a/src/apps/desktop/src/main/index.ts b/src/apps/desktop/src/main/index.ts index 30e6e73d9..0066368f0 100644 --- a/src/apps/desktop/src/main/index.ts +++ b/src/apps/desktop/src/main/index.ts @@ -2,7 +2,7 @@ import { app, BrowserWindow, Menu, nativeImage, powerSaveBlocker, session, shell import * as fs from 'fs' import * as os from 'os' import * as path from 'path' -import { loadConfig, normalizeConfig, saveConfig } from './config' +import { loadConfig, normalizeConfig, resourcePath, saveConfig } from './config' import { startSidecar, stopSidecar, @@ -69,19 +69,19 @@ function getAppIconPath(): string { ? ( process.platform === 'darwin' ? [ - path.join(process.resourcesPath, 'icon.icns'), - path.join(process.resourcesPath, 'app.asar', 'resources', 'icon.png'), + resourcePath('icon.icns'), + resourcePath('app.asar', 'resources', 'icon.png'), path.join(app.getAppPath(), 'resources', 'icon.icns'), ] : process.platform === 'win32' ? [ - path.join(process.resourcesPath, 'icon.ico'), - path.join(process.resourcesPath, 'app.asar', 'resources', 'icon.ico'), + resourcePath('icon.ico'), + resourcePath('app.asar', 'resources', 'icon.ico'), path.join(app.getAppPath(), 'resources', 'icon.ico'), ] : [ - path.join(process.resourcesPath, 'icon.png'), - path.join(process.resourcesPath, 'app.asar', 'resources', 'icon.png'), + resourcePath('icon.png'), + resourcePath('app.asar', 'resources', 'icon.png'), path.join(app.getAppPath(), 'resources', 'icon.png'), ] ) @@ -457,7 +457,7 @@ function loadContent(win: BrowserWindow): void { win.webContents.openDevTools({ mode: 'detach' }) } else if (app.isPackaged) { // 生产打包模式 - const rendererPath = path.join(process.resourcesPath, 'renderer', 'index.html') + const rendererPath = resourcePath('renderer', 'index.html') win.loadFile(rendererPath) } else { // 开发模式但非 ELECTRON_DEV(直接 build 后测试) diff --git a/src/apps/desktop/src/main/ipc.ts b/src/apps/desktop/src/main/ipc.ts index c0bf4076c..233d73dff 100644 --- a/src/apps/desktop/src/main/ipc.ts +++ b/src/apps/desktop/src/main/ipc.ts @@ -3,7 +3,7 @@ import http from 'http' import os from 'os' import { DatabaseSync } from 'node:sqlite' import path from 'path' -import { loadConfig, saveConfig, getConfigPath } from './config' +import { loadConfig, resourcePath, saveConfig, getConfigPath } from './config' import { getSidecarStatus, getSidecarRuntime, @@ -1083,19 +1083,19 @@ function getDesktopIconDataUrl(): string | null { ? ( process.platform === 'darwin' ? [ - path.join(process.resourcesPath, 'app.asar', 'resources', 'icon.png'), - path.join(process.resourcesPath, 'icon.png'), + resourcePath('app.asar', 'resources', 'icon.png'), + resourcePath('icon.png'), ] : process.platform === 'win32' ? [ - path.join(process.resourcesPath, 'app.asar', 'resources', 'icon.png'), - path.join(process.resourcesPath, 'icon.png'), - path.join(process.resourcesPath, 'icon.ico'), - path.join(process.resourcesPath, 'app.asar', 'resources', 'icon.ico'), + resourcePath('app.asar', 'resources', 'icon.png'), + resourcePath('icon.png'), + resourcePath('icon.ico'), + resourcePath('app.asar', 'resources', 'icon.ico'), ] : [ - path.join(process.resourcesPath, 'app.asar', 'resources', 'icon.png'), - path.join(process.resourcesPath, 'icon.png'), + resourcePath('app.asar', 'resources', 'icon.png'), + resourcePath('icon.png'), ] ) : [ diff --git a/src/apps/desktop/src/main/sidecar.ts b/src/apps/desktop/src/main/sidecar.ts index 62200a915..5292459ec 100644 --- a/src/apps/desktop/src/main/sidecar.ts +++ b/src/apps/desktop/src/main/sidecar.ts @@ -10,6 +10,7 @@ import { app } from 'electron' import type { LocalPortMode, MemoryConfig, NetworkConfig } from './types' import { appendSidecarLog, getDesktopLogPaths } from './logging' import { getBrowserSearchBaseUrl } from './browser-search' +import { resourcePath } from './config' export type SidecarStatus = 'stopped' | 'starting' | 'running' | 'crashed' @@ -147,7 +148,7 @@ export function getSidecarPath(): string { function getBundledSidecarPath(): string { const bundledName = process.platform === 'win32' ? 'desktop.exe' : 'desktop' - return path.join(process.resourcesPath, 'sidecar', bundledName) + return resourcePath('sidecar', bundledName) } function getDevBuiltSidecarPath(): string { @@ -473,7 +474,7 @@ function resolveBinaryPath(): string { function resolveBundledProjectDir(): string | null { if (!app.isPackaged) return null - const candidate = path.join(process.resourcesPath, 'arkloop-project') + const candidate = resourcePath('arkloop-project') if (!fs.existsSync(path.join(candidate, 'compose.yaml'))) return null return ensureWritableBundledProjectDir(candidate) } diff --git a/src/apps/desktop/src/main/tray.ts b/src/apps/desktop/src/main/tray.ts index 65c7641b0..008782886 100644 --- a/src/apps/desktop/src/main/tray.ts +++ b/src/apps/desktop/src/main/tray.ts @@ -1,6 +1,7 @@ import { Tray, Menu, nativeImage, nativeTheme, app, BrowserWindow, globalShortcut } from 'electron' import * as fs from 'fs' import * as path from 'path' +import { resourcePath } from './config' let tray: Tray | null = null type ShowWindow = () => void @@ -8,8 +9,8 @@ type ShowWindow = () => void function resolveResource(name: string): string { const candidates = app.isPackaged ? [ - path.join(process.resourcesPath, name), - path.join(process.resourcesPath, 'app.asar', 'resources', name), + resourcePath(name), + resourcePath('app.asar', 'resources', name), path.join(app.getAppPath(), 'resources', name), ] : [ From 2962ae3aafb5bc8e7c3f6478c1e19aa3672fc8df Mon Sep 17 00:00:00 2001 From: alexma233 Date: Sun, 14 Jun 2026 11:18:07 +0800 Subject: [PATCH 4/5] fix(nix): enable desktop gpu acceleration by default --- flake.nix | 24 ++++++++++++++++++++---- src/apps/desktop/src/main/index.ts | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 0d2c41fac..5b0dc3163 100644 --- a/flake.nix +++ b/flake.nix @@ -124,6 +124,12 @@ pnpm = pkgs.pnpm_10_29_2; electronArch = electronArchFor pkgs; goBin = goBinaries pkgs; + gpuLibraryPath = lib.makeLibraryPath [ + pkgs.libglvnd + pkgs.libva + pkgs.mesa + pkgs.vulkan-loader + ]; runtimePath = lib.makeBinPath [ pkgs.bash pkgs.coreutils @@ -236,17 +242,27 @@ mkdir -p "$resources_dir/activity-record/bin" cp ${goBin}/bin/activity-record "$resources_dir/activity-record/bin/activity-record" + mkdir -p "$out/libexec/arkloop" + cat > "$out/libexec/arkloop/arkloop" < { + if (process.env.ARKLOOP_LOG_GPU !== '1') return + try { + console.info('[desktop] gpu command line', { + disableGpu: app.commandLine.hasSwitch('disable-gpu'), + disableGpuSandbox: app.commandLine.hasSwitch('disable-gpu-sandbox'), + ignoreBlocklist: app.commandLine.hasSwitch('ignore-gpu-blocklist'), + ozonePlatform: app.commandLine.getSwitchValue('ozone-platform'), + useGl: app.commandLine.getSwitchValue('use-gl'), + useAngle: app.commandLine.getSwitchValue('use-angle'), + disableFeatures: app.commandLine.getSwitchValue('disable-features'), + }) + console.info('[desktop] gpu feature status', app.getGPUFeatureStatus()) + console.info('[desktop] gpu info', await app.getGPUInfo('complete')) + } catch (error) { + console.warn('[desktop] gpu info unavailable', { error }) + } +} + let isQuitting = false let shutdownInProgress = false let powerSaveBlockerId: number | null = null @@ -545,6 +564,9 @@ if (!hasSingleInstanceLock) { // 先创建窗口,让用户立即看到 LoadingPage mainWindow = createWindow() + setTimeout(() => { + void logGpuInfoIfRequested() + }, 1000) const pushEmbeddedStateToRendererOnce = (() => { let sent = false From 01307b37aba865bf9f9ea7e7e777d274a26c59af Mon Sep 17 00:00:00 2001 From: alexma233 Date: Sun, 14 Jun 2026 11:44:06 +0800 Subject: [PATCH 5/5] fix(nix): skip cli install prompt in desktop package --- flake.nix | 1 + src/apps/desktop/src/main/cli-tool.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 5b0dc3163..ad8875d5b 100644 --- a/flake.nix +++ b/flake.nix @@ -257,6 +257,7 @@ makeWrapper "$out/libexec/arkloop/arkloop" "$out/bin/arkloop" \ "''${gappsWrapperArgs[@]}" \ --set ELECTRON_FORCE_IS_PACKAGED 1 \ + --set ARKLOOP_NIX_PACKAGE 1 \ --set ARKLOOP_RESOURCES_PATH "$resources_dir" \ --set ARKLOOP_DISABLE_APP_UPDATER 1 \ --set ARKLOOP_ACTIVITY_RECORD_BIN "$resources_dir/activity-record/bin/activity-record" \ diff --git a/src/apps/desktop/src/main/cli-tool.ts b/src/apps/desktop/src/main/cli-tool.ts index b076dfec5..a6243bcd5 100644 --- a/src/apps/desktop/src/main/cli-tool.ts +++ b/src/apps/desktop/src/main/cli-tool.ts @@ -161,6 +161,7 @@ export async function installCommandLineTool(): Promise { function shouldPromptForCommandLineTool(): boolean { if (!app.isPackaged) return false + if (process.env.ARKLOOP_NIX_PACKAGE === '1') return false const config = loadConfig() if (config.desktop.commandLineToolPromptDisabled) return false const status = getCommandLineToolStatus()