From ae79bb7fadcf2610054e91f4ec8d171c0de666df Mon Sep 17 00:00:00 2001 From: Drake Bott Date: Sat, 4 Jul 2026 20:08:45 -0500 Subject: [PATCH 1/8] update --- flake.lock | 152 +++++++++++++--- flake.nix | 8 +- home/linux/default.nix | 8 +- home/linux/niri/default.nix | 278 +++++++++++++++++++++++++++++ home/linux/niri/host/desktop.nix | 20 +++ home/linux/niri/host/pocket.nix | 14 ++ home/linux/plasma/default.nix | 70 -------- home/linux/plasma/host/desktop.nix | 7 - home/linux/plasma/host/eink.nix | 7 - home/linux/plasma/host/pocket.nix | 7 - hosts/pocket/configuration.nix | 5 +- lib/default.nix | 2 +- outputs/hosts.nix | 2 +- system/nixOS/bluetooth.nix | 6 +- system/nixOS/default.nix | 6 +- system/nixOS/greetd.nix | 6 +- system/nixOS/niri/default.nix | 19 ++ system/nixOS/plasma/default.nix | 27 --- 18 files changed, 476 insertions(+), 168 deletions(-) create mode 100644 home/linux/niri/default.nix create mode 100644 home/linux/niri/host/desktop.nix create mode 100644 home/linux/niri/host/pocket.nix delete mode 100644 home/linux/plasma/default.nix delete mode 100644 home/linux/plasma/host/desktop.nix delete mode 100644 home/linux/plasma/host/eink.nix delete mode 100644 home/linux/plasma/host/pocket.nix create mode 100644 system/nixOS/niri/default.nix delete mode 100644 system/nixOS/plasma/default.nix diff --git a/flake.lock b/flake.lock index 703b10cf..47b87425 100644 --- a/flake.lock +++ b/flake.lock @@ -375,6 +375,62 @@ "type": "github" } }, + "niri": { + "inputs": { + "niri-stable": "niri-stable", + "niri-unstable": "niri-unstable", + "nixpkgs": "nixpkgs_6", + "nixpkgs-stable": "nixpkgs-stable", + "xwayland-satellite-stable": "xwayland-satellite-stable", + "xwayland-satellite-unstable": "xwayland-satellite-unstable" + }, + "locked": { + "lastModified": 1783123561, + "narHash": "sha256-A4XJC5qR19bBJ3KO3oE1cK4e98bPrO5QYcSNPVZuUas=", + "owner": "sodiboo", + "repo": "niri-flake", + "rev": "62b5feade147800f4e49503ae6fb643892689d65", + "type": "github" + }, + "original": { + "owner": "sodiboo", + "repo": "niri-flake", + "type": "github" + } + }, + "niri-stable": { + "flake": false, + "locked": { + "lastModified": 1756556321, + "narHash": "sha256-RLD89dfjN0RVO86C/Mot0T7aduCygPGaYbog566F0Qo=", + "owner": "YaLTeR", + "repo": "niri", + "rev": "01be0e65f4eb91a9cd624ac0b76aaeab765c7294", + "type": "github" + }, + "original": { + "owner": "YaLTeR", + "ref": "v25.08", + "repo": "niri", + "type": "github" + } + }, + "niri-unstable": { + "flake": false, + "locked": { + "lastModified": 1781781064, + "narHash": "sha256-Ii/koEm/sRyg65qbAQWqEgboSEIhdH0EL4KglAc14p0=", + "owner": "YaLTeR", + "repo": "niri", + "rev": "49fc6117fd6c043adaa2ead316b82db5ed735d36", + "type": "github" + }, + "original": { + "owner": "YaLTeR", + "repo": "niri", + "type": "github" + } + }, "nix-darwin": { "inputs": { "nixpkgs": [ @@ -439,7 +495,7 @@ }, "nixos-hardware": { "inputs": { - "nixpkgs": "nixpkgs_6" + "nixpkgs": "nixpkgs_7" }, "locked": { "lastModified": 1782562157, @@ -487,6 +543,22 @@ "type": "github" } }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1782847189, + "narHash": "sha256-twXPFqFsrrY5r28Zh7Homgcp2gUMBgQ6WDS98Q/3xFI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b6018f87da91d19d0ab4cf979885689b469cdd41", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs-unstable": { "locked": { "lastModified": 1782948114, @@ -568,6 +640,22 @@ } }, "nixpkgs_6": { + "locked": { + "lastModified": 1782959384, + "narHash": "sha256-xnJJk+ct+D2+wdRxj1wk36w5zV9RVESwRqcklPdt3fM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "65179426c83bb3f6bc14898b42ea1c6f01d374b0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_7": { "locked": { "lastModified": 1767892417, "narHash": "sha256-8bW3q88CEg2u4hSP66Vf4lpbLonHz7hqDNBMcCY7E9U=", @@ -580,7 +668,7 @@ "url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz" } }, - "nixpkgs_7": { + "nixpkgs_8": { "locked": { "lastModified": 1782847225, "narHash": "sha256-JC9PjqKYG9ve5U8aDOLQipp3+KLANBHUvGdLZlxzdKI=", @@ -621,29 +709,6 @@ "type": "github" } }, - "plasma-manager": { - "inputs": { - "home-manager": [ - "home-manager" - ], - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1775856943, - "narHash": "sha256-b7Mp7P+q2Md5AGt4rjHfMcBykzMumFTen10ST++AuTU=", - "owner": "nix-community", - "repo": "plasma-manager", - "rev": "a524a6160e6df89f7673ba293cf7d78b559eb1a5", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "plasma-manager", - "type": "github" - } - }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat_2", @@ -672,13 +737,13 @@ "glide": "glide", "home-manager": "home-manager", "mac-app-util": "mac-app-util", + "niri": "niri", "nix-darwin": "nix-darwin", "nix-tresorit": "nix-tresorit", "nixos-avf": "nixos-avf", "nixos-hardware": "nixos-hardware", - "nixpkgs": "nixpkgs_7", + "nixpkgs": "nixpkgs_8", "nixpkgs-unstable": "nixpkgs-unstable", - "plasma-manager": "plasma-manager", "pre-commit-hooks": "pre-commit-hooks", "spicetify-nix": "spicetify-nix", "steam-config-nix": "steam-config-nix", @@ -972,6 +1037,39 @@ "repo": "treefmt-nix", "type": "github" } + }, + "xwayland-satellite-stable": { + "flake": false, + "locked": { + "lastModified": 1755491097, + "narHash": "sha256-m+9tUfsmBeF2Gn4HWa6vSITZ4Gz1eA1F5Kh62B0N4oE=", + "owner": "Supreeeme", + "repo": "xwayland-satellite", + "rev": "388d291e82ffbc73be18169d39470f340707edaa", + "type": "github" + }, + "original": { + "owner": "Supreeeme", + "ref": "v0.7", + "repo": "xwayland-satellite", + "type": "github" + } + }, + "xwayland-satellite-unstable": { + "flake": false, + "locked": { + "lastModified": 1781226823, + "narHash": "sha256-28696iIw8uE0ZUyFTtzhEM8xMh85clCYypMxkvUi+sc=", + "owner": "Supreeeme", + "repo": "xwayland-satellite", + "rev": "8575d0ef55d70f9b4c46b6bffb3accf912217e1e", + "type": "github" + }, + "original": { + "owner": "Supreeeme", + "repo": "xwayland-satellite", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 5fde4eed..e87debd0 100644 --- a/flake.nix +++ b/flake.nix @@ -37,11 +37,6 @@ url = "github:different-name/steam-config-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; - plasma-manager = { - url = "github:nix-community/plasma-manager"; - inputs.nixpkgs.follows = "nixpkgs"; - inputs.home-manager.follows = "home-manager"; - }; nixos-hardware.url = "github:NixOS/nixos-hardware/master"; nixos-avf = { url = "github:nix-community/nixos-avf"; @@ -56,6 +51,9 @@ inputs.nixpkgs.follows = "nixpkgs"; inputs.home-manager.follows = "home-manager"; }; + # niri compositor: NixOS + home-manager modules with build-time config + # validation. Keeps its own nixpkgs pin so its binary cache stays hit. + niri.url = "github:sodiboo/niri-flake"; mac-app-util = { # Pinned to nixos-25.11 (sbcl 2.5.10) instead of following our nixos-26.05: # 26.05's sbcl 2.6.4 breaks cl-nix-lite's fare-quasiquote build diff --git a/home/linux/default.nix b/home/linux/default.nix index b7fed5c6..3c4a8724 100644 --- a/home/linux/default.nix +++ b/home/linux/default.nix @@ -1,4 +1,4 @@ -{ features, hostName, lib, ... }: +{ features, lib, ... }: { imports = [ ./theme.nix @@ -7,11 +7,9 @@ ./mpv ] ++ lib.optionals features.gaming [ ./games - ] ++ lib.optionals (features.desktopEnvironment == "plasma") [ - ./plasma ] ++ lib.optionals (features.desktopEnvironment == "sway") [ ./sway - ] ++ lib.optionals (features.desktopEnvironment != null && features.desktopEnvironment != "sway") [ - ./${features.desktopEnvironment}/host/${hostName}.nix + ] ++ lib.optionals (features.desktopEnvironment == "niri") [ + ./niri ]; } diff --git a/home/linux/niri/default.nix b/home/linux/niri/default.nix new file mode 100644 index 00000000..4a464067 --- /dev/null +++ b/home/linux/niri/default.nix @@ -0,0 +1,278 @@ +{ config, lib, pkgs, hostName, ... }: +let + c = config.lib.stylix.colors; # base16 hex, no leading '#' + font = config.stylix.fonts.monospace.name; + isPocket = hostName == "pocket"; + + # Per-host `output` blocks (a plain KDL string), spliced into config.kdl. + niriOutputs = import ./host/${hostName}.nix; + + # Right-hand modules: laptop-only extras (backlight, battery) folded in. + rightModules = + [ "tray" "pulseaudio" "network" ] + ++ lib.optionals isPocket [ "backlight" "battery" ] + ++ [ "clock" ]; +in +{ + # `programs.niri` here comes from niri-flake's NixOS module, which injects its + # home-manager module automatically (via home-manager.sharedModules) — so we + # must NOT import homeModules.niri again. It validates the KDL at build time. + + home.packages = with pkgs; [ + swaylock + brightnessctl + playerctl + pavucontrol + networkmanagerapplet # nm-connection-editor for the bar's on-click + xwayland-satellite # X11 apps under niri + ]; + + programs = { + # ---- niri compositor config --------------------------------------- + # Raw KDL (validated at build time by niri-flake). We keep hand-written + # KDL rather than the typed `settings` DSL — it's one readable file. + niri.config = '' + // Managed by home-manager (home/linux/niri). Edit the Nix file, not this. + // niri reloads this file live on save — no restart needed. + + input { + keyboard { + xkb { layout "us"; } + } + touchpad { + tap + natural-scroll + } + focus-follows-mouse max-scroll-amount="0%" + warp-mouse-to-focus + } + + // ---- Monitors (per-host) ---- + ${niriOutputs} + + layout { + gaps 8 + center-focused-column "never" + preset-column-widths { + proportion 0.33333 + proportion 0.5 + proportion 0.66667 + } + default-column-width { proportion 0.5; } + focus-ring { + width 2 + active-color "#${c.base0D}" + inactive-color "#${c.base02}" + } + border { off; } + } + + prefer-no-csd + screenshot-path "~/Pictures/Screenshots/Screenshot-%Y-%m-%d-%H-%M-%S.png" + + hotkey-overlay { skip-at-startup; } + + // X11 apps: xwayland-satellite serves display :0, and niri exports + // DISPLAY so children (and systemd/dbus) can reach it. + environment { + DISPLAY ":0" + } + spawn-at-startup "xwayland-satellite" ":0" + + window-rule { + geometry-corner-radius 6 + clip-to-geometry true + } + + binds { + Mod+Shift+Slash { show-hotkey-overlay; } + + Mod+Return { spawn "ghostty"; } + Mod+D { spawn "fuzzel"; } + Mod+Q { close-window; } + Mod+Alt+L { spawn "swaylock"; } + + // Focus (vim keys, matching the old sway setup) + Mod+H { focus-column-left; } + Mod+L { focus-column-right; } + Mod+J { focus-window-down; } + Mod+K { focus-window-up; } + + // Move + Mod+Shift+H { move-column-left; } + Mod+Shift+L { move-column-right; } + Mod+Shift+J { move-window-down; } + Mod+Shift+K { move-window-up; } + + // Monitors (desktop is dual-head) + Mod+Ctrl+H { focus-monitor-left; } + Mod+Ctrl+L { focus-monitor-right; } + Mod+Shift+Ctrl+H { move-column-to-monitor-left; } + Mod+Shift+Ctrl+L { move-column-to-monitor-right; } + + Mod+Home { focus-column-first; } + Mod+End { focus-column-last; } + + // Workspaces + Mod+1 { focus-workspace 1; } + Mod+2 { focus-workspace 2; } + Mod+3 { focus-workspace 3; } + Mod+4 { focus-workspace 4; } + Mod+5 { focus-workspace 5; } + Mod+6 { focus-workspace 6; } + Mod+7 { focus-workspace 7; } + Mod+8 { focus-workspace 8; } + Mod+9 { focus-workspace 9; } + Mod+Shift+1 { move-column-to-workspace 1; } + Mod+Shift+2 { move-column-to-workspace 2; } + Mod+Shift+3 { move-column-to-workspace 3; } + Mod+Shift+4 { move-column-to-workspace 4; } + Mod+Shift+5 { move-column-to-workspace 5; } + Mod+Shift+6 { move-column-to-workspace 6; } + Mod+Shift+7 { move-column-to-workspace 7; } + Mod+Shift+8 { move-column-to-workspace 8; } + Mod+Shift+9 { move-column-to-workspace 9; } + Mod+U { focus-workspace-down; } + Mod+I { focus-workspace-up; } + + // Sizing / layout + Mod+F { maximize-column; } + Mod+Shift+F { fullscreen-window; } + Mod+C { center-column; } + Mod+R { switch-preset-column-width; } + Mod+Minus { set-column-width "-10%"; } + Mod+Equal { set-column-width "+10%"; } + Mod+Comma { consume-window-into-column; } + Mod+Period { expel-window-from-column; } + Mod+Space { switch-focus-between-floating-and-tiling; } + Mod+Shift+Space { toggle-window-floating; } + + // Screenshots (niri built-in) + Print { screenshot; } + Mod+Print { screenshot-screen; } + Mod+Shift+Print { screenshot-window; } + + // Media / volume / brightness + XF86AudioRaiseVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05+"; } + XF86AudioLowerVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05-"; } + XF86AudioMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; } + XF86AudioMicMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"; } + XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "set" "5%+"; } + XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "set" "5%-"; } + XF86AudioPlay allow-when-locked=true { spawn "playerctl" "play-pause"; } + XF86AudioNext allow-when-locked=true { spawn "playerctl" "next"; } + XF86AudioPrev allow-when-locked=true { spawn "playerctl" "previous"; } + + Mod+Shift+E { quit; } + Mod+Shift+P { power-off-monitors; } + } + ''; + + # ---- Waybar: KDE-style bottom panel ------------------------------- + waybar = { + enable = true; + systemd.enable = true; + settings.main = { + layer = "top"; + position = "bottom"; + height = 32; + spacing = 6; + modules-left = [ "niri/workspaces" ]; + modules-center = [ "niri/window" ]; + modules-right = rightModules; + + "niri/workspaces".format = "{index}"; + "niri/window" = { + format = "{}"; + max-length = 80; + }; + clock = { + format = "{:%a %d %b %H:%M}"; + tooltip-format = "{:%Y %B}\n{calendar}"; + }; + pulseaudio = { + format = "{icon} {volume}%"; + format-muted = "󰝟 muted"; + format-icons.default = [ "󰕿" "󰖀" "󰕾" ]; + on-click = "pavucontrol"; + }; + network = { + format-wifi = "󰤨 {essid}"; + format-ethernet = "󰈀 {ifname}"; + format-disconnected = "󰤭 off"; + tooltip-format = "{ifname}: {ipaddr}"; + on-click = "nm-connection-editor"; + }; + tray = { + icon-size = 16; + spacing = 8; + }; + battery = { + format = "{icon} {capacity}%"; + format-charging = "󰂄 {capacity}%"; + format-icons = [ "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹" ]; + }; + backlight = { + format = "󰃟 {percent}%"; + on-scroll-up = "brightnessctl set 5%+"; + on-scroll-down = "brightnessctl set 5%-"; + }; + }; + style = '' + * { + font-family: "${font}"; + font-size: 13px; + min-height: 0; + } + window#waybar { + background: #${c.base00}; + color: #${c.base05}; + } + #workspaces button { + padding: 0 8px; + color: #${c.base04}; + background: transparent; + } + #workspaces button.active { + color: #${c.base00}; + background: #${c.base0D}; + border-radius: 4px; + } + #workspaces button.urgent { + color: #${c.base00}; + background: #${c.base08}; + border-radius: 4px; + } + #window { color: #${c.base04}; } + #clock, #pulseaudio, #network, #battery, #backlight, #tray { + padding: 0 10px; + } + #battery.charging { color: #${c.base0B}; } + #battery.critical:not(.charging) { color: #${c.base08}; } + #network.disconnected { color: #${c.base08}; } + ''; + }; + + fuzzel.enable = true; + }; + # We own the waybar CSS; keep stylix from also styling it. + stylix.targets.waybar.enable = false; + + services.mako.enable = true; + # Network management is the waybar `network` module (on-click + # nm-connection-editor) + blueman for bluetooth; no separate nm-applet tray. + + # polkit agent for auth prompts (nm-connection-editor, etc.) + systemd.user.services.polkit-gnome = { + Unit = { + Description = "polkit-gnome authentication agent"; + PartOf = [ "graphical-session.target" ]; + After = [ "graphical-session.target" ]; + }; + Install.WantedBy = [ "graphical-session.target" ]; + Service = { + ExecStart = "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1"; + Restart = "on-failure"; + }; + }; +} diff --git a/home/linux/niri/host/desktop.nix b/home/linux/niri/host/desktop.nix new file mode 100644 index 00000000..0dd56b08 --- /dev/null +++ b/home/linux/niri/host/desktop.nix @@ -0,0 +1,20 @@ +# niri `output` blocks for the desktop, imported as a string by ../default.nix. +# Layout mirrors the current KDE setup (detected via kscreen-doctor): +# DP-1 2560x1440 landscape, main +# DP-3 rotated 270° portrait panel to its right (logical 1440x2560) +# If a monitor comes up upside-down, swap transform "270" <-> "90". +'' + output "DP-1" { + mode "2560x1440" + scale 1.0 + transform "normal" + position x=0 y=282 + } + + output "DP-3" { + mode "2560x1440" + scale 1.0 + transform "270" + position x=2560 y=0 + } +'' diff --git a/home/linux/niri/host/pocket.nix b/home/linux/niri/host/pocket.nix new file mode 100644 index 00000000..d06254e4 --- /dev/null +++ b/home/linux/niri/host/pocket.nix @@ -0,0 +1,14 @@ +# niri `output` block for the GPD Pocket, imported as a string by ../default.nix. +# The internal panel is mounted portrait, so the primary output is rotated to +# landscape. CONFIRM the connector name + rotation on first boot: +# niri msg outputs +# If the name isn't "DSI-1" (some units report "eDP-1"), rename below. If the +# image is sideways the wrong way, swap transform "270" <-> "90". +'' + output "DSI-1" { + mode "1200x1920" + scale 1.5 + transform "270" + position x=0 y=0 + } +'' diff --git a/home/linux/plasma/default.nix b/home/linux/plasma/default.nix deleted file mode 100644 index f779a97e..00000000 --- a/home/linux/plasma/default.nix +++ /dev/null @@ -1,70 +0,0 @@ -{ pkgs, inputs, theme, ... }: -let - isLight = theme.appearance == "light"; -in -{ - imports = [ inputs.plasma-manager.homeModules.plasma-manager ]; - home.packages = with pkgs; [ - exfatprogs - hfsprogs - kdePackages.ark - kdePackages.dolphin - kdePackages.filelight - kdePackages.gwenview - kdePackages.kcalc - kdePackages.konsole - kdePackages.okular - kdePackages.partitionmanager - kdePackages.yakuake - ]; - - programs.plasma = { - enable = true; - - workspace = { - theme = if isLight then "default" else "breeze-dark"; - colorScheme = if isLight then "BreezeLight" else "BreezeDark"; - cursor.theme = "breeze_cursors"; - iconTheme = if isLight then "breeze" else "breeze-dark"; - }; - - panels = [ - { - location = "bottom"; - height = 44; - } - ]; - - shortcuts = { - "kwin"."Show Desktop" = "Meta+D"; - "kwin"."Switch to Desktop 1" = "Meta+1"; - "kwin"."Switch to Desktop 2" = "Meta+2"; - "kwin"."Switch to Desktop 3" = "Meta+3"; - "kwin"."Switch to Desktop 4" = "Meta+4"; - "kwin"."Switch to Desktop 5" = "Meta+5"; - "kwin"."Switch to Desktop 6" = "Meta+6"; - "kwin"."Switch to Desktop 7" = "Meta+7"; - "kwin"."Switch to Desktop 8" = "Meta+8"; - "kwin"."Switch to Desktop 9" = "Meta+9"; - "kwin"."Window Close" = "Meta+Q"; - "kwin"."Window Maximize" = "Meta+M"; - "kwin"."Window Minimize" = "Meta+N"; - "kwin"."Window to Desktop 1" = "Meta+Shift+1"; - "kwin"."Window to Desktop 2" = "Meta+Shift+2"; - "kwin"."Window to Desktop 3" = "Meta+Shift+3"; - "kwin"."Window to Desktop 4" = "Meta+Shift+4"; - "kwin"."Window to Desktop 5" = "Meta+Shift+5"; - "kwin"."Window to Desktop 6" = "Meta+Shift+6"; - "kwin"."Window to Desktop 7" = "Meta+Shift+7"; - "kwin"."Window to Desktop 8" = "Meta+Shift+8"; - "kwin"."Window to Desktop 9" = "Meta+Shift+9"; - "org.kde.dolphin.desktop"."_launch" = "Meta+E"; - "org.kde.konsole.desktop"."_launch" = "Meta+T"; - "org.kde.krunner.desktop"."_launch" = "Meta+Space"; - }; - - configFile = { - "kdeglobals"."KDE"."SingleClick" = false; - }; - }; -} diff --git a/home/linux/plasma/host/desktop.nix b/home/linux/plasma/host/desktop.nix deleted file mode 100644 index 41e2705a..00000000 --- a/home/linux/plasma/host/desktop.nix +++ /dev/null @@ -1,7 +0,0 @@ -_: { - # Host-specific Plasma configuration for desktop - # Add desktop-specific Plasma settings here - programs.plasma = { - # Example: desktop-specific panel configuration or shortcuts - }; -} diff --git a/home/linux/plasma/host/eink.nix b/home/linux/plasma/host/eink.nix deleted file mode 100644 index 41e2705a..00000000 --- a/home/linux/plasma/host/eink.nix +++ /dev/null @@ -1,7 +0,0 @@ -_: { - # Host-specific Plasma configuration for desktop - # Add desktop-specific Plasma settings here - programs.plasma = { - # Example: desktop-specific panel configuration or shortcuts - }; -} diff --git a/home/linux/plasma/host/pocket.nix b/home/linux/plasma/host/pocket.nix deleted file mode 100644 index 2692a1f5..00000000 --- a/home/linux/plasma/host/pocket.nix +++ /dev/null @@ -1,7 +0,0 @@ -_: { - # Host-specific Plasma configuration for pocket - # Add pocket-specific Plasma settings here - programs.plasma = { - # Example: pocket-specific panel configuration or shortcuts - }; -} diff --git a/hosts/pocket/configuration.nix b/hosts/pocket/configuration.nix index 7e7f0158..49793329 100644 --- a/hosts/pocket/configuration.nix +++ b/hosts/pocket/configuration.nix @@ -10,10 +10,9 @@ variant = ""; }; + # grim/slurp/mako now come from the niri modules (screenshots are built-in; + # mako runs as a home-manager user service). environment.systemPackages = with pkgs; [ - grim - slurp - mako crosspipe ghostty ]; diff --git a/lib/default.nix b/lib/default.nix index 7a96bdeb..e3426887 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -31,7 +31,7 @@ let }; in assert builtins.elem t.appearance [ "light" "dark" ]; - assert builtins.elem f.desktopEnvironment [ null "plasma" "sway" "macos" ]; + assert builtins.elem f.desktopEnvironment [ null "sway" "niri" "macos" ]; { inherit inputs username system versions; theme = t; diff --git a/outputs/hosts.nix b/outputs/hosts.nix index 4caeb00c..1bc5dce4 100644 --- a/outputs/hosts.nix +++ b/outputs/hosts.nix @@ -18,7 +18,7 @@ let system = "x86_64-linux"; username = "drakeb"; format = "nixos"; - features.desktopEnvironment = "plasma"; + features.desktopEnvironment = "niri"; theme.baseFontSize = 12; extraSystemModules = chaletArgs; }; diff --git a/system/nixOS/bluetooth.nix b/system/nixOS/bluetooth.nix index ff2f8f91..a3e425ef 100644 --- a/system/nixOS/bluetooth.nix +++ b/system/nixOS/bluetooth.nix @@ -1,9 +1,11 @@ -_: +{ features, ... }: { hardware.bluetooth = { enable = true; powerOnBoot = true; }; - # Bluedevil is included with Plasma 6 by default for KDE Bluetooth integration + # Bluetooth tray applet + manager (KDE's Bluedevil is gone with Plasma). + # GUI hosts only — headless hosts have no session to show the applet. + services.blueman.enable = features.gui; } diff --git a/system/nixOS/default.nix b/system/nixOS/default.nix index 68e2b2c4..c94956d8 100644 --- a/system/nixOS/default.nix +++ b/system/nixOS/default.nix @@ -16,10 +16,10 @@ ] ++ lib.optionals features.gaming [ ./gaming.nix ] ++ ( - if features.desktopEnvironment == "plasma" - then [ ./plasma ] - else if features.desktopEnvironment == "sway" + if features.desktopEnvironment == "sway" then [ ./sway ] + else if features.desktopEnvironment == "niri" + then [ ./niri ] else [ ] ); } diff --git a/system/nixOS/greetd.nix b/system/nixOS/greetd.nix index c52c81f1..2566ea6b 100644 --- a/system/nixOS/greetd.nix +++ b/system/nixOS/greetd.nix @@ -1,9 +1,9 @@ -{ lib, pkgs, username, features, autologin, ... }: +{ config, lib, pkgs, username, features, autologin, ... }: let de = features.desktopEnvironment; sessionCommand = - if de == "plasma" then "${pkgs.kdePackages.plasma-workspace}/bin/startplasma-wayland" - else if de == "sway" then "${pkgs.sway}/bin/sway" + if de == "sway" then "${pkgs.sway}/bin/sway" + else if de == "niri" then "${config.programs.niri.package}/bin/niri-session" else throw "greetd: unsupported desktopEnvironment: ${de}"; in { diff --git a/system/nixOS/niri/default.nix b/system/nixOS/niri/default.nix new file mode 100644 index 00000000..163975c5 --- /dev/null +++ b/system/nixOS/niri/default.nix @@ -0,0 +1,19 @@ +{ inputs, pkgs, ... }: +{ + # niri-flake's module wires the compositor: package (niri-stable), the + # niri-session, xdg portals, polkit, and session registration. + imports = [ inputs.niri.nixosModules.niri ]; + + programs.niri.enable = true; + # git-main niri (newer than niri-flake's niri-stable tag). niri-flake doesn't + # apply its overlay globally, so reference the flake output directly. + programs.niri.package = inputs.niri.packages.${pkgs.system}.niri-unstable; + + # niri has built-in screenshots, so only wl-clipboard is extra (unlike sway, + # which needs grim/slurp). + environment.systemPackages = [ pkgs.wl-clipboard ]; + + # Without this, swaylock falls back to pam.d/other (deny-all) and can never + # unlock — a hard lockout. Registers /etc/pam.d/swaylock. + security.pam.services.swaylock = { }; +} diff --git a/system/nixOS/plasma/default.nix b/system/nixOS/plasma/default.nix deleted file mode 100644 index 4b6e9b39..00000000 --- a/system/nixOS/plasma/default.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ pkgs, ... }: -{ - services.xserver.enable = true; - services.desktopManager.plasma6.enable = true; - - environment.plasma6.excludePackages = with pkgs.kdePackages; [ - oxygen - ]; - - programs.dconf.enable = true; - - environment.systemPackages = with pkgs; [ - filezilla - kdePackages.kdeconnect-kde - kdePackages.kio-extras - kdePackages.kdeplasma-addons - kdePackages.kwalletmanager - kdePackages.partitionmanager - kdePackages.plasma-browser-integration - kdePackages.plasma-disks - kdePackages.plasma-nm - kdePackages.plasma-pa - kdePackages.plasma-thunderbolt - kdePackages.powerdevil - kdePackages.spectacle - ]; -} From 4cf4b1e0d4618b7f80914a06437e11be3b4d35cb Mon Sep 17 00:00:00 2001 From: Drake Bott Date: Sat, 4 Jul 2026 23:54:07 -0500 Subject: [PATCH 2/8] update --- home/linux/niri/default.nix | 42 +++++++++++++++++--------------- home/linux/niri/host/desktop.nix | 3 +++ system/nixOS/niri/default.nix | 22 ++++++----------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/home/linux/niri/default.nix b/home/linux/niri/default.nix index 4a464067..c79552b0 100644 --- a/home/linux/niri/default.nix +++ b/home/linux/niri/default.nix @@ -7,6 +7,22 @@ let # Per-host `output` blocks (a plain KDL string), spliced into config.kdl. niriOutputs = import ./host/${hostName}.nix; + # Auto-stacks windows vertically on portrait outputs (niri has no native + # vertical layout — issue #1071). Watches niri IPC, consumes new windows into + # the column. Tune VERTICAL_STACK_N in the script for windows-per-column. + niriStackToN = pkgs.fetchFromGitHub { + owner = "FarokhRaad"; + repo = "niri-stack-to-n"; + rev = "a8a76e35d6cd3149c7a47417d75779d533942c8a"; + hash = "sha256-2Hur7XFq6V/ZD/ONLlllFQYTW/QotSE9FFFjjxtLMXg="; + }; + + # Only desktop has a portrait monitor; skip the helper on the pocket. + stackSpawn = lib.optionalString (!isPocket) '' + // Auto-stack windows vertically on portrait monitors (no native niri + // option — issue #1071). The helper detects portrait outputs itself. + spawn-at-startup "${pkgs.python3}/bin/python3" "${niriStackToN}/niri_stack_to_n.py"''; + # Right-hand modules: laptop-only extras (backlight, battery) folded in. rightModules = [ "tray" "pulseaudio" "network" ] @@ -72,12 +88,11 @@ in hotkey-overlay { skip-at-startup; } - // X11 apps: xwayland-satellite serves display :0, and niri exports - // DISPLAY so children (and systemd/dbus) can reach it. - environment { - DISPLAY ":0" - } - spawn-at-startup "xwayland-satellite" ":0" + // X11 apps: niri manages xwayland-satellite itself (finds it on PATH, + // spawns on demand, and sets DISPLAY dynamically). + xwayland-satellite { } + + ${stackSpawn} window-rule { geometry-corner-radius 6 @@ -261,18 +276,5 @@ in services.mako.enable = true; # Network management is the waybar `network` module (on-click # nm-connection-editor) + blueman for bluetooth; no separate nm-applet tray. - - # polkit agent for auth prompts (nm-connection-editor, etc.) - systemd.user.services.polkit-gnome = { - Unit = { - Description = "polkit-gnome authentication agent"; - PartOf = [ "graphical-session.target" ]; - After = [ "graphical-session.target" ]; - }; - Install.WantedBy = [ "graphical-session.target" ]; - Service = { - ExecStart = "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1"; - Restart = "on-failure"; - }; - }; + # The polkit agent comes from niri-flake (niri-flake-polkit / polkit-kde). } diff --git a/home/linux/niri/host/desktop.nix b/home/linux/niri/host/desktop.nix index 0dd56b08..96e3d0b0 100644 --- a/home/linux/niri/host/desktop.nix +++ b/home/linux/niri/host/desktop.nix @@ -11,6 +11,9 @@ position x=0 y=282 } + // Portrait. Windows auto-stack vertically here via the niri-stack-to-n + // helper (see home/linux/niri/default.nix) — niri has no native vertical + // layout (issue #1071). The helper detects this output as portrait itself. output "DP-3" { mode "2560x1440" scale 1.0 diff --git a/system/nixOS/niri/default.nix b/system/nixOS/niri/default.nix index 163975c5..40c71eb5 100644 --- a/system/nixOS/niri/default.nix +++ b/system/nixOS/niri/default.nix @@ -1,19 +1,13 @@ { inputs, pkgs, ... }: { - # niri-flake's module wires the compositor: package (niri-stable), the - # niri-session, xdg portals, polkit, and session registration. + # niri-flake's module wires everything: niri-session, xdg portals, a polkit + # agent, the swaylock PAM service, and the niri.cachix.org substituter. imports = [ inputs.niri.nixosModules.niri ]; - programs.niri.enable = true; - # git-main niri (newer than niri-flake's niri-stable tag). niri-flake doesn't - # apply its overlay globally, so reference the flake output directly. - programs.niri.package = inputs.niri.packages.${pkgs.system}.niri-unstable; - - # niri has built-in screenshots, so only wl-clipboard is extra (unlike sway, - # which needs grim/slurp). - environment.systemPackages = [ pkgs.wl-clipboard ]; - - # Without this, swaylock falls back to pam.d/other (deny-all) and can never - # unlock — a hard lockout. Registers /etc/pam.d/swaylock. - security.pam.services.swaylock = { }; + programs.niri = { + enable = true; + # git-main niri. niri-flake doesn't apply its overlay globally, so reference + # the flake output directly (this is what niri.cachix.org caches). + package = inputs.niri.packages.${pkgs.system}.niri-unstable; + }; } From 3ac952273c5592c4f0a2050b573c33d2cc94d0d4 Mon Sep 17 00:00:00 2001 From: Drake Bott Date: Sun, 5 Jul 2026 17:55:08 -0500 Subject: [PATCH 3/8] update --- flake.lock | 78 ++++---- home/common/stylix.nix | 8 + home/linux/desktop.nix | 1 + home/linux/niri/default.nix | 333 +++++++++++++------------------ home/linux/niri/host/desktop.nix | 40 ++-- home/linux/niri/host/pocket.nix | 25 +-- scripts/niri-vert-scroll.clj | 78 ++++++++ 7 files changed, 301 insertions(+), 262 deletions(-) create mode 100644 scripts/niri-vert-scroll.clj diff --git a/flake.lock b/flake.lock index 47b87425..7612e3b4 100644 --- a/flake.lock +++ b/flake.lock @@ -76,11 +76,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1766810506, - "narHash": "sha256-I4BxozsEu205tA7jazufztI8ZQ5p7hcCnjqrSKPz9nI=", + "lastModified": 1782994178, + "narHash": "sha256-VGc3/2fe6hnOmNSLlOygEAbeS69v7A7rjKDKRzAgE2Q=", "owner": "hraban", "repo": "cl-nix-lite", - "rev": "038e341cede255a83a8f04af114dc95717461d32", + "rev": "eb584721e5e799bafe0c6210a8a1a398f90e8ac0", "type": "github" }, "original": { @@ -127,11 +127,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1730663653, - "narHash": "sha256-kFCUWettiFHDIqxCWWQ9qY8pVh+Lj+XL0Giyy/kdomg=", + "lastModified": 1766808011, + "narHash": "sha256-s83BS0abtIj7vzUf8JE0209KphfHA6qRw/92D9k/F+0=", "owner": "hraban", "repo": "flake-compat", - "rev": "e5b16676185cb7548581c852f51ce7f3a49bba5e", + "rev": "6e0aafdc4c4c2043c66f756d5045374b42184fc5", "type": "github" }, "original": { @@ -338,11 +338,11 @@ ] }, "locked": { - "lastModified": 1782704057, - "narHash": "sha256-G1I1gd32F7mp9LAe1DaZ4ZL7NX5gyiKwdCMwro1Vrck=", + "lastModified": 1783221248, + "narHash": "sha256-ESQnuNHEDChsB4IxoLRhscVahqkDWkTb+qdIz8euYt4=", "owner": "nix-community", "repo": "home-manager", - "rev": "868d0a692de703c2de98fab61968e4e310b7c28e", + "rev": "af2beae5f0fae0a4310cc0e6aef2572f56090353", "type": "github" }, "original": { @@ -362,11 +362,11 @@ "treefmt-nix": "treefmt-nix_2" }, "locked": { - "lastModified": 1766810876, - "narHash": "sha256-VPElWFQIiP31lXQXEom+L4sl85alZpZn33O4hewsP9k=", + "lastModified": 1783182903, + "narHash": "sha256-7IteXipJZfFepi5zakxe0jQ5i3vRBQguk0+Gtte1Fu4=", "owner": "hraban", "repo": "mac-app-util", - "rev": "4747968574ea58512c5385466400b2364c85d2d0", + "rev": "039f33deef21782d4db97087f426504951239887", "type": "github" }, "original": { @@ -385,11 +385,11 @@ "xwayland-satellite-unstable": "xwayland-satellite-unstable" }, "locked": { - "lastModified": 1783123561, - "narHash": "sha256-A4XJC5qR19bBJ3KO3oE1cK4e98bPrO5QYcSNPVZuUas=", + "lastModified": 1783267733, + "narHash": "sha256-DTvaM+0vanneOcKg3NKXV7ilf0ShY4BOBz/nde5GHos=", "owner": "sodiboo", "repo": "niri-flake", - "rev": "62b5feade147800f4e49503ae6fb643892689d65", + "rev": "74053f79cad0f6c3a4a0be6b7928795d2e6a9f4b", "type": "github" }, "original": { @@ -418,11 +418,11 @@ "niri-unstable": { "flake": false, "locked": { - "lastModified": 1781781064, - "narHash": "sha256-Ii/koEm/sRyg65qbAQWqEgboSEIhdH0EL4KglAc14p0=", + "lastModified": 1783248941, + "narHash": "sha256-citgYJbvR1iUKXpiWlFq3NO+fw6hXGL3Qk1ampU7lhA=", "owner": "YaLTeR", "repo": "niri", - "rev": "49fc6117fd6c043adaa2ead316b82db5ed735d36", + "rev": "a30ca7983b2f1fc3ddeb209b3fe18fa78e0dbd25", "type": "github" }, "original": { @@ -561,11 +561,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1782948114, - "narHash": "sha256-AXmz9ho4Lud5CsbrZsuSVwpQZ4o5FgZ1chxBn5cJ8+0=", + "lastModified": 1783247743, + "narHash": "sha256-9b2xGt5CAd6WriPJOZs/EYXq9fGVCeoisYo1P3nhL2Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "9e92285f211dad236540fd617d7e30e0b99bc0e1", + "rev": "c4013e501c048ae7c4a8940c92837636042bf6c3", "type": "github" }, "original": { @@ -577,11 +577,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1766736597, - "narHash": "sha256-BASnpCLodmgiVn0M1MU2Pqyoz0aHwar/0qLkp7CjvSQ=", + "lastModified": 1779796641, + "narHash": "sha256-ZsIrKmhp4vbBXoXXmR/tBXA/UCsAQiJL9vsgZEduhVY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "f560ccec6b1116b22e6ed15f4c510997d99d5852", + "rev": "25f538306313eae3927264466c70d7001dcea1df", "type": "github" }, "original": { @@ -625,11 +625,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1761236834, - "narHash": "sha256-+pthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE+fV2Q=", + "lastModified": 1770107345, + "narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d5faa84122bc0a1fd5d378492efce4e289f8eac1", + "rev": "4533d9293756b63904b7238acb84ac8fe4c8c2c4", "type": "github" }, "original": { @@ -641,11 +641,11 @@ }, "nixpkgs_6": { "locked": { - "lastModified": 1782959384, - "narHash": "sha256-xnJJk+ct+D2+wdRxj1wk36w5zV9RVESwRqcklPdt3fM=", + "lastModified": 1783224372, + "narHash": "sha256-8i/87eeoqiGE4yOTjwSA3Eh/ziJRQEmd/unYU+K27sk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "65179426c83bb3f6bc14898b42ea1c6f01d374b0", + "rev": "d407951447dcd00442e97087bf374aad70c04cea", "type": "github" }, "original": { @@ -670,11 +670,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1782847225, - "narHash": "sha256-JC9PjqKYG9ve5U8aDOLQipp3+KLANBHUvGdLZlxzdKI=", + "lastModified": 1783148766, + "narHash": "sha256-uslt2pqShTIXDdAHRHv2QkYLsVdY8Oqwz0EA48/RSM8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "95ca1e203c0750115fd4a6f17d5a245dfe6b1edd", + "rev": "a50de1b7d8a586adc18d2395c19de7d6058e6030", "type": "github" }, "original": { @@ -781,11 +781,11 @@ "systems": "systems_5" }, "locked": { - "lastModified": 1781834224, - "narHash": "sha256-l9pVFs8+CmCvC+feR/PL5vstu3CVmKx/xCW8fGmux0c=", + "lastModified": 1783258469, + "narHash": "sha256-WNNE9Fm/DHj6ppMCxdAujyRamcNeng9wC3se0aw/oPc=", "owner": "different-name", "repo": "steam-config-nix", - "rev": "22c06e684b0785f9031872e0af7d9a96d27ad87c", + "rev": "87b661b33f5f7cf441d4e2234ab302bdd6c6b310", "type": "github" }, "original": { @@ -1005,11 +1005,11 @@ "nixpkgs": "nixpkgs_5" }, "locked": { - "lastModified": 1766000401, - "narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=", + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", "type": "github" }, "original": { diff --git a/home/common/stylix.nix b/home/common/stylix.nix index 3187e4fb..25828955 100644 --- a/home/common/stylix.nix +++ b/home/common/stylix.nix @@ -10,6 +10,14 @@ in autoEnable = lib.mkDefault true; image = lib.mkDefault null; + # Themed cursor. niri-flake's stylix target reads this to set niri's xcursor + # theme + size; also themes GTK/other apps' cursors. (Global, not niri-only.) + cursor = { + name = lib.mkDefault "Bibata-Modern-Classic"; + package = lib.mkDefault pkgs.bibata-cursors; + size = lib.mkDefault 24; + }; + fonts = { monospace = { name = lib.mkOptionDefault "MonoLisa Nerd Font"; diff --git a/home/linux/desktop.nix b/home/linux/desktop.nix index acfa316f..c003c51a 100644 --- a/home/linux/desktop.nix +++ b/home/linux/desktop.nix @@ -14,6 +14,7 @@ in filezilla flashprint crosspipe + kdePackages.dolphin kdePackages.kdenlive glaxnimate krita diff --git a/home/linux/niri/default.nix b/home/linux/niri/default.nix index c79552b0..16be7505 100644 --- a/home/linux/niri/default.nix +++ b/home/linux/niri/default.nix @@ -1,28 +1,10 @@ { config, lib, pkgs, hostName, ... }: let - c = config.lib.stylix.colors; # base16 hex, no leading '#' - font = config.stylix.fonts.monospace.name; isPocket = hostName == "pocket"; - # Per-host `output` blocks (a plain KDL string), spliced into config.kdl. + # Per-host `outputs` attrset, spliced into settings.outputs. niriOutputs = import ./host/${hostName}.nix; - # Auto-stacks windows vertically on portrait outputs (niri has no native - # vertical layout — issue #1071). Watches niri IPC, consumes new windows into - # the column. Tune VERTICAL_STACK_N in the script for windows-per-column. - niriStackToN = pkgs.fetchFromGitHub { - owner = "FarokhRaad"; - repo = "niri-stack-to-n"; - rev = "a8a76e35d6cd3149c7a47417d75779d533942c8a"; - hash = "sha256-2Hur7XFq6V/ZD/ONLlllFQYTW/QotSE9FFFjjxtLMXg="; - }; - - # Only desktop has a portrait monitor; skip the helper on the pocket. - stackSpawn = lib.optionalString (!isPocket) '' - // Auto-stack windows vertically on portrait monitors (no native niri - // option — issue #1071). The helper detects portrait outputs itself. - spawn-at-startup "${pkgs.python3}/bin/python3" "${niriStackToN}/niri_stack_to_n.py"''; - # Right-hand modules: laptop-only extras (backlight, battery) folded in. rightModules = [ "tray" "pulseaudio" "network" ] @@ -32,7 +14,10 @@ in { # `programs.niri` here comes from niri-flake's NixOS module, which injects its # home-manager module automatically (via home-manager.sharedModules) — so we - # must NOT import homeModules.niri again. It validates the KDL at build time. + # must NOT import homeModules.niri again. The same auto-wiring pulls in + # niri-flake's stylix target (homeModules.stylix, active because stylix is a + # NixOS module here); it owns the window border + cursor colors, so we don't + # set focus-ring/border/cursor below. home.packages = with pkgs; [ swaylock @@ -44,144 +29,143 @@ in ]; programs = { - # ---- niri compositor config --------------------------------------- - # Raw KDL (validated at build time by niri-flake). We keep hand-written - # KDL rather than the typed `settings` DSL — it's one readable file. - niri.config = '' - // Managed by home-manager (home/linux/niri). Edit the Nix file, not this. - // niri reloads this file live on save — no restart needed. - - input { - keyboard { - xkb { layout "us"; } - } - touchpad { - tap - natural-scroll - } - focus-follows-mouse max-scroll-amount="0%" - warp-mouse-to-focus - } - - // ---- Monitors (per-host) ---- - ${niriOutputs} - - layout { - gaps 8 - center-focused-column "never" - preset-column-widths { - proportion 0.33333 - proportion 0.5 - proportion 0.66667 - } - default-column-width { proportion 0.5; } - focus-ring { - width 2 - active-color "#${c.base0D}" - inactive-color "#${c.base02}" - } - border { off; } - } - - prefer-no-csd - screenshot-path "~/Pictures/Screenshots/Screenshot-%Y-%m-%d-%H-%M-%S.png" - - hotkey-overlay { skip-at-startup; } - - // X11 apps: niri manages xwayland-satellite itself (finds it on PATH, - // spawns on demand, and sets DISPLAY dynamically). - xwayland-satellite { } - - ${stackSpawn} - - window-rule { - geometry-corner-radius 6 - clip-to-geometry true - } - - binds { - Mod+Shift+Slash { show-hotkey-overlay; } - - Mod+Return { spawn "ghostty"; } - Mod+D { spawn "fuzzel"; } - Mod+Q { close-window; } - Mod+Alt+L { spawn "swaylock"; } - - // Focus (vim keys, matching the old sway setup) - Mod+H { focus-column-left; } - Mod+L { focus-column-right; } - Mod+J { focus-window-down; } - Mod+K { focus-window-up; } - - // Move - Mod+Shift+H { move-column-left; } - Mod+Shift+L { move-column-right; } - Mod+Shift+J { move-window-down; } - Mod+Shift+K { move-window-up; } - - // Monitors (desktop is dual-head) - Mod+Ctrl+H { focus-monitor-left; } - Mod+Ctrl+L { focus-monitor-right; } - Mod+Shift+Ctrl+H { move-column-to-monitor-left; } - Mod+Shift+Ctrl+L { move-column-to-monitor-right; } - - Mod+Home { focus-column-first; } - Mod+End { focus-column-last; } - - // Workspaces - Mod+1 { focus-workspace 1; } - Mod+2 { focus-workspace 2; } - Mod+3 { focus-workspace 3; } - Mod+4 { focus-workspace 4; } - Mod+5 { focus-workspace 5; } - Mod+6 { focus-workspace 6; } - Mod+7 { focus-workspace 7; } - Mod+8 { focus-workspace 8; } - Mod+9 { focus-workspace 9; } - Mod+Shift+1 { move-column-to-workspace 1; } - Mod+Shift+2 { move-column-to-workspace 2; } - Mod+Shift+3 { move-column-to-workspace 3; } - Mod+Shift+4 { move-column-to-workspace 4; } - Mod+Shift+5 { move-column-to-workspace 5; } - Mod+Shift+6 { move-column-to-workspace 6; } - Mod+Shift+7 { move-column-to-workspace 7; } - Mod+Shift+8 { move-column-to-workspace 8; } - Mod+Shift+9 { move-column-to-workspace 9; } - Mod+U { focus-workspace-down; } - Mod+I { focus-workspace-up; } - - // Sizing / layout - Mod+F { maximize-column; } - Mod+Shift+F { fullscreen-window; } - Mod+C { center-column; } - Mod+R { switch-preset-column-width; } - Mod+Minus { set-column-width "-10%"; } - Mod+Equal { set-column-width "+10%"; } - Mod+Comma { consume-window-into-column; } - Mod+Period { expel-window-from-column; } - Mod+Space { switch-focus-between-floating-and-tiling; } - Mod+Shift+Space { toggle-window-floating; } - - // Screenshots (niri built-in) - Print { screenshot; } - Mod+Print { screenshot-screen; } - Mod+Shift+Print { screenshot-window; } + # ---- niri compositor config (typed settings DSL) ------------------ + # niri-flake validates this at build time and generates the KDL. We use + # `settings` (not raw `config`) because niri-flake's stylix target only + # writes into `settings` — setting `config` would override it wholesale. + # niri reloads on rebuild-switch; no restart needed. + niri.settings = { + input = { + keyboard.xkb.layout = "us"; + touchpad = { + tap = true; + natural-scroll = true; + }; + focus-follows-mouse = { + enable = true; + max-scroll-amount = "0%"; + }; + warp-mouse-to-focus.enable = true; + }; - // Media / volume / brightness - XF86AudioRaiseVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05+"; } - XF86AudioLowerVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05-"; } - XF86AudioMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; } - XF86AudioMicMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"; } - XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "set" "5%+"; } - XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "set" "5%-"; } - XF86AudioPlay allow-when-locked=true { spawn "playerctl" "play-pause"; } - XF86AudioNext allow-when-locked=true { spawn "playerctl" "next"; } - XF86AudioPrev allow-when-locked=true { spawn "playerctl" "previous"; } + # Monitors (per-host). + outputs = niriOutputs; + + layout = { + gaps = 8; + center-focused-column = "never"; + preset-column-widths = [ + { proportion = 0.33333; } + { proportion = 0.5; } + { proportion = 0.66667; } + ]; + default-column-width.proportion = 0.5; + # focus-ring / border colors come from stylix (niri-flake stylix target). + }; - Mod+Shift+E { quit; } - Mod+Shift+P { power-off-monitors; } - } - ''; + prefer-no-csd = true; + screenshot-path = "~/Pictures/Screenshots/Screenshot-%Y-%m-%d-%H-%M-%S.png"; + hotkey-overlay.skip-at-startup = true; + + # X11 apps: niri manages xwayland-satellite itself (finds it on PATH, + # spawns on demand). enable defaults true, path null → uses PATH. + + # niri-vert-scroll (scripts/niri-vert-scroll.clj) makes portrait outputs + # one vertical scrolling column. Only desktop has a portrait monitor. + spawn-at-startup = lib.optionals (!isPocket) [{ argv = [ "niri-vert-scroll" ]; }]; + + window-rules = [{ + geometry-corner-radius = + let r = 6.0; in { top-left = r; top-right = r; bottom-left = r; bottom-right = r; }; + clip-to-geometry = true; + }]; + + binds = with config.lib.niri.actions; { + "Mod+Shift+Slash".action = show-hotkey-overlay; + + "Mod+Return".action = spawn "ghostty"; + "Mod+D".action = spawn "fuzzel"; + "Mod+Q".action = close-window; + "Mod+Alt+L".action = spawn "swaylock"; + + # Focus (vim keys, matching the old sway setup) + "Mod+H".action = focus-column-left; + "Mod+L".action = focus-column-right; + "Mod+J".action = focus-window-down; + "Mod+K".action = focus-window-up; + + # Move + "Mod+Shift+H".action = move-column-left; + "Mod+Shift+L".action = move-column-right; + "Mod+Shift+J".action = move-window-down; + "Mod+Shift+K".action = move-window-up; + + # Monitors (desktop is dual-head) + "Mod+Ctrl+H".action = focus-monitor-left; + "Mod+Ctrl+L".action = focus-monitor-right; + "Mod+Shift+Ctrl+H".action = move-column-to-monitor-left; + "Mod+Shift+Ctrl+L".action = move-column-to-monitor-right; + + "Mod+Home".action = focus-column-first; + "Mod+End".action = focus-column-last; + + # Workspaces + "Mod+1".action = focus-workspace 1; + "Mod+2".action = focus-workspace 2; + "Mod+3".action = focus-workspace 3; + "Mod+4".action = focus-workspace 4; + "Mod+5".action = focus-workspace 5; + "Mod+6".action = focus-workspace 6; + "Mod+7".action = focus-workspace 7; + "Mod+8".action = focus-workspace 8; + "Mod+9".action = focus-workspace 9; + # move-column-to-workspace isn't in config.lib.niri.actions as a bare + # builder (only the -down/-up variants are), so use the path form. + "Mod+Shift+1".action.move-column-to-workspace = 1; + "Mod+Shift+2".action.move-column-to-workspace = 2; + "Mod+Shift+3".action.move-column-to-workspace = 3; + "Mod+Shift+4".action.move-column-to-workspace = 4; + "Mod+Shift+5".action.move-column-to-workspace = 5; + "Mod+Shift+6".action.move-column-to-workspace = 6; + "Mod+Shift+7".action.move-column-to-workspace = 7; + "Mod+Shift+8".action.move-column-to-workspace = 8; + "Mod+Shift+9".action.move-column-to-workspace = 9; + "Mod+U".action = focus-workspace-down; + "Mod+I".action = focus-workspace-up; + + # Sizing / layout + "Mod+F".action = maximize-column; + "Mod+Shift+F".action = fullscreen-window; + "Mod+C".action = center-column; + "Mod+R".action = switch-preset-column-width; + "Mod+Minus".action = set-column-width "-10%"; + "Mod+Equal".action = set-column-width "+10%"; + "Mod+Comma".action = consume-window-into-column; + "Mod+Period".action = expel-window-from-column; + "Mod+Space".action = switch-focus-between-floating-and-tiling; + "Mod+Shift+Space".action = toggle-window-floating; + + # Screenshots (niri built-in). Not bare builders in config.lib.niri.actions; + # use the path form (empty attrset = default props). + "Print".action.screenshot = { }; + "Mod+Print".action.screenshot-screen = { }; + "Mod+Shift+Print".action.screenshot-window = { }; + + # Media / volume / brightness + "XF86AudioRaiseVolume" = { allow-when-locked = true; action = spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05+"; }; + "XF86AudioLowerVolume" = { allow-when-locked = true; action = spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05-"; }; + "XF86AudioMute" = { allow-when-locked = true; action = spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; }; + "XF86AudioMicMute" = { allow-when-locked = true; action = spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"; }; + "XF86MonBrightnessUp" = { allow-when-locked = true; action = spawn "brightnessctl" "set" "5%+"; }; + "XF86MonBrightnessDown" = { allow-when-locked = true; action = spawn "brightnessctl" "set" "5%-"; }; + "XF86AudioPlay" = { allow-when-locked = true; action = spawn "playerctl" "play-pause"; }; + "XF86AudioNext" = { allow-when-locked = true; action = spawn "playerctl" "next"; }; + "XF86AudioPrev" = { allow-when-locked = true; action = spawn "playerctl" "previous"; }; + + "Mod+Shift+E".action = quit; + "Mod+Shift+P".action = power-off-monitors; + }; + }; # ---- Waybar: KDE-style bottom panel ------------------------------- waybar = { @@ -233,45 +217,12 @@ in on-scroll-down = "brightnessctl set 5%-"; }; }; - style = '' - * { - font-family: "${font}"; - font-size: 13px; - min-height: 0; - } - window#waybar { - background: #${c.base00}; - color: #${c.base05}; - } - #workspaces button { - padding: 0 8px; - color: #${c.base04}; - background: transparent; - } - #workspaces button.active { - color: #${c.base00}; - background: #${c.base0D}; - border-radius: 4px; - } - #workspaces button.urgent { - color: #${c.base00}; - background: #${c.base08}; - border-radius: 4px; - } - #window { color: #${c.base04}; } - #clock, #pulseaudio, #network, #battery, #backlight, #tray { - padding: 0 10px; - } - #battery.charging { color: #${c.base0B}; } - #battery.critical:not(.charging) { color: #${c.base08}; } - #network.disconnected { color: #${c.base08}; } - ''; + # No `style`: stylix.targets.waybar owns the CSS (colors + font), themed + # to the active base16 scheme. Module layout/config stays ours above. }; fuzzel.enable = true; }; - # We own the waybar CSS; keep stylix from also styling it. - stylix.targets.waybar.enable = false; services.mako.enable = true; # Network management is the waybar `network` module (on-click diff --git a/home/linux/niri/host/desktop.nix b/home/linux/niri/host/desktop.nix index 96e3d0b0..068ccc7b 100644 --- a/home/linux/niri/host/desktop.nix +++ b/home/linux/niri/host/desktop.nix @@ -1,23 +1,23 @@ -# niri `output` blocks for the desktop, imported as a string by ../default.nix. -# Layout mirrors the current KDE setup (detected via kscreen-doctor): +# niri `outputs` for the desktop, imported as an attrset by ../default.nix +# into settings.outputs. Layout mirrors the KDE setup (via kscreen-doctor): # DP-1 2560x1440 landscape, main # DP-3 rotated 270° portrait panel to its right (logical 1440x2560) -# If a monitor comes up upside-down, swap transform "270" <-> "90". -'' - output "DP-1" { - mode "2560x1440" - scale 1.0 - transform "normal" - position x=0 y=282 - } +# If a monitor comes up upside-down, swap rotation 270 <-> 90. +{ + "DP-1" = { + mode = { width = 2560; height = 1440; }; + scale = 1.0; + position = { x = 0; y = 282; }; + }; - // Portrait. Windows auto-stack vertically here via the niri-stack-to-n - // helper (see home/linux/niri/default.nix) — niri has no native vertical - // layout (issue #1071). The helper detects this output as portrait itself. - output "DP-3" { - mode "2560x1440" - scale 1.0 - transform "270" - position x=2560 y=0 - } -'' + # Portrait. niri-vert-scroll (scripts/niri-vert-scroll.clj) makes this a + # single vertical scrolling column — niri has no native vertical layout + # (issue #1071). The typed outputs DSL has no per-output layout option, so + # the script also widens the consolidated column to 100% itself. + "DP-3" = { + mode = { width = 2560; height = 1440; }; + scale = 1.0; + transform.rotation = 270; + position = { x = 2560; y = 0; }; + }; +} diff --git a/home/linux/niri/host/pocket.nix b/home/linux/niri/host/pocket.nix index d06254e4..83c7353d 100644 --- a/home/linux/niri/host/pocket.nix +++ b/home/linux/niri/host/pocket.nix @@ -1,14 +1,15 @@ -# niri `output` block for the GPD Pocket, imported as a string by ../default.nix. -# The internal panel is mounted portrait, so the primary output is rotated to -# landscape. CONFIRM the connector name + rotation on first boot: +# niri `outputs` for the GPD Pocket, imported as an attrset by ../default.nix +# into settings.outputs. The internal panel is mounted portrait, so the primary +# output is rotated to landscape. CONFIRM the connector name + rotation on first +# boot: # niri msg outputs # If the name isn't "DSI-1" (some units report "eDP-1"), rename below. If the -# image is sideways the wrong way, swap transform "270" <-> "90". -'' - output "DSI-1" { - mode "1200x1920" - scale 1.5 - transform "270" - position x=0 y=0 - } -'' +# image is sideways the wrong way, swap rotation 270 <-> 90. +{ + "DSI-1" = { + mode = { width = 1200; height = 1920; }; + scale = 1.5; + transform.rotation = 270; + position = { x = 0; y = 0; }; + }; +} diff --git a/scripts/niri-vert-scroll.clj b/scripts/niri-vert-scroll.clj new file mode 100644 index 00000000..c0c94aef --- /dev/null +++ b/scripts/niri-vert-scroll.clj @@ -0,0 +1,78 @@ +;; One vertical, scrolling column on a portrait monitor — niri has no native +;; vertical layout (YaLTeR/niri#1071). Pulls each window into a single column, +;; then even-splits height up to :max-visible (full, ½, ⅓ …) and holds at +;; 1/:max-visible beyond that so the column overflows and scrolls. + +(require '[cheshire.core :as json] + '[babashka.process :as p] + '[clojure.java.io :as io]) + +(def config {:max-visible 3}) + +;; nil on failure — niri msg errors transiently; a throw would kill the daemon. +(defn niri-json [& args] + (try (-> (apply p/shell {:out :string} "niri" "msg" "--json" args) + :out (json/parse-string true)) + (catch Exception _ nil))) + +(defn niri-action [& args] + (try (apply p/shell {:out :string :err :string} "niri" "msg" "action" args) + (catch Exception _ nil))) + +(defn portrait-ws-ids [] + (let [portrait (->> (niri-json "outputs") vals + (filter #(< (get-in % [:logical :width] 0) + (get-in % [:logical :height] 0))) + (map :name) set)] + (->> (niri-json "workspaces") + (filter #(contains? portrait (:output %))) + (map :id) set))) + +(defn column-of [w] (get-in w [:layout :pos_in_scrolling_layout 0])) + +(defn apply-heights! [ws-id] + (let [wins (filter #(= (:workspace_id %) ws-id) (niri-json "windows")) + maxv (:max-visible config) + overflow (> (count wins) maxv) + pct (str (quot 100 maxv) "%")] + (doseq [w wins] + (if overflow + (niri-action "set-window-height" pct "--id" (str (:id w))) + (niri-action "reset-window-height" "--id" (str (:id w))))))) + +;; The pull re-fires an event, so multi-column layouts converge one step a time. +(defn consolidate! [w] + (when (> (or (column-of w) 1) 1) + (niri-action "consume-or-expel-window-left" "--id" (str (:id w))) + (apply-heights! (:workspace_id w)))) + +;; The typed niri config can't set a per-output default-column-width, so we +;; widen the portrait column to full width here. set-column-width has no --id; +;; it acts on the *focused* column, so only fire it when this window is focused +;; (else we'd resize whatever column is focused on the landscape monitor). +;; ponytail: a background-opened portrait window stays at default width until +;; it's focused-and-changed — fine, the common open-here case is focused. +(defn widen! [w] + (when (:is_focused w) + (niri-action "set-column-width" "100%"))) + +(defn parse-line [s] + (try (json/parse-string s true) (catch Exception _ nil))) + +(defn -main [] + (let [proc (p/process ["niri" "msg" "--json" "event-stream"] {:out :stream})] + (with-open [rdr (io/reader (:out proc))] + (loop [ws-ids (portrait-ws-ids)] + (when-let [line (.readLine rdr)] + (let [[ev-type payload] (some-> line parse-line first)] + (case ev-type + :WorkspacesChanged (recur (portrait-ws-ids)) + :WindowClosed (do (run! apply-heights! ws-ids) (recur ws-ids)) + :WindowOpenedOrChanged (let [w (:window payload)] + (when (contains? ws-ids (:workspace_id w)) + (consolidate! w) + (widen! w)) + (recur ws-ids)) + (recur ws-ids)))))))) + +(-main) From a8aedce61e4dae1d5308a7350f0735e552029731 Mon Sep 17 00:00:00 2001 From: Drake Bott Date: Mon, 6 Jul 2026 20:31:27 -0500 Subject: [PATCH 4/8] update --- flake.lock | 24 +++++----- home/linux/default.nix | 2 - home/linux/niri/default.nix | 40 ++++++++--------- home/linux/niri/host/desktop.nix | 40 ++++++++++------- home/linux/niri/host/eink.nix | 45 +++++++++++++++++++ home/linux/niri/host/pocket.nix | 20 +++++---- home/linux/sway/default.nix | 65 --------------------------- home/linux/sway/host/eink.nix | 56 ----------------------- lib/default.nix | 2 +- outputs/hosts.nix | 2 +- scripts/niri-even-widths.clj | 76 ++++++++++++++++++++++++++++++++ system/nixOS/default.nix | 13 ++---- system/nixOS/greetd.nix | 14 +++--- system/nixOS/sway/default.nix | 21 --------- 14 files changed, 199 insertions(+), 221 deletions(-) create mode 100644 home/linux/niri/host/eink.nix delete mode 100644 home/linux/sway/default.nix delete mode 100644 home/linux/sway/host/eink.nix create mode 100644 scripts/niri-even-widths.clj delete mode 100644 system/nixOS/sway/default.nix diff --git a/flake.lock b/flake.lock index 7612e3b4..51fc66db 100644 --- a/flake.lock +++ b/flake.lock @@ -498,11 +498,11 @@ "nixpkgs": "nixpkgs_7" }, "locked": { - "lastModified": 1782562157, - "narHash": "sha256-a7+T6QSeowynwZ1ZJJbP8T8ntAytvrui8kFGJmIZt2c=", + "lastModified": 1783370751, + "narHash": "sha256-E+3MIMvKuo9k+K+qLQ9YXzsBzkgHuyVLnsEbN2DFfuc=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "a9cf7546a938c737b079e738de73934a13de9784", + "rev": "662bd6e312d2c8b212e32cb377abaee190749320", "type": "github" }, "original": { @@ -561,11 +561,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1783247743, - "narHash": "sha256-9b2xGt5CAd6WriPJOZs/EYXq9fGVCeoisYo1P3nhL2Y=", + "lastModified": 1783279667, + "narHash": "sha256-/NAkDSsve+GNM0Bt6tleJdCGfsTlK89nPjkVOzZMo0s=", "owner": "nixos", "repo": "nixpkgs", - "rev": "c4013e501c048ae7c4a8940c92837636042bf6c3", + "rev": "f205b5574fd0cb7da5b702a2da51507b7f4fdd1b", "type": "github" }, "original": { @@ -759,11 +759,11 @@ "systems": "systems_4" }, "locked": { - "lastModified": 1782898510, - "narHash": "sha256-7QVN7ijsXbIBRrI9LdXfeoFktTS0I6HZya9tu6+STrs=", + "lastModified": 1783303713, + "narHash": "sha256-d6Zs6wD0kvWQXn7qPU0YsuqoqBhT7zi5+1M365FINt8=", "owner": "Gerg-L", "repo": "spicetify-nix", - "rev": "9cabea6f5973ec01f60080ea50f54f8f6d74dc95", + "rev": "d9d714243e2f8d10af28db5111f018ec2d77acc9", "type": "github" }, "original": { @@ -814,11 +814,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1782925252, - "narHash": "sha256-A/LK7mgBO+JwXP2464rlUnSxsgRFJTU7hkHFji0qU70=", + "lastModified": 1783359251, + "narHash": "sha256-HUiCnEVlJ4n+qJlZojiz/zv+P0cqM5zsg1dxpz2J7Mg=", "owner": "nix-community", "repo": "stylix", - "rev": "8d56f0f6e9fd75e056a773b5ce3fc1b073c37dce", + "rev": "e602ad042f00409f33c8ad2829cd8d59ba345c7e", "type": "github" }, "original": { diff --git a/home/linux/default.nix b/home/linux/default.nix index 3c4a8724..ed8d3216 100644 --- a/home/linux/default.nix +++ b/home/linux/default.nix @@ -7,8 +7,6 @@ ./mpv ] ++ lib.optionals features.gaming [ ./games - ] ++ lib.optionals (features.desktopEnvironment == "sway") [ - ./sway ] ++ lib.optionals (features.desktopEnvironment == "niri") [ ./niri ]; diff --git a/home/linux/niri/default.nix b/home/linux/niri/default.nix index 16be7505..5a064493 100644 --- a/home/linux/niri/default.nix +++ b/home/linux/niri/default.nix @@ -1,10 +1,7 @@ -{ config, lib, pkgs, hostName, ... }: +{ config, lib, pkgs, hostName, features, ... }: let isPocket = hostName == "pocket"; - # Per-host `outputs` attrset, spliced into settings.outputs. - niriOutputs = import ./host/${hostName}.nix; - # Right-hand modules: laptop-only extras (backlight, battery) folded in. rightModules = [ "tray" "pulseaudio" "network" ] @@ -12,6 +9,9 @@ let ++ [ "clock" ]; in { + # Per-host module: settings.outputs + host-specific extras. + imports = [ ./host/${hostName}.nix ]; + # `programs.niri` here comes from niri-flake's NixOS module, which injects its # home-manager module automatically (via home-manager.sharedModules) — so we # must NOT import homeModules.niri again. The same auto-wiring pulls in @@ -23,9 +23,11 @@ in swaylock brightnessctl playerctl + ] ++ lib.optionals features.gui [ + # Desktop chrome — skipped on gui = false hosts (eink). pavucontrol networkmanagerapplet # nm-connection-editor for the bar's on-click - xwayland-satellite # X11 apps under niri + xwayland-satellite # X11 apps; niri finds it on PATH and spawns it on demand ]; programs = { @@ -48,9 +50,6 @@ in warp-mouse-to-focus.enable = true; }; - # Monitors (per-host). - outputs = niriOutputs; - layout = { gaps = 8; center-focused-column = "never"; @@ -58,22 +57,23 @@ in { proportion = 0.33333; } { proportion = 0.5; } { proportion = 0.66667; } + { proportion = 1.0; } ]; - default-column-width.proportion = 0.5; + # Open full width — no half-empty screen with a lone window. Mod+R + # cycles down to ⅓/½/⅔ when tiling two side by side. + default-column-width.proportion = 1.0; # focus-ring / border colors come from stylix (niri-flake stylix target). }; + # niri keeps each column at its opened width; niri-even-widths (scripts/) + # rebalances landscape workspaces to 1 / min(n,3) so a lone window is full + # width, two split ½, three+ hold at ⅓ and scroll. Skipped on headless. + spawn-at-startup = lib.optionals features.gui [{ argv = [ "niri-even-widths" ]; }]; + prefer-no-csd = true; screenshot-path = "~/Pictures/Screenshots/Screenshot-%Y-%m-%d-%H-%M-%S.png"; hotkey-overlay.skip-at-startup = true; - # X11 apps: niri manages xwayland-satellite itself (finds it on PATH, - # spawns on demand). enable defaults true, path null → uses PATH. - - # niri-vert-scroll (scripts/niri-vert-scroll.clj) makes portrait outputs - # one vertical scrolling column. Only desktop has a portrait monitor. - spawn-at-startup = lib.optionals (!isPocket) [{ argv = [ "niri-vert-scroll" ]; }]; - window-rules = [{ geometry-corner-radius = let r = 6.0; in { top-left = r; top-right = r; bottom-left = r; bottom-right = r; }; @@ -103,8 +103,8 @@ in # Monitors (desktop is dual-head) "Mod+Ctrl+H".action = focus-monitor-left; "Mod+Ctrl+L".action = focus-monitor-right; - "Mod+Shift+Ctrl+H".action = move-column-to-monitor-left; - "Mod+Shift+Ctrl+L".action = move-column-to-monitor-right; + "Mod+Shift+Ctrl+H".action = move-window-to-monitor-left; + "Mod+Shift+Ctrl+L".action = move-window-to-monitor-right; "Mod+Home".action = focus-column-first; "Mod+End".action = focus-column-last; @@ -169,7 +169,7 @@ in # ---- Waybar: KDE-style bottom panel ------------------------------- waybar = { - enable = true; + enable = features.gui; systemd.enable = true; settings.main = { layer = "top"; @@ -224,7 +224,7 @@ in fuzzel.enable = true; }; - services.mako.enable = true; + services.mako.enable = features.gui; # Network management is the waybar `network` module (on-click # nm-connection-editor) + blueman for bluetooth; no separate nm-applet tray. # The polkit agent comes from niri-flake (niri-flake-polkit / polkit-kde). diff --git a/home/linux/niri/host/desktop.nix b/home/linux/niri/host/desktop.nix index 068ccc7b..8498e32c 100644 --- a/home/linux/niri/host/desktop.nix +++ b/home/linux/niri/host/desktop.nix @@ -1,23 +1,31 @@ -# niri `outputs` for the desktop, imported as an attrset by ../default.nix -# into settings.outputs. Layout mirrors the KDE setup (via kscreen-doctor): +# niri config for the desktop. Monitor layout mirrors the old KDE setup +# (via kscreen-doctor): # DP-1 2560x1440 landscape, main # DP-3 rotated 270° portrait panel to its right (logical 1440x2560) # If a monitor comes up upside-down, swap rotation 270 <-> 90. +_: { - "DP-1" = { - mode = { width = 2560; height = 1440; }; - scale = 1.0; - position = { x = 0; y = 282; }; - }; + programs.niri.settings = { + # niri-vert-scroll (scripts/niri-vert-scroll.clj) makes portrait outputs + # one vertical scrolling column; only this host has one (DP-3). + spawn-at-startup = [{ argv = [ "niri-vert-scroll" ]; }]; + + outputs = { + "DP-1" = { + mode = { width = 2560; height = 1440; }; + scale = 1.0; + position = { x = 0; y = 282; }; + }; - # Portrait. niri-vert-scroll (scripts/niri-vert-scroll.clj) makes this a - # single vertical scrolling column — niri has no native vertical layout - # (issue #1071). The typed outputs DSL has no per-output layout option, so - # the script also widens the consolidated column to 100% itself. - "DP-3" = { - mode = { width = 2560; height = 1440; }; - scale = 1.0; - transform.rotation = 270; - position = { x = 2560; y = 0; }; + # Portrait. niri has no native vertical layout (issue #1071) and the + # typed outputs DSL has no per-output layout option — niri-vert-scroll + # (spawned above) handles it. + "DP-3" = { + mode = { width = 2560; height = 1440; }; + scale = 1.0; + transform.rotation = 270; + position = { x = 2560; y = 0; }; + }; + }; }; } diff --git a/home/linux/niri/host/eink.nix b/home/linux/niri/host/eink.nix new file mode 100644 index 00000000..8c17fc90 --- /dev/null +++ b/home/linux/niri/host/eink.nix @@ -0,0 +1,45 @@ +# niri extras for the eink box (e-ink monitor), ported from the old sway +# setup. No outputs block: connector name unconfirmed, niri auto-configures; +# pin one down with `niri msg outputs` if needed. +{ config, lib, pkgs, theme, ... }: +{ + home.packages = with pkgs; [ + foliate + zathura + ]; + + programs.niri.settings = { + # E-ink: animations just smear into ghosting. + animations.enable = false; + # Old sway setup: seat * hide_cursor 3000. + cursor.hide-after-inactive-ms = 3000; + # White backdrop + zero gaps (old sway: `bg #ffffff solid_color`, gaps 0) + # — niri's default dark backdrop is a permanent dark field on the panel. + layout = { + background-color = "#ffffff"; + gaps = lib.mkForce 0; + }; + # Don't drag the cursor across the panel on keyboard focus changes. + input.warp-mouse-to-focus.enable = lib.mkForce false; + + spawn-at-startup = [{ argv = [ "ghostty" ]; }]; + + binds = with config.lib.niri.actions; { + "Mod+N".action = spawn "ghostty" "-e" "nvim"; + "Mod+B".action = spawn "foliate"; + }; + }; + + # Big legible launcher (ported from the old sway host file); stylix's + # fuzzel target otherwise lands at ~10pt sans on a 20pt-baseline panel. + programs.fuzzel.settings = { + main = { + font = lib.mkForce "MonoLisa Nerd Font:size=${toString (theme.baseFontSize + 2)}"; + lines = 10; + width = 40; + horizontal-pad = 20; + vertical-pad = 10; + }; + border.width = 2; + }; +} diff --git a/home/linux/niri/host/pocket.nix b/home/linux/niri/host/pocket.nix index 83c7353d..2c6d822b 100644 --- a/home/linux/niri/host/pocket.nix +++ b/home/linux/niri/host/pocket.nix @@ -1,15 +1,17 @@ -# niri `outputs` for the GPD Pocket, imported as an attrset by ../default.nix -# into settings.outputs. The internal panel is mounted portrait, so the primary -# output is rotated to landscape. CONFIRM the connector name + rotation on first -# boot: +# niri outputs for the GPD Pocket. The internal panel is mounted portrait, so +# the primary output is rotated to landscape. CONFIRM the connector name + +# rotation on first boot: # niri msg outputs # If the name isn't "DSI-1" (some units report "eDP-1"), rename below. If the # image is sideways the wrong way, swap rotation 270 <-> 90. +_: { - "DSI-1" = { - mode = { width = 1200; height = 1920; }; - scale = 1.5; - transform.rotation = 270; - position = { x = 0; y = 0; }; + programs.niri.settings.outputs = { + "DSI-1" = { + mode = { width = 1200; height = 1920; }; + scale = 1.5; + transform.rotation = 270; + position = { x = 0; y = 0; }; + }; }; } diff --git a/home/linux/sway/default.nix b/home/linux/sway/default.nix deleted file mode 100644 index 0e8f36b1..00000000 --- a/home/linux/sway/default.nix +++ /dev/null @@ -1,65 +0,0 @@ -{ lib, hostName, ... }: -{ - imports = [ - ./host/${hostName}.nix - ]; - - wayland.windowManager.sway = { - enable = true; - systemd.enable = true; - - config = { - modifier = "Mod4"; # Super key - terminal = "ghostty"; - - # No title bars, simple borders - window = { - titlebar = false; - border = 2; - }; - - # Simple gaps - gaps = { - inner = 0; - outer = 0; - }; - - # Default to fullscreen for focused apps - defaultWorkspace = "workspace number 1"; - - # Minimal keybindings - keybindings = - let - mod = "Mod4"; - in - lib.mkOptionDefault { - "${mod}+Return" = "exec ghostty"; - "${mod}+d" = "exec fuzzel"; - "${mod}+q" = "kill"; - "${mod}+Shift+e" = "exec swaymsg exit"; - - # Focus - "${mod}+h" = "focus left"; - "${mod}+j" = "focus down"; - "${mod}+k" = "focus up"; - "${mod}+l" = "focus right"; - - # Move - "${mod}+Shift+h" = "move left"; - "${mod}+Shift+j" = "move down"; - "${mod}+Shift+k" = "move up"; - "${mod}+Shift+l" = "move right"; - - # Fullscreen - "${mod}+f" = "fullscreen toggle"; - }; - - bars = [ ]; # No bar by default, can be overridden per-host - }; - }; - - # Fuzzel launcher - minimal and fast - programs.fuzzel = { - enable = true; - }; -} diff --git a/home/linux/sway/host/eink.nix b/home/linux/sway/host/eink.nix deleted file mode 100644 index 3de041bf..00000000 --- a/home/linux/sway/host/eink.nix +++ /dev/null @@ -1,56 +0,0 @@ -{ pkgs, lib, theme, ... }: -{ - wayland.windowManager.sway = { - config = { - fonts = { - names = lib.mkForce [ "MonoLisa Nerd Font" ]; - size = lib.mkForce (theme.baseFontSize * 1.0); - }; - - keybindings = - let - mod = "Mod4"; - in - lib.mkOptionDefault { - "${mod}+n" = "exec ghostty -e nvim"; - "${mod}+b" = "exec foliate"; - }; - - startup = [ - { command = "ghostty"; } - ]; - - output = { - "*" = { - bg = "#ffffff solid_color"; - }; - }; - }; - - extraConfig = '' - seat * hide_cursor 3000 - for_window [class=".*"] opacity 1 - for_window [app_id=".*"] opacity 1 - ''; - }; - - programs.fuzzel = { - settings = { - main = { - font = lib.mkForce "MonoLisa Nerd Font:size=${toString (theme.baseFontSize + 2)}"; - lines = 10; - width = 40; - horizontal-pad = 20; - vertical-pad = 10; - }; - border = { - width = 2; - }; - }; - }; - - home.packages = with pkgs; [ - foliate - zathura - ]; -} diff --git a/lib/default.nix b/lib/default.nix index e3426887..f9c9e5c6 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -31,7 +31,7 @@ let }; in assert builtins.elem t.appearance [ "light" "dark" ]; - assert builtins.elem f.desktopEnvironment [ null "sway" "niri" "macos" ]; + assert builtins.elem f.desktopEnvironment [ null "niri" "macos" ]; { inherit inputs username system versions; theme = t; diff --git a/outputs/hosts.nix b/outputs/hosts.nix index 1bc5dce4..c96f922e 100644 --- a/outputs/hosts.nix +++ b/outputs/hosts.nix @@ -38,7 +38,7 @@ in system = "x86_64-linux"; username = "drakeb"; format = "nixos"; - features = { desktopEnvironment = "sway"; gui = false; }; + features = { desktopEnvironment = "niri"; gui = false; }; theme = { appearance = "light"; baseFontSize = 20; scheme = "primer-light"; }; autologin = true; extraSystemModules = chaletArgs; diff --git a/scripts/niri-even-widths.clj b/scripts/niri-even-widths.clj new file mode 100644 index 00000000..6f58124d --- /dev/null +++ b/scripts/niri-even-widths.clj @@ -0,0 +1,76 @@ +;; Even column widths on landscape workspaces — niri has no native +;; auto-rebalance (columns keep whatever width they opened at). One column +;; fills the screen; a 2nd splits to ½; a 3rd (and beyond) holds at ⅓ so the +;; row overflows and scrolls. Sibling of niri-vert-scroll.clj, which does the +;; same even-split for *height* on portrait outputs. + +(require '[cheshire.core :as json] + '[babashka.process :as p] + '[clojure.java.io :as io]) + +(def max-visible 3) + +;; nil on failure — niri msg errors transiently; a throw would kill the daemon. +(defn niri-json [& args] + (try (-> (apply p/shell {:out :string} "niri" "msg" "--json" args) + :out (json/parse-string true)) + (catch Exception _ nil))) + +(defn niri-action [& args] + (try (apply p/shell {:out :string :err :string} "niri" "msg" "action" args) + (catch Exception _ nil))) + +(defn landscape-ws-ids [] + (let [landscape (->> (niri-json "outputs") vals + (filter #(>= (get-in % [:logical :width] 0) + (get-in % [:logical :height] 0))) + (map :name) set)] + (->> (niri-json "workspaces") + (filter #(contains? landscape (:output %))) + (map :id) set))) + +(defn column-of [w] (get-in w [:layout :pos_in_scrolling_layout 0])) + +;; Rebalance any workspace whose column count changed since we last saw it. +;; Keyed on count so our own set-window-width (which re-fires +;; WindowOpenedOrChanged) is a no-op — it never changes the count, so no loop. +;; set-window-width takes --id, so we resize each column by its first window's +;; id without focusing it (no stolen focus / warped mouse). One windows fetch +;; covers every workspace. +(defn reconcile-all [ws-ids counts] + (let [by-ws (->> (niri-json "windows") + (filter #(and (contains? ws-ids (:workspace_id %)) + (not (:is_floating %)))) + (group-by :workspace_id))] + (reduce + (fn [acc ws-id] + (let [cols (vals (group-by column-of (get by-ws ws-id []))) + n (count cols)] + (if (= n (get counts ws-id)) + acc + (do (when (pos? n) + (let [pct (str (quot 100 (min n max-visible)) "%")] + (doseq [col cols] + (niri-action "set-window-width" pct "--id" + (str (:id (first col))))))) + (assoc acc ws-id n))))) + counts + ws-ids))) + +(defn parse-line [s] + (try (json/parse-string s true) (catch Exception _ nil))) + +(defn -main [] + (let [proc (p/process ["niri" "msg" "--json" "event-stream"] {:out :stream})] + (with-open [rdr (io/reader (:out proc))] + (loop [ws-ids (landscape-ws-ids) + counts {}] + (when-let [line (.readLine rdr)] + (let [[ev-type _] (some-> line parse-line first)] + (case ev-type + :WorkspacesChanged (recur (landscape-ws-ids) counts) + :WindowClosed (recur ws-ids (reconcile-all ws-ids counts)) + :WindowOpenedOrChanged (recur ws-ids (reconcile-all ws-ids counts)) + (recur ws-ids counts)))))))) + +(-main) diff --git a/system/nixOS/default.nix b/system/nixOS/default.nix index c94956d8..62b79b9d 100644 --- a/system/nixOS/default.nix +++ b/system/nixOS/default.nix @@ -11,15 +11,10 @@ ./mullvad.nix ./printing.nix ./stylix.nix - ] ++ lib.optionals (features.desktopEnvironment != null) [ - ./greetd.nix ] ++ lib.optionals features.gaming [ ./gaming.nix - ] ++ ( - if features.desktopEnvironment == "sway" - then [ ./sway ] - else if features.desktopEnvironment == "niri" - then [ ./niri ] - else [ ] - ); + ] ++ lib.optionals (features.desktopEnvironment == "niri") [ + ./greetd.nix + ./niri + ]; } diff --git a/system/nixOS/greetd.nix b/system/nixOS/greetd.nix index 2566ea6b..2a210c0f 100644 --- a/system/nixOS/greetd.nix +++ b/system/nixOS/greetd.nix @@ -1,18 +1,14 @@ -{ config, lib, pkgs, username, features, autologin, ... }: -let - de = features.desktopEnvironment; - sessionCommand = - if de == "sway" then "${pkgs.sway}/bin/sway" - else if de == "niri" then "${config.programs.niri.package}/bin/niri-session" - else throw "greetd: unsupported desktopEnvironment: ${de}"; -in +{ config, lib, username, features, autologin, ... }: +# Only imported for niri hosts (system/nixOS/default.nix); fail loud if a new +# DE ever reaches here without its own session command. +assert features.desktopEnvironment == "niri"; { services.greetd.enable = true; programs.regreet.enable = !autologin; services.greetd.settings.default_session = lib.mkIf autologin { - command = sessionCommand; + command = "${config.programs.niri.package}/bin/niri-session"; user = username; }; } diff --git a/system/nixOS/sway/default.nix b/system/nixOS/sway/default.nix deleted file mode 100644 index 34ca2ed2..00000000 --- a/system/nixOS/sway/default.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ pkgs, ... }: -{ - programs.sway = { - enable = true; - wrapperFeatures.gtk = true; - }; - - programs.dconf.enable = true; - - xdg.portal = { - enable = true; - wlr.enable = true; - extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; - }; - - environment.systemPackages = with pkgs; [ - wl-clipboard - grim - slurp - ]; -} From ac694b2c5140d6c47a27ffb3749fb5c33f866e18 Mon Sep 17 00:00:00 2001 From: Drake Bott Date: Tue, 7 Jul 2026 13:08:45 -0500 Subject: [PATCH 5/8] update --- flake.lock | 12 ++++++------ home/common/glide/default.nix | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 51fc66db..0cc8e8d6 100644 --- a/flake.lock +++ b/flake.lock @@ -95,11 +95,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1783124463, - "narHash": "sha256-rIawyN3+D9uqGmoYpTLZ2D6PYEp8xPykto8Uu+ER830=", + "lastModified": 1783380155, + "narHash": "sha256-SwUwKaLrZ+c5y4GoZHKBf1nUsDHVsIUtn2CFloZrV2A=", "owner": "sadjow", "repo": "claude-code-nix", - "rev": "9b05ec91a00ee8edea17b9279d55a5c1f084d754", + "rev": "25d979d1a39d0acdbcb1b8d0b9d83ffdd10b5f43", "type": "github" }, "original": { @@ -514,11 +514,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1782948114, - "narHash": "sha256-AXmz9ho4Lud5CsbrZsuSVwpQZ4o5FgZ1chxBn5cJ8+0=", + "lastModified": 1783279667, + "narHash": "sha256-/NAkDSsve+GNM0Bt6tleJdCGfsTlK89nPjkVOzZMo0s=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9e92285f211dad236540fd617d7e30e0b99bc0e1", + "rev": "f205b5574fd0cb7da5b702a2da51507b7f4fdd1b", "type": "github" }, "original": { diff --git a/home/common/glide/default.nix b/home/common/glide/default.nix index 0620341e..d59567c9 100644 --- a/home/common/glide/default.nix +++ b/home/common/glide/default.nix @@ -3,6 +3,22 @@ programs.glide-browser = { enable = true; + + # Force Kagi as the only search engine. Baked into policies.json via + # wrapFirefox; verify at about:policies. SearchEngines.Remove needs an + # ESR-style build to honor it — if the built-ins linger, glide ignored it. + policies.SearchEngines = { + Default = "Kagi"; + Remove = [ "Google" "Bing" "DuckDuckGo" "Amazon.com" "eBay" "Wikipedia (en)" ]; + Add = [{ + Name = "Kagi"; + URLTemplate = "https://kagi.com/search?q={searchTerms}"; + Method = "GET"; + IconURL = "https://kagi.com/favicon.ico"; + SuggestURLTemplate = "https://kagi.com/api/autosuggest?q={searchTerms}"; + Alias = "kagi"; + }]; + }; }; home.file.".config/glide" = { From c2b92cf800979a5812f34a61d8d628d8d08ada76 Mon Sep 17 00:00:00 2001 From: Drake Bott Date: Tue, 7 Jul 2026 13:26:09 -0500 Subject: [PATCH 6/8] update --- home/common/glide/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/home/common/glide/default.nix b/home/common/glide/default.nix index d59567c9..aabd5fcb 100644 --- a/home/common/glide/default.nix +++ b/home/common/glide/default.nix @@ -9,7 +9,7 @@ # ESR-style build to honor it — if the built-ins linger, glide ignored it. policies.SearchEngines = { Default = "Kagi"; - Remove = [ "Google" "Bing" "DuckDuckGo" "Amazon.com" "eBay" "Wikipedia (en)" ]; + Remove = [ "Google" "Bing" "DuckDuckGo" "Amazon.com" "eBay" "Wikipedia (en)" "Perplexity" ]; Add = [{ Name = "Kagi"; URLTemplate = "https://kagi.com/search?q={searchTerms}"; From cc9a93d915d56694d5b54da4a1eb42de7012ed3d Mon Sep 17 00:00:00 2001 From: Drake Bott Date: Tue, 7 Jul 2026 13:26:51 -0500 Subject: [PATCH 7/8] update --- home/common/glide/default.nix | 3 --- 1 file changed, 3 deletions(-) diff --git a/home/common/glide/default.nix b/home/common/glide/default.nix index aabd5fcb..e072d536 100644 --- a/home/common/glide/default.nix +++ b/home/common/glide/default.nix @@ -4,9 +4,6 @@ programs.glide-browser = { enable = true; - # Force Kagi as the only search engine. Baked into policies.json via - # wrapFirefox; verify at about:policies. SearchEngines.Remove needs an - # ESR-style build to honor it — if the built-ins linger, glide ignored it. policies.SearchEngines = { Default = "Kagi"; Remove = [ "Google" "Bing" "DuckDuckGo" "Amazon.com" "eBay" "Wikipedia (en)" "Perplexity" ]; From 1bc4e1487789cc98a532b18f27373492d318def8 Mon Sep 17 00:00:00 2001 From: Drake Bott Date: Wed, 8 Jul 2026 22:30:22 -0500 Subject: [PATCH 8/8] update --- flake.lock | 24 ++++++++++++------------ home/linux/niri/default.nix | 32 ++++++++++++++++++++++++++++---- home/linux/niri/host/desktop.nix | 4 +--- home/linux/niri/host/eink.nix | 1 - scripts/niri-vert-scroll.clj | 7 +++---- 5 files changed, 44 insertions(+), 24 deletions(-) diff --git a/flake.lock b/flake.lock index 0cc8e8d6..195bae66 100644 --- a/flake.lock +++ b/flake.lock @@ -95,11 +95,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1783380155, - "narHash": "sha256-SwUwKaLrZ+c5y4GoZHKBf1nUsDHVsIUtn2CFloZrV2A=", + "lastModified": 1783545757, + "narHash": "sha256-ujyh71rjKf0frYqn4HDZ7baSp5+BNhYpux5Q/8nITRM=", "owner": "sadjow", "repo": "claude-code-nix", - "rev": "25d979d1a39d0acdbcb1b8d0b9d83ffdd10b5f43", + "rev": "1a4322b6de8b5f99968cb342ac4a1b25d0712698", "type": "github" }, "original": { @@ -385,11 +385,11 @@ "xwayland-satellite-unstable": "xwayland-satellite-unstable" }, "locked": { - "lastModified": 1783267733, - "narHash": "sha256-DTvaM+0vanneOcKg3NKXV7ilf0ShY4BOBz/nde5GHos=", + "lastModified": 1783526091, + "narHash": "sha256-Zkkil0e8Wg5BcBmJkWJwBXlETefYQbYKw72xJUIBPLE=", "owner": "sodiboo", "repo": "niri-flake", - "rev": "74053f79cad0f6c3a4a0be6b7928795d2e6a9f4b", + "rev": "a24d4c0dce53fd8dbeb9ad20411e282c7b9875f1", "type": "github" }, "original": { @@ -418,11 +418,11 @@ "niri-unstable": { "flake": false, "locked": { - "lastModified": 1783248941, - "narHash": "sha256-citgYJbvR1iUKXpiWlFq3NO+fw6hXGL3Qk1ampU7lhA=", + "lastModified": 1783522755, + "narHash": "sha256-dI0HkX1djETia7cD/Y64h8BNIsSOfTRMzfNum2J6UhE=", "owner": "YaLTeR", "repo": "niri", - "rev": "a30ca7983b2f1fc3ddeb209b3fe18fa78e0dbd25", + "rev": "0777769e719b7c9b7c980d4ea66288bfbb4da5b3", "type": "github" }, "original": { @@ -670,11 +670,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1783148766, - "narHash": "sha256-uslt2pqShTIXDdAHRHv2QkYLsVdY8Oqwz0EA48/RSM8=", + "lastModified": 1783389287, + "narHash": "sha256-0xIy4dVLqq47rA+mRy0hXDfjhQd4E5PoIns/RmB7nR4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "a50de1b7d8a586adc18d2395c19de7d6058e6030", + "rev": "0ad6f47ea4fe188f4bc8f0380f93ae8523337c6c", "type": "github" }, "original": { diff --git a/home/linux/niri/default.nix b/home/linux/niri/default.nix index 5a064493..2f2e0d03 100644 --- a/home/linux/niri/default.nix +++ b/home/linux/niri/default.nix @@ -2,14 +2,13 @@ let isPocket = hostName == "pocket"; - # Right-hand modules: laptop-only extras (backlight, battery) folded in. + # Waybar's right-side modules; laptop-only extras added on pocket. rightModules = [ "tray" "pulseaudio" "network" ] - ++ lib.optionals isPocket [ "backlight" "battery" ] + ++ lib.optionals isPocket [ "custom/cellular" "backlight" "battery" ] ++ [ "clock" ]; in { - # Per-host module: settings.outputs + host-specific extras. imports = [ ./host/${hostName}.nix ]; # `programs.niri` here comes from niri-flake's NixOS module, which injects its @@ -63,8 +62,13 @@ in # cycles down to ⅓/½/⅔ when tiling two side by side. default-column-width.proportion = 1.0; # focus-ring / border colors come from stylix (niri-flake stylix target). + # Empty-workspace + overview backdrop have no stylix target, so with + # image = null they'd fall back to niri's gray — wire them by hand. + background-color = "#${config.lib.stylix.colors.base00}"; }; + overview.backdrop-color = "#${config.lib.stylix.colors.base01}"; + # niri keeps each column at its opened width; niri-even-widths (scripts/) # rebalances landscape workspaces to 1 / min(n,3) so a lone window is full # width, two split ½, three+ hold at ⅓ and scroll. Skipped on headless. @@ -88,7 +92,7 @@ in "Mod+Q".action = close-window; "Mod+Alt+L".action = spawn "swaylock"; - # Focus (vim keys, matching the old sway setup) + # Focus (vim keys) "Mod+H".action = focus-column-left; "Mod+L".action = focus-column-right; "Mod+J".action = focus-window-down; @@ -202,6 +206,26 @@ in tooltip-format = "{ifname}: {ipaddr}"; on-click = "nm-connection-editor"; }; + # Cellular (pocket): waybar's `network` module has no modem support, so + # poll ModemManager directly. `-m any` grabs the first modem. + "custom/cellular" = { + exec = pkgs.writeShellScript "waybar-cellular" '' + kv=$(${pkgs.modemmanager}/bin/mmcli -m any --output-keyvalue 2>/dev/null) \ + || { echo '{"text":""}'; exit 0; } + get() { printf '%s\n' "$kv" | ${pkgs.gnused}/bin/sed -n "s/^$1 *: //p"; } + state=$(get modem.generic.state) + if [ "$state" != connected ]; then + printf '{"text":"󰣲","tooltip":"cellular: %s"}\n' "''${state:-off}"; exit 0 + fi + printf '{"text":"󰄋 %s%%","tooltip":"%s · %s"}\n' \ + "$(get modem.generic.signal-quality.value)" \ + "$(get modem.3gpp.operator-name)" \ + "$(get modem.generic.access-technologies.value)" + ''; + return-type = "json"; + interval = 30; + on-click = "nm-connection-editor"; + }; tray = { icon-size = 16; spacing = 8; diff --git a/home/linux/niri/host/desktop.nix b/home/linux/niri/host/desktop.nix index 8498e32c..26b04274 100644 --- a/home/linux/niri/host/desktop.nix +++ b/home/linux/niri/host/desktop.nix @@ -17,9 +17,7 @@ _: position = { x = 0; y = 282; }; }; - # Portrait. niri has no native vertical layout (issue #1071) and the - # typed outputs DSL has no per-output layout option — niri-vert-scroll - # (spawned above) handles it. + # Portrait output; niri-vert-scroll (above) drives its vertical layout. "DP-3" = { mode = { width = 2560; height = 1440; }; scale = 1.0; diff --git a/home/linux/niri/host/eink.nix b/home/linux/niri/host/eink.nix index 8c17fc90..b2d22908 100644 --- a/home/linux/niri/host/eink.nix +++ b/home/linux/niri/host/eink.nix @@ -11,7 +11,6 @@ programs.niri.settings = { # E-ink: animations just smear into ghosting. animations.enable = false; - # Old sway setup: seat * hide_cursor 3000. cursor.hide-after-inactive-ms = 3000; # White backdrop + zero gaps (old sway: `bg #ffffff solid_color`, gaps 0) # — niri's default dark backdrop is a permanent dark field on the panel. diff --git a/scripts/niri-vert-scroll.clj b/scripts/niri-vert-scroll.clj index c0c94aef..93896d17 100644 --- a/scripts/niri-vert-scroll.clj +++ b/scripts/niri-vert-scroll.clj @@ -7,7 +7,7 @@ '[babashka.process :as p] '[clojure.java.io :as io]) -(def config {:max-visible 3}) +(def max-visible 3) ;; nil on failure — niri msg errors transiently; a throw would kill the daemon. (defn niri-json [& args] @@ -32,9 +32,8 @@ (defn apply-heights! [ws-id] (let [wins (filter #(= (:workspace_id %) ws-id) (niri-json "windows")) - maxv (:max-visible config) - overflow (> (count wins) maxv) - pct (str (quot 100 maxv) "%")] + overflow (> (count wins) max-visible) + pct (str (quot 100 max-visible) "%")] (doseq [w wins] (if overflow (niri-action "set-window-height" pct "--id" (str (:id w)))