From 8ccf5fb7de5b6e8c6859df491ce499c216a5816c Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Wed, 29 Apr 2026 13:33:39 -0700 Subject: [PATCH] hyprland: add noctalia shell module --- nixos/configuration.nix | 1 + nixos/flake.lock | 84 ++++++++++++++++++++++++++++++++++++++- nixos/flake.nix | 7 ++++ nixos/hyprland.nix | 6 ++- nixos/noctalia.nix | 87 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 nixos/noctalia.nix diff --git a/nixos/configuration.nix b/nixos/configuration.nix index aa6c474b..36b92817 100644 --- a/nixos/configuration.nix +++ b/nixos/configuration.nix @@ -29,6 +29,7 @@ ./laptop.nix ./nix.nix ./notifications-tray-icon.nix + ./noctalia.nix ./nvidia.nix ./options.nix ./plasma.nix diff --git a/nixos/flake.lock b/nixos/flake.lock index 30c985b9..6576b438 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -1919,6 +1919,50 @@ "type": "github" } }, + "noctalia": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "noctalia-qs": "noctalia-qs" + }, + "locked": { + "lastModified": 1777427472, + "narHash": "sha256-kqcfLdxb+CqTroMErCScvx6YQcZYJcf6X+z5I8kBJK8=", + "owner": "noctalia-dev", + "repo": "noctalia-shell", + "rev": "9f8dd48c8df5ab1f7f87ddf9842627e1e5682186", + "type": "github" + }, + "original": { + "owner": "noctalia-dev", + "repo": "noctalia-shell", + "type": "github" + } + }, + "noctalia-qs": { + "inputs": { + "nixpkgs": [ + "noctalia", + "nixpkgs" + ], + "systems": "systems_4", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1777380063, + "narHash": "sha256-q5mWOEICcZzr+KnjIwDHV9EXiBxOC9cnBpxZbDAViU8=", + "owner": "noctalia-dev", + "repo": "noctalia-qs", + "rev": "8742a7a748c43bf44eb6862a8ebd3591ed71502d", + "type": "github" + }, + "original": { + "owner": "noctalia-dev", + "repo": "noctalia-qs", + "type": "github" + } + }, "notifications-tray-icon": { "inputs": { "flake-utils": [ @@ -2184,10 +2228,11 @@ "nixos-wsl": "nixos-wsl", "nixpkgs": "nixpkgs_6", "nixtheplanet": "nixtheplanet", + "noctalia": "noctalia", "notifications-tray-icon": "notifications-tray-icon", "org-agenda-api": "org-agenda-api", "railbird-secrets": "railbird-secrets", - "systems": "systems_4", + "systems": "systems_5", "vscode-server": "vscode-server", "xmonad": "xmonad_2", "xmonad-contrib": "xmonad-contrib_2" @@ -2293,6 +2338,21 @@ } }, "systems_4": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + }, + "systems_5": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -2329,6 +2389,28 @@ "url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar" } }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "noctalia", + "noctalia-qs", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775636079, + "narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, "vscode-server": { "inputs": { "flake-utils": [ diff --git a/nixos/flake.nix b/nixos/flake.nix index d95e060e..48367d1f 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -231,6 +231,11 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + noctalia = { + url = "github:noctalia-dev/noctalia-shell"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; outputs = inputs @ { @@ -443,6 +448,7 @@ "https://colonelpanic8-dotfiles.cachix.org" "https://codex-cli.cachix.org" "https://claude-code.cachix.org" + "https://noctalia.cachix.org" ]; extra-trusted-substituters = [ "https://ai.cachix.org" @@ -464,6 +470,7 @@ "colonelpanic8-dotfiles.cachix.org-1:O6GF3nptpeMFapX29okzO92eSWXR36zqW6ZF2C8P0eQ=" "codex-cli.cachix.org-1:1Br3H1hHoRYG22n//cGKJOk3cQXgYobUel6O8DgSing=" "claude-code.cachix.org-1:YeXf2aNu7UTX8Vwrze0za1WEDS+4DuI2kVeWEE4fsRk=" + "noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4=" ]; }; nixosConfigurations = diff --git a/nixos/hyprland.nix b/nixos/hyprland.nix index cac2e782..6e8d5670 100644 --- a/nixos/hyprland.nix +++ b/nixos/hyprland.nix @@ -34,6 +34,7 @@ let runtimeInputs = [ pkgs.rofi hyprRofiWindow + inputs.noctalia.packages.${system}.default ]; text = '' exec ${../dotfiles/lib/bin/hypr_shell_ui} "$@" @@ -94,7 +95,10 @@ let }; }; enabledModule = makeEnable config "myModules.hyprland" true { - myModules.taffybar.enable = true; + # Install both shell service units so `desktop_shell_ui set ...` can switch + # between them at runtime without a NixOS rebuild. + myModules.noctalia.enable = lib.mkDefault true; + myModules.taffybar.enable = lib.mkDefault true; # Needed for hyprlock authentication without PAM fallback warnings. security.pam.services.hyprlock = { }; diff --git a/nixos/noctalia.nix b/nixos/noctalia.nix new file mode 100644 index 00000000..3dd3de53 --- /dev/null +++ b/nixos/noctalia.nix @@ -0,0 +1,87 @@ +{ + config, + inputs, + lib, + makeEnable, + pkgs, + ... +}: +let + system = pkgs.stdenv.hostPlatform.system; + noctaliaPackage = inputs.noctalia.packages.${system}.default; + waitForWayland = pkgs.writeShellScript "noctalia-wait-for-wayland" '' + runtime_dir="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}" + + for _ in $(${pkgs.coreutils}/bin/seq 1 50); do + if [ -n "''${WAYLAND_DISPLAY:-}" ] && [ -S "$runtime_dir/$WAYLAND_DISPLAY" ]; then + exit 0 + fi + ${pkgs.coreutils}/bin/sleep 0.1 + done + + echo "Wayland socket not ready: WAYLAND_DISPLAY=''${WAYLAND_DISPLAY:-} XDG_RUNTIME_DIR=$runtime_dir" >&2 + exit 1 + ''; +in +makeEnable config "myModules.noctalia" false { + environment.systemPackages = [ + noctaliaPackage + ]; + + # Noctalia's battery widget talks to UPower. Hosts that deliberately do not + # have batteries can still override this back to false. + services.upower.enable = lib.mkDefault true; + + home-manager.sharedModules = [ + inputs.noctalia.homeModules.default + ({ lib, ... }: { + programs.noctalia-shell = { + enable = true; + # This module provides the Hyprland-scoped service below. + systemd.enable = false; + }; + + home.activation.noctaliaLauncherOverviewLayer = + lib.hm.dag.entryAfter [ "writeBoundary" ] '' + settings_file="$HOME/.config/noctalia/settings.json" + settings_tmp="$(${pkgs.coreutils}/bin/mktemp)" + + ${pkgs.coreutils}/bin/mkdir -p "$(${pkgs.coreutils}/bin/dirname "$settings_file")" + + if [ -e "$settings_file" ] && ${lib.getExe pkgs.jq} -e . "$settings_file" >/dev/null 2>&1; then + ${lib.getExe pkgs.jq} \ + '.appLauncher = (.appLauncher // {}) | .appLauncher.overviewLayer = true' \ + "$settings_file" > "$settings_tmp" + ${pkgs.coreutils}/bin/mv "$settings_tmp" "$settings_file" + else + ${pkgs.coreutils}/bin/printf '%s\n' \ + '{' \ + ' "appLauncher": {' \ + ' "overviewLayer": true' \ + ' }' \ + '}' > "$settings_file" + ${pkgs.coreutils}/bin/rm -f "$settings_tmp" + fi + ''; + + systemd.user.services.noctalia-shell = { + Unit = { + Description = "Noctalia Shell"; + Documentation = "https://docs.noctalia.dev"; + PartOf = [ "hyprland-session.target" ]; + After = [ "hyprland-session.target" ]; + }; + + Service = { + ExecCondition = "/run/current-system/sw/bin/desktop_shell_ui exec-condition noctalia"; + ExecStartPre = "${waitForWayland}"; + ExecStart = "${lib.getExe noctaliaPackage} --no-duplicate"; + Restart = "on-failure"; + RestartSec = 1; + }; + + Install.WantedBy = [ "hyprland-session.target" ]; + }; + }) + ]; +}