{ description = "nova-shell - minimal Quickshell bar for niri"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; quickshell = { url = "git+https://git.outfoxxed.me/outfoxxed/quickshell"; inputs.nixpkgs.follows = "nixpkgs"; }; treefmt-nix = { url = "github:numtide/treefmt-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs, quickshell, treefmt-nix, ... }: let systems = [ "x86_64-linux" "aarch64-linux" ]; treefmt-config = { projectRootFile = "flake.nix"; programs.nixfmt.enable = true; programs.qmlformat.enable = true; programs.rustfmt.enable = true; programs.taplo.enable = true; programs.keep-sorted.enable = true; }; # Patch qtbase wayland client to fix use-after-free in QWaylandSurface::oldestEnteredScreen. # Waiting screens deleted via registry_global_remove leave dangling pointers in surface # m_screens lists, crashing on the next screensChanged signal. Track surfaces in the # display and scrub references before any screen deletion. # TODO: remove once fixed upstream in Qt qtbaseOverlay = final: prev: { qt6 = prev.qt6.overrideScope ( qtFinal: qtPrev: { qtbase = qtPrev.qtbase.overrideAttrs (old: { patches = (old.patches or [ ]) ++ [ ./nix/patches/qtbase-wayland-screen-uaf.patch ]; }); } ); }; forAllSystems = fn: nixpkgs.lib.genAttrs systems ( system: fn rec { rawPkgs = nixpkgs.legacyPackages.${system}; pkgs = rawPkgs.extend qtbaseOverlay; treefmt-eval = treefmt-nix.lib.evalModule rawPkgs treefmt-config; } ); in { formatter = forAllSystems ({ treefmt-eval, ... }: treefmt-eval.config.build.wrapper); packages = forAllSystems ( { pkgs, rawPkgs, ... }: let # Rebuild quickshell against patched Qt via its overlay qs = (pkgs.extend quickshell.overlays.default).quickshell.override { withX11 = false; withI3 = false; }; nova-stats = pkgs.callPackage ./nix/stats-daemon.nix { }; nova-shaders = pkgs.callPackage ./nix/shaders.nix { }; in rec { inherit nova-stats nova-shaders; nova-shell = pkgs.callPackage ./nix/package.nix { quickshell = qs; inherit nova-stats nova-shaders; }; nova-shell-cli = pkgs.runCommand "nova-shell-cli" { nativeBuildInputs = [ pkgs.makeWrapper ]; } '' mkdir -p $out/bin makeWrapper ${qs}/bin/quickshell $out/bin/nova-shell \ --add-flags "-p ${nova-shell}/share/nova-shell/shell.qml" ''; docs = pkgs.callPackage ./nix/docs.nix { inherit self; }; default = nova-shell; # nova-shell on unpatched Qt for A/B crash testing nova-shell-unpatched = let qsUnpatched = (rawPkgs.extend quickshell.overlays.default).quickshell.override { withX11 = false; withI3 = false; }; in rawPkgs.callPackage ./nix/package.nix { quickshell = qsUnpatched; nova-stats = rawPkgs.callPackage ./nix/stats-daemon.nix { }; nova-shaders = rawPkgs.callPackage ./nix/shaders.nix { }; }; } ); devShells = forAllSystems ( { rawPkgs, ... }: { default = rawPkgs.mkShell { packages = with rawPkgs; [ cargo rustc rust-analyzer clippy rustfmt libnotify qt6.qtdeclarative ]; }; } ); checks = forAllSystems ( { pkgs, rawPkgs, treefmt-eval, }: { formatting = treefmt-eval.config.build.check self; build = self.packages.${pkgs.stdenv.hostPlatform.system}.default; nova-stats = self.packages.${pkgs.stdenv.hostPlatform.system}.nova-stats; docs = self.packages.${pkgs.stdenv.hostPlatform.system}.docs; qmllint = rawPkgs.runCommand "nova-shell-qmllint" { nativeBuildInputs = [ rawPkgs.qt6.qtdeclarative ]; src = self; } '' cd $src export QML_IMPORT_PATH="${rawPkgs.qt6.qtdeclarative}/lib/qt-6/qml:${ quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default }/lib/qt-6/qml" qmllint -E \ -I shell/modules -I shell/services -I shell/applets -I shell/lock \ shell/shell.qml shell/modules/*.qml shell/services/*.qml \ shell/applets/*.qml shell/lock/*.qml \ > $TMPDIR/raw.txt 2>&1 || true # Extract unique warning messages (file:message, without line numbers) grep -E "^Warning:" $TMPDIR/raw.txt \ | sed 's/^Warning: //' \ | sed 's/\([^:]*\):[0-9]*:[0-9]*: /\1: /' \ | sort -u > $TMPDIR/current.txt # Compare against baseline touch $TMPDIR/new-warnings.txt $TMPDIR/stale-warnings.txt while IFS= read -r line; do if ! grep -qFx "$line" test/qmllint-baseline.txt 2>/dev/null; then echo "$line" >> $TMPDIR/new-warnings.txt fi done < $TMPDIR/current.txt while IFS= read -r line; do if ! grep -qFx "$line" $TMPDIR/current.txt; then echo "$line" >> $TMPDIR/stale-warnings.txt fi done < test/qmllint-baseline.txt # Output dir with individual files for inspection mkdir -p $out cp $TMPDIR/raw.txt $out/raw.txt cp $TMPDIR/current.txt $out/current.txt cp test/qmllint-baseline.txt $out/baseline.txt cp $TMPDIR/new-warnings.txt $out/new-warnings.txt cp $TMPDIR/stale-warnings.txt $out/stale-warnings.txt failed=0 if [ -s $TMPDIR/new-warnings.txt ]; then echo "new warnings:" sed 's/^/ /' $TMPDIR/new-warnings.txt failed=1 fi if [ -s $TMPDIR/stale-warnings.txt ]; then echo "stale warnings:" sed 's/^/ /' $TMPDIR/stale-warnings.txt failed=1 fi [ "$failed" -eq 0 ] || exit 1 ''; nova-stats-clippy = (pkgs.callPackage ./nix/stats-daemon.nix { }).overrideAttrs (old: { pname = "nova-stats-clippy"; nativeBuildInputs = old.nativeBuildInputs ++ [ pkgs.clippy ]; buildPhase = "cargo clippy --all-targets -- -D warnings"; installPhase = "touch $out"; doCheck = false; }); } ); homeModules.default = import ./nix/hm-module.nix self; homeManagerModules.default = self.homeModules.default; }; }