From b8cbf387fad623d92681c0bd0f8d20f7e680155e Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Fri, 29 May 2026 23:13:40 -0700 Subject: [PATCH] hyprland: expose cacheable package set --- .github/workflows/cachix.yml | 19 +- nixos/flake/hyprland-stuff.nix | 408 +++++++++++++++++++++++++++++++++ nixos/flake/per-system.nix | 39 ++-- nixos/justfile | 10 +- 4 files changed, 450 insertions(+), 26 deletions(-) create mode 100644 nixos/flake/hyprland-stuff.nix diff --git a/.github/workflows/cachix.yml b/.github/workflows/cachix.yml index 38f0338d..de36402d 100644 --- a/.github/workflows/cachix.yml +++ b/.github/workflows/cachix.yml @@ -1,15 +1,23 @@ -name: Build and Push Cachix (imalison-taffybar) +name: Build and Push Cachix on: push: branches: [master] paths: - "dotfiles/config/taffybar/**" + - "dotfiles/config/hypr/**" + - "dotfiles/lib/bin/hypr_*" + - "dotfiles/lib/bin/hypr*" + - "nixos/**" - ".github/workflows/cachix.yml" pull_request: branches: [master] paths: - "dotfiles/config/taffybar/**" + - "dotfiles/config/hypr/**" + - "dotfiles/lib/bin/hypr_*" + - "dotfiles/lib/bin/hypr*" + - "nixos/**" - ".github/workflows/cachix.yml" workflow_dispatch: {} @@ -87,3 +95,12 @@ jobs: --no-link \ --print-build-logs \ ./dotfiles/config/taffybar#defaultPackage.x86_64-linux + + - name: Build Hyprland stuff + run: | + set -euxo pipefail + nix build \ + --no-link \ + --print-build-logs \ + ./nixos#hyprland-stuff \ + --override-input railbird-secrets path:./nixos/ci/railbird-secrets-stub diff --git a/nixos/flake/hyprland-stuff.nix b/nixos/flake/hyprland-stuff.nix new file mode 100644 index 00000000..13795b90 --- /dev/null +++ b/nixos/flake/hyprland-stuff.nix @@ -0,0 +1,408 @@ +{ + pkgs, + inputs, + lib ? pkgs.lib, + hyprlandConfigDir ? ../../dotfiles/config/hypr, +}: let + system = pkgs.stdenv.hostPlatform.system; + hyprlandInput = inputs.hyprland; + hyprlandInputPkgs = import hyprlandInput.inputs.nixpkgs {inherit system;}; + makeHyprlandGlaze = glaze: + (glaze.override { + enableSIMD = false; + }).overrideAttrs (old: { + cmakeFlags = + (old.cmakeFlags or []) + ++ [ + "-Dglaze_DEVELOPER_MODE=OFF" + "-Dglaze_ENABLE_FUZZING=OFF" + "-DBUILD_TESTING=OFF" + ]; + }); + avoidHyprlandGccIce = glaze: package: + (package.override { + stdenv = pkgs.clangStdenv; + }).overrideAttrs (old: { + buildInputs = + map + (input: + if (input.pname or null) == "glaze" + then makeHyprlandGlaze glaze + else input) + (old.buildInputs or []); + }); + avoidOverlayHyprlandGccIce = final: package: + (package.override { + stdenv = final.clangStdenv; + }).overrideAttrs (old: { + buildInputs = + map + (input: + if (input.pname or null) == "glaze" + then makeHyprlandGlaze final.glaze + else input) + (old.buildInputs or []); + }); + hyprlandGccIceOverlay = final: prev: { + glaze = makeHyprlandGlaze prev.glaze; + hyprland = avoidOverlayHyprlandGccIce final prev.hyprland; + hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;}; + hyprland-with-tests = final.hyprland.override {withTests = true;}; + }; + # GCC 15.2 ICEs while compiling Hyprland 0.55 on this pin. Keep the + # Hyprland/plugin pin set intact and build Hyprland itself with Clang. + baseHyprlandPackage = (avoidHyprlandGccIce hyprlandInputPkgs.glaze hyprlandInput.packages.${system}.hyprland).overrideAttrs (_: { + # Clang 21 can segfault in LLVM's Live DEBUG_VALUE analysis while compiling + # ConfigValues.cpp when this build runs in parallel. + enableParallelBuilding = false; + }); + hyprlandPluginsForBase = pkgs.callPackage "${pkgs.path}/pkgs/applications/window-managers/hyprwm/hyprland-plugins" { + hyprland = baseHyprlandPackage; + }; + cleanupStaleGraphicalSession = pkgs.writeShellScript "cleanup-stale-graphical-session" '' + set -u + + # Only clean targets that are plainly stale. If a compositor is still + # running, let the active session own its own shutdown path. + if ${pkgs.procps}/bin/pgrep -u "$(${pkgs.coreutils}/bin/id -u)" -f '(^|/)(Hyprland|\.Hyprland-wrapped|river|kwin_wayland)( |$)' >/dev/null 2>&1; then + exit 0 + fi + + ${pkgs.systemd}/bin/systemctl --user stop \ + hyprland-session.target \ + river-xmonad-session.target \ + graphical-session.target \ + graphical-session-pre.target \ + tray.target \ + 2>/dev/null || true + + ${pkgs.systemd}/bin/systemctl --user unset-environment \ + WAYLAND_DISPLAY \ + DISPLAY \ + XAUTHORITY \ + HYPRLAND_INSTANCE_SIGNATURE \ + XDG_CURRENT_DESKTOP \ + XDG_SESSION_DESKTOP \ + XDG_SESSION_TYPE \ + IMALISON_SESSION_TYPE \ + IMALISON_WINDOW_MANAGER \ + 2>/dev/null || true + + ${pkgs.systemd}/bin/systemctl --user reset-failed 2>/dev/null || true + ''; + makeHyprlandLuaPackage = package: + (pkgs.symlinkJoin { + name = "${package.name}-lua-config"; + inherit (package) version; + paths = [package]; + meta = + builtins.removeAttrs (package.meta or {}) ["outputsToInstall"] + // { + mainProgram = package.meta.mainProgram or "Hyprland"; + }; + passthru = + (package.passthru or {}) + // { + providedSessions = package.passthru.providedSessions or ["hyprland"]; + }; + postBuild = '' + mkdir -p "$out/bin" "$out/share/wayland-sessions" + printf '%s\n' \ + '#!${pkgs.runtimeShell}' \ + 'nvidia_drm_device="/dev/dri/by-path/pci-0000:01:00.0-card"' \ + 'intel_drm_device="/dev/dri/by-path/pci-0000:00:02.0-card"' \ + 'if [ -e "$nvidia_drm_device" ] && [ -e "$intel_drm_device" ]; then' \ + ' export AQ_DRM_DEVICES="$nvidia_drm_device:$intel_drm_device"' \ + 'fi' \ + 'config_path="''${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprland.lua"' \ + 'exec "${package}/bin/start-hyprland" --path "${package}/bin/Hyprland" -- --config "$config_path" "$@"' \ + > "$out/bin/start-hyprland-lua" + chmod +x "$out/bin/start-hyprland-lua" + + printf '%s\n' \ + '#!${pkgs.runtimeShell}' \ + '${cleanupStaleGraphicalSession}' \ + 'exec ${pkgs.uwsm}/bin/uwsm start -g -1 -e -D Hyprland hyprland.desktop' \ + > "$out/bin/start-hyprland-uwsm-clean" + chmod +x "$out/bin/start-hyprland-uwsm-clean" + + rm -f "$out/share/wayland-sessions/hyprland.desktop" + substitute \ + "${package}/share/wayland-sessions/hyprland.desktop" \ + "$out/share/wayland-sessions/hyprland.desktop" \ + --replace-fail \ + "Exec=${package}/bin/start-hyprland" \ + "Exec=$out/bin/start-hyprland-lua" + + rm -f "$out/share/wayland-sessions/hyprland-uwsm.desktop" + substitute \ + "${package}/share/wayland-sessions/hyprland-uwsm.desktop" \ + "$out/share/wayland-sessions/hyprland-uwsm.desktop" \ + --replace-fail \ + "Exec=uwsm start -e -D Hyprland hyprland.desktop" \ + "Exec=$out/bin/start-hyprland-uwsm-clean" + ''; + }) + // { + override = {enableXWayland ? true, ...} @ args: + makeHyprlandLuaPackage (package.override args); + overrideAttrs = f: makeHyprlandLuaPackage (package.overrideAttrs f); + }; + hyprlandPackage = makeHyprlandLuaPackage baseHyprlandPackage; + enableHyprglass = false; + hyprglass = pkgs.callPackage ../packages/hyprglass { + src = inputs.hyprglass; + hyprland = baseHyprlandPackage; + aquamarine = inputs.aquamarine.packages.${system}.aquamarine; + hyprcursor = inputs.hyprcursor.packages.${system}.hyprcursor; + hyprgraphics = inputs.hyprgraphics.packages.${system}.hyprgraphics; + hyprlang = inputs.hyprlang.packages.${system}.hyprlang; + hyprutils = inputs.hyprutils.packages.${system}.hyprutils; + }; + hyprwobbly = (pkgs.callPackage "${inputs.hyprwobbly}/default.nix" {}).overrideAttrs (old: { + patches = + (old.patches or []) + ++ [ + ../packages/hyprwobbly-safe-geometry-and-idle-timer.patch + ]; + }); + hyprexpo = inputs.hyprexpo.packages.${system}.hyprexpo; + hyprwinview = hyprlandPluginsForBase.mkHyprlandPlugin { + pluginName = "hyprwinview"; + version = "0.1.0"; + src = inputs.hyprwinview; + inherit (baseHyprlandPackage) nativeBuildInputs; + buildInputs = [pkgs.librsvg]; + meta = { + description = "A window overview plugin for Hyprland"; + homepage = "https://github.com/colonelpanic8/hyprwinview"; + license = lib.licenses.bsd3; + platforms = lib.platforms.linux; + }; + }; + hyprWorkspaceHistory = hyprlandPluginsForBase.mkHyprlandPlugin { + pluginName = "hypr-workspace-history"; + version = "0.1.0"; + src = inputs.hypr-workspace-history; + inherit (baseHyprlandPackage) nativeBuildInputs; + meta = { + description = "Workspace history cycling plugin for Hyprland"; + homepage = "https://github.com/colonelpanic8/hypr-workspace-history"; + license = lib.licenses.bsd3; + platforms = lib.platforms.linux; + }; + }; + hyprtasking = pkgs.gcc14Stdenv.mkDerivation { + pname = "hyprtasking"; + version = "0.1.0-unstable-${inputs.hyprtasking.shortRev or "dirty"}"; + src = inputs.hyprtasking; + + strictDeps = true; + nativeBuildInputs = + [ + pkgs.pkg-config + pkgs.meson + pkgs.ninja + ] + ++ baseHyprlandPackage.nativeBuildInputs; + buildInputs = [baseHyprlandPackage] ++ baseHyprlandPackage.buildInputs; + + meta = { + description = "Powerful workspace management plugin for Hyprland"; + homepage = "https://github.com/raybbian/hyprtasking"; + license = lib.licenses.bsd3; + platforms = lib.platforms.linux; + }; + }; + hyprDynamicCursors = pkgs.gcc14Stdenv.mkDerivation { + pname = "hypr-dynamic-cursors"; + version = "0-unstable-${inputs.hypr-dynamic-cursors.shortRev or "dirty"}"; + src = inputs.hypr-dynamic-cursors; + + strictDeps = true; + nativeBuildInputs = + baseHyprlandPackage.nativeBuildInputs + ++ [ + baseHyprlandPackage + pkgs.pkg-config + ]; + buildInputs = [(lib.getDev baseHyprlandPackage)] ++ baseHyprlandPackage.buildInputs; + + dontConfigure = true; + dontUseCmakeConfigure = true; + dontUseMesonConfigure = true; + dontUseNinjaBuild = true; + dontUseNinjaInstall = true; + + buildPhase = '' + runHook preBuild + make + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + install -Dm755 out/dynamic-cursors.so "$out/lib/libhypr-dynamic-cursors.so" + runHook postInstall + ''; + + meta = { + description = "A plugin to make Hyprland cursor movement more realistic"; + homepage = "https://github.com/VirtCode/hypr-dynamic-cursors"; + license = lib.licenses.bsd3; + platforms = lib.platforms.linux; + }; + }; + hyprNStack = pkgs.stdenv.mkDerivation { + pname = "hyprNStack"; + version = "0-unstable-${inputs.hyprNStack.shortRev or "dirty"}"; + + src = inputs.hyprNStack; + + strictDeps = true; + nativeBuildInputs = [ + pkgs.pkg-config + ]; + buildInputs = + [ + baseHyprlandPackage + ] + ++ baseHyprlandPackage.buildInputs; + + dontStrip = true; + + installPhase = '' + runHook preInstall + install -Dm755 nstackLayoutPlugin.so "$out/lib/libhyprNStack.so" + runHook postInstall + ''; + + meta = { + description = "N-stack layout plugin for Hyprland"; + homepage = "https://github.com/zakk4223/hyprNStack"; + license = lib.licenses.bsd3; + platforms = baseHyprlandPackage.meta.platforms; + }; + }; + hyprlandPluginPackages = + [ + hyprNStack + hyprexpo + hyprwinview + hyprWorkspaceHistory + hyprtasking + hyprDynamicCursors + hyprwobbly + ] + ++ lib.optionals enableHyprglass [hyprglass]; + llvmNmCompat = pkgs.writeShellApplication { + name = "llvm-nm"; + runtimeInputs = [pkgs.binutils]; + text = '' + exec nm "$@" + ''; + }; + hyprRofiWindow = pkgs.writeShellApplication { + name = "hypr_rofi_window"; + runtimeInputs = [ + pkgs.python3 + pkgs.rofi + hyprlandPackage + ]; + text = '' + exec python3 ${../../dotfiles/lib/bin/hypr_rofi_window} "$@" + ''; + }; + hyprShellUi = pkgs.writeShellApplication { + name = "hypr_shell_ui"; + runtimeInputs = [ + pkgs.rofi + hyprRofiWindow + ]; + text = '' + exec ${../../dotfiles/lib/bin/hypr_shell_ui} "$@" + ''; + }; + hyprRofiLayout = pkgs.writeShellApplication { + name = "hypr_rofi_layout"; + runtimeInputs = [ + pkgs.coreutils + pkgs.findutils + pkgs.gawk + pkgs.rofi + hyprlandPackage + ]; + text = '' + exec ${../../dotfiles/lib/bin/hypr_rofi_layout} "$@" + ''; + }; + hyprRofiAction = pkgs.writeShellApplication { + name = "hypr_rofi_action"; + runtimeInputs = [ + pkgs.libnotify + pkgs.python3 + pkgs.rofi + hyprlandPackage + ]; + text = '' + exec python3 ${../../dotfiles/lib/bin/hypr_rofi_action} "$@" + ''; + }; + hyprlandConfigSyntax = import ../checks/hyprland-config-syntax { + inherit pkgs hyprlandConfigDir; + }; + hyprlandStuffPaths = + [ + hyprlandPackage + llvmNmCompat + hyprRofiLayout + hyprRofiAction + hyprRofiWindow + hyprShellUi + ] + ++ hyprlandPluginPackages; + hyprlandStuffPathStrings = map builtins.toString (hyprlandStuffPaths ++ [hyprlandConfigSyntax]); + hyprlandStuff = pkgs.symlinkJoin { + name = "imalison-hyprland-stuff"; + paths = hyprlandStuffPaths; + postBuild = '' + mkdir -p "$out/nix-support" + printf '%s\n' ${lib.escapeShellArgs hyprlandStuffPathStrings} > "$out/nix-support/hyprland-store-paths" + ''; + passthru = { + inherit + baseHyprlandPackage + hyprlandPackage + hyprlandPluginPackages + hyprlandConfigSyntax + ; + }; + meta = { + description = "Ivan Malison's Hyprland package, plugins, scripts, and Lua config syntax check"; + platforms = lib.platforms.linux; + }; + }; +in { + inherit + baseHyprlandPackage + cleanupStaleGraphicalSession + hyprDynamicCursors + hyprNStack + hyprRofiAction + hyprRofiLayout + hyprRofiWindow + hyprShellUi + hyprWorkspaceHistory + hyprglass + hyprlandConfigSyntax + hyprlandGccIceOverlay + hyprlandPackage + hyprlandPluginPackages + hyprlandStuff + hyprtasking + hyprwinview + hyprwobbly + hyprexpo + llvmNmCompat + ; +} diff --git a/nixos/flake/per-system.nix b/nixos/flake/per-system.nix index ff00ef39..15a9fdae 100644 --- a/nixos/flake/per-system.nix +++ b/nixos/flake/per-system.nix @@ -17,16 +17,9 @@ inherit pkgs system; inherit inputs; }; - hyprglass = pkgs.callPackage ../packages/hyprglass { - src = inputs.hyprglass; - hyprland = inputs.hyprland.packages.${system}.hyprland; - aquamarine = inputs.aquamarine.packages.${system}.aquamarine; - hyprcursor = inputs.hyprcursor.packages.${system}.hyprcursor; - hyprgraphics = inputs.hyprgraphics.packages.${system}.hyprgraphics; - hyprlang = inputs.hyprlang.packages.${system}.hyprlang; - hyprutils = inputs.hyprutils.packages.${system}.hyprutils; + hyprlandStuffPackages = import ./hyprland-stuff.nix { + inherit pkgs inputs lib; }; - hyprexpo = inputs.hyprexpo.packages.${system}.hyprexpo; tangledConfig = dotfilesOrgApi.org-agenda-custom-config; # Import container build logic @@ -42,11 +35,13 @@ in { kat-org-agenda-api = containerLib.containers.kat; } // lib.optionalAttrs pkgs.stdenv.isLinux { - hyprNStack = inputs.hyprNStack.packages.${system}.hyprNStack; - hyprexpo-lua = hyprexpo; - hyprwinview = inputs.hyprwinview.packages.${system}.hyprwinview; - hypr-workspace-history = inputs.hypr-workspace-history.packages.${system}.hypr-workspace-history; - inherit hyprglass; + hyprland-stuff = hyprlandStuffPackages.hyprlandStuff; + hyprland-lua = hyprlandStuffPackages.hyprlandPackage; + hyprNStack = hyprlandStuffPackages.hyprNStack; + hyprexpo-lua = hyprlandStuffPackages.hyprexpo; + hyprwinview = hyprlandStuffPackages.hyprwinview; + hypr-workspace-history = hyprlandStuffPackages.hyprWorkspaceHistory; + hyprglass = hyprlandStuffPackages.hyprglass; }; checks = @@ -60,15 +55,13 @@ in { ''; } // lib.optionalAttrs pkgs.stdenv.isLinux { - hyprNStack = inputs.hyprNStack.packages.${system}.hyprNStack; - hyprexpo-lua = hyprexpo; - hyprwinview = inputs.hyprwinview.packages.${system}.hyprwinview; - hypr-workspace-history = inputs.hypr-workspace-history.packages.${system}.hypr-workspace-history; - inherit hyprglass; - hyprland-config-syntax = import ../checks/hyprland-config-syntax { - inherit pkgs; - hyprlandConfigDir = ../../dotfiles/config/hypr; - }; + hyprNStack = hyprlandStuffPackages.hyprNStack; + hyprexpo-lua = hyprlandStuffPackages.hyprexpo; + hyprwinview = hyprlandStuffPackages.hyprwinview; + hypr-workspace-history = hyprlandStuffPackages.hyprWorkspaceHistory; + hyprglass = hyprlandStuffPackages.hyprglass; + hyprland-config-syntax = hyprlandStuffPackages.hyprlandConfigSyntax; + hyprland-stuff = hyprlandStuffPackages.hyprlandStuff; # Hyprland 0.54 currently segfaults in --verify-config before it can # validate even an empty config in this environment. Keep coverage in # hyprland-config-syntax until the upstream verifier is usable again. diff --git a/nixos/justfile b/nixos/justfile index 6f9a894b..8e76a321 100644 --- a/nixos/justfile +++ b/nixos/justfile @@ -1,6 +1,12 @@ +switch-substituters := "https://cache.nixos.org https://cache.nixos-cuda.org https://cuda-maintainers.cachix.org https://ai.cachix.org https://nix-community.cachix.org https://numtide.cachix.org https://colonelpanic8-dotfiles.cachix.org https://taffybar.cachix.org https://codex-cli.cachix.org https://claude-code.cachix.org" +switch-trusted-public-keys := "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cache.nixos-cuda.org:74DUi4Ye579gUqzH4ziL9IyiJBlDpMRn9MBN8oNan9M= cuda-maintainers.cachix.org-1:0dq3bujKpuEPMCX6U4WylrUDZ9JyUG0VpVZa7CNfq5E= ai.cachix.org-1:N9dzRK+alWwoKXQlnn0H6aUx0lU/mspIoz8hMvGvbbc= nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs= numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE= colonelpanic8-dotfiles.cachix.org-1:O6GF3nptpeMFapX29okzO92eSWXR36zqW6ZF2C8P0eQ= taffybar.cachix.org-1:beZotJ1nVEsAnJxa3lWn0zwzZM7oeXmGh4ADRpHeeIo= codex-cli.cachix.org-1:1Br3H1hHoRYG22n//cGKJOk3cQXgYobUel6O8DgSing= claude-code.cachix.org-1:YeXf2aNu7UTX8Vwrze0za1WEDS+4DuI2kVeWEE4fsRk=" + switch *args: case "$(systemctl show -P ActiveState nixos-upgrade.service 2>/dev/null)" in active|activating|reloading) /run/wrappers/bin/sudo systemctl stop nixos-upgrade.service;; esac - /run/wrappers/bin/sudo nixos-rebuild switch --flake ".#" --impure {{args}} + /run/wrappers/bin/sudo nixos-rebuild switch --flake ".#" --impure --option substituters '{{switch-substituters}}' --option trusted-public-keys '{{switch-trusted-public-keys}}' {{args}} populate-cachix cache="colonelpanic8-dotfiles" host=`hostname` *args: - store_path="$(nix build --no-link --print-out-paths ".#nixosConfigurations.{{host}}.config.system.build.toplevel" --impure --option warn-dirty false {{args}})" && cachix push {{cache}} "$store_path + store_path="$(nix build --no-link --print-out-paths ".#nixosConfigurations.{{host}}.config.system.build.toplevel" --impure --option warn-dirty false {{args}})" && cachix push {{cache}} "$store_path" + +publish-hyprland-cachix cache="colonelpanic8-dotfiles" *args: + store_paths="$(nix build --no-link --print-out-paths ".#hyprland-stuff" --option warn-dirty false {{args}})" && cachix push {{cache}} $store_paths