diff --git a/nix/colorshell.nix b/nix/colorshell.nix index 82099d9..3b21840 100644 --- a/nix/colorshell.nix +++ b/nix/colorshell.nix @@ -11,7 +11,9 @@ glib, gjs, libadwaita, + pywal16, dart-sass, + psmisc, socat, }: let @@ -26,6 +28,8 @@ let lib.fileset.unions [ ../flake.nix ../flake.lock + ../result + ../build ./. ] ); @@ -68,10 +72,21 @@ let inherit src; - # Replace reference to ags FHS install path postPatch = '' - substituteInPlace package.json pnpm-lock.yaml \ - --replace-fail "/usr/share/ags/js" "${inputs'.ags.packages.ags.jsPackage}" + # Copy the ags JS lib from the Nix store into the source tree so pnpm can + # treat it as a local file: dependency (no /nix/store path inside pnpm). + cp -R ${inputs'.ags.packages.ags.jsPackage} ags-js-lib + + # Point the devDependency at the local copy instead of the FHS path. + substituteInPlace package.json \ + --replace-fail "file:/usr/share/ags/js" "file:./ags-js-lib" + + # Update the lockfile to reference the local copy as well. + # We need to keep specifiers in sync with package.json to satisfy + # pnpm's frozen-lockfile check. + substituteInPlace pnpm-lock.yaml \ + --replace-fail "file:/usr/share/ags/js" "file:./ags-js-lib" \ + --replace-fail "../../../../usr/share/ags/js" "./ags-js-lib" ''; installPhase = '' @@ -97,6 +112,7 @@ buildNpmPackage (finalAttrs: { ; fetcherVersion = 2; + # Hash updated after local pnpmDeps build hash = "sha256-m/aPNvv26r0DUvRUR4TL2GwwAHKvEIkc8Nvlm/jpnPc="; # fetcher version 2 fails if there are no *-exec files in the output @@ -133,7 +149,8 @@ buildNpmPackage (finalAttrs: { buildPhase = '' runHook preBuild - mkdir build + # Allow incremental or repeated builds: don't fail if ./build already exists + mkdir -p build outPath=./build/${packageJSON.name} ags bundle ./src/app.ts $outPath \ --gtk 4 \ @@ -166,11 +183,29 @@ buildNpmPackage (finalAttrs: { --prefix PATH : ${ lib.makeBinPath [ # runtime executables + pywal16 # provides `wal` for colorshell's wallpaper module dart-sass glib + psmisc socat ] } + --prefix GI_TYPELIB_PATH : "${ + lib.makeSearchPath "lib/girepository-1.0" (with inputs'.astal.packages; [ + astal4 + apps + auth + battery + bluetooth + hyprland + io + mpris + network + notifd + tray + wireplumber + ]) + }" ) ''; @@ -178,3 +213,5 @@ buildNpmPackage (finalAttrs: { resources = colorshellResources; }; }) + + \ No newline at end of file diff --git a/nix/upstream-colorshell.nix b/nix/upstream-colorshell.nix new file mode 100644 index 0000000..d55b2ff --- /dev/null +++ b/nix/upstream-colorshell.nix @@ -0,0 +1,180 @@ +{ + inputs', + lib, + stdenv, + stdenvNoCC, + moreutils, + pnpm_10, + buildNpmPackage, + wrapGAppsHook4, + gobject-introspection, + glib, + gjs, + libadwaita, + dart-sass, + socat, +}: +let + packageJSON = lib.importJSON ../package.json; + pname = packageJSON.name; + version = packageJSON.version; + + # Cleaned sources from this repository + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.difference ../. ( + lib.fileset.unions [ + ../flake.nix + ../flake.lock + ./. + ] + ); + }; + + # Derivation building just the gresources file + colorshellResources = stdenv.mkDerivation { + pname = "${pname}-resources.gresource"; + inherit version; + + inherit src; + + buildInputs = [ + glib + ]; + + buildPhase = '' + runHook preBuild + + glib-compile-resources resources.gresource.xml \ + --sourcedir ./resources \ + --target resources.gresource + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + cp resources.gresource $out + + runHook postInstall + ''; + }; + + # Cleaned sources, with FHS paths patched out. + colorshellSrc = stdenvNoCC.mkDerivation { + pname = "${pname}-src"; + inherit version; + + inherit src; + + # Replace reference to ags FHS install path + postPatch = '' + substituteInPlace package.json pnpm-lock.yaml \ + --replace-fail "/usr/share/ags/js" "${inputs'.ags.packages.ags.jsPackage}" + ''; + + installPhase = '' + mkdir $out + cp -rp * $out + ''; + }; +in +buildNpmPackage (finalAttrs: { + inherit pname version; + + src = colorshellSrc; + sourceRoot = "${finalAttrs.src.name}"; + + npmConfigHook = pnpm_10.configHook; + npmDeps = finalAttrs.pnpmDeps; + pnpmDeps = pnpm_10.fetchDeps { + inherit (finalAttrs) + pname + version + src + sourceRoot + ; + + fetcherVersion = 2; + hash = "sha256-Z5JP7hPEjLY9wGnWe6kM6T1qk3UUSlJnoxdDqS/ksnw="; + + # fetcher version 2 fails if there are no *-exec files in the output + preFixup = '' + touch $out/.dummy-exec + ''; + }; + + nativeBuildInputs = [ + wrapGAppsHook4 + gobject-introspection + inputs'.ags.packages.default + moreutils + ]; + + buildInputs = [ + glib + gjs + libadwaita + inputs'.astal.packages.astal4 + inputs'.astal.packages.apps + inputs'.astal.packages.auth + inputs'.astal.packages.battery + inputs'.astal.packages.bluetooth + inputs'.astal.packages.hyprland + inputs'.astal.packages.io + inputs'.astal.packages.mpris + inputs'.astal.packages.network + inputs'.astal.packages.notifd + inputs'.astal.packages.tray + inputs'.astal.packages.wireplumber + ]; + + buildPhase = '' + runHook preBuild + + mkdir build + outPath=./build/${packageJSON.name} + ags bundle ./src/app.ts $outPath \ + --gtk 4 \ + --root ./src \ + --define "DEVEL=false" \ + --define "COLORSHELL_VERSION='${finalAttrs.version}'" \ + --define "GRESOURCES_FILE='${colorshellResources}'" + + # add socket-communication support on executable + { + head -n1 $outPath + sed '1{/^#!.*$/d}' ${../scripts/socket.sh} + cat "$outPath" | sed '/^#!.*$/d' + } | sponge $outPath + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin + cp -rp build/${packageJSON.name} $out/bin/ + + runHook postInstall + ''; + + preFixup = '' + gappsWrapperArgs+=( + --prefix PATH : ${ + lib.makeBinPath [ + # runtime executables + dart-sass + glib + socat + ] + } + ) + ''; + + passthru = { + resources = colorshellResources; + }; +}) diff --git a/scripts/start.sh b/scripts/start.sh index a463307..97f2c91 100644 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -5,10 +5,19 @@ file="${1:-./build/colorshell}" function start() { if Is_running; then echo "[info] killing previous instance" - colorshell quit || killall gjs + colorshell quit || killall gjs 2>/dev/null || true fi echo "[info] starting" - exec "$file" + + # Always run through nix develop to ensure proper environment variables are set + # This is needed because the manually built executable doesn't have wrapGAppsHook + if command -v nix > /dev/null 2>&1; then + # Use nix develop -c to run with proper environment + # The -c flag runs the command in the devshell environment + exec nix develop -c bash -c "exec \"$file\" \"\$@\"" -- "$@" + else + exec "$file" "$@" + fi } if [[ -f $file ]]; then diff --git a/src/app.ts b/src/app.ts index 8e82e8a..edfd514 100644 --- a/src/app.ts +++ b/src/app.ts @@ -168,21 +168,74 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re private init(): void { // load gresource from build-defined path try { - const gresourcesPath: string = GRESOURCES_FILE.startsWith('/') ? GRESOURCES_FILE : (GRESOURCES_FILE.split('/').filter(s => - s !== "" - ).map(path => { - // support environment variables at runtime - if(/^\$/.test(path)) { - const env = GLib.getenv(path.replace(/^\$/, "")); - if(env === null) - throw new Error(`Couldn't get environment variable: ${path}`); - - return env; + // Handle missing GRESOURCES_FILE define (when running via AGS Home Manager) + if (typeof GRESOURCES_FILE === "undefined" || !GRESOURCES_FILE) { + // Try to find gresource in common locations + // When symlinked via Home Manager, configDir is ~/.config/ags -> ../colorshell/src + // We need to resolve the actual path of the colorshell directory + const configDir = GLib.get_user_config_dir(); + const agsConfigFile = Gio.File.new_for_path(`${configDir}/ags/app.ts`); + let colorshellBuildPath: string | null = null; + + // Try to resolve the symlink to find the actual colorshell directory + // When Home Manager symlinks ~/.config/ags -> ../colorshell/src, + // we need to go up one level from src to find the colorshell root + try { + // Get the real path of app.ts (resolves symlinks) + const appTsFile = Gio.File.new_for_path(`${configDir}/ags/app.ts`); + if (appTsFile.query_exists(null)) { + const realPath = appTsFile.get_path()!; + // realPath will be something like /home/olivier/NixOS-New/colorshell/src/app.ts + // Go up from src/ to get colorshell root, then to build/ + const srcDir = GLib.path_get_dirname(realPath); + const colorshellDir = GLib.path_get_dirname(srcDir); + colorshellBuildPath = `${colorshellDir}/build/resources.gresource`; + } + } catch (e) { + // If symlink resolution fails, try absolute paths } - return path; - }).join('/')); - this.#gresource = Gio.Resource.load(gresourcesPath); - Gio.resources_register(this.#gresource); + + const possiblePaths = [ + colorshellBuildPath, + `${GLib.get_home_dir()}/NixOS-New/colorshell/build/resources.gresource`, // Absolute path (most reliable) + `${configDir}/../colorshell/build/resources.gresource`, // Relative to ~/.config/ags + `${GLib.get_user_config_dir()}/colorshell/build/resources.gresource`, + `${GLib.get_home_dir()}/.config/colorshell/build/resources.gresource`, + ].filter(p => p !== null) as string[]; + + let gresourcesPath: string | null = null; + for (const path of possiblePaths) { + const file = Gio.File.new_for_path(path); + if (file.query_exists(null)) { + gresourcesPath = path; + break; + } + } + + if (!gresourcesPath) { + console.warn("Colorshell: GRESOURCES_FILE not defined and couldn't find gresource file. Icons may not be available."); + return; + } + + this.#gresource = Gio.Resource.load(gresourcesPath); + Gio.resources_register(this.#gresource); + } else { + const gresourcesPath: string = GRESOURCES_FILE.startsWith('/') ? GRESOURCES_FILE : (GRESOURCES_FILE.split('/').filter(s => + s !== "" + ).map(path => { + // support environment variables at runtime + if(/^\$/.test(path)) { + const env = GLib.getenv(path.replace(/^\$/, "")); + if(env === null) + throw new Error(`Couldn't get environment variable: ${path}`); + + return env; + } + return path; + }).join('/')); + this.#gresource = Gio.Resource.load(gresourcesPath); + Gio.resources_register(this.#gresource); + } // add icons Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!)