network: iwd + networkd instead of NetworkManager; hyprland: AeroSpace-style workspaces

Wi-Fi kept failing with "Secrets were required, but not provided". NM ran
on top of iwd while impala (SUPER+W) drove iwd directly, so two managers
held separate saved networks; iwd couldn't load its own .psk files, asked
NM for the passphrase, and NM had no secret agent to answer. impala is
iwd-only, so NM goes instead: iwd owns Wi-Fi, networkd does DHCP and
resolved does DNS, on both hosts. eduroam's iwd profile is seeded once via
tmpfiles (C, so a password added later is never overwritten). On mbp,
resolved's mDNS is off because avahi owns .local.

On pc the GigE camera link (enp7s0) is now declared: static
192.168.12.1/24, no RAs, not required for online. It used to take AAU's
IPv6 RAs on that segment, which made v6 default out a dead-end port and
every v6-first connection stall until fallback.

Drop the virtual-desktops plugin: SUPER+N now switches only the monitor
that workspace lives on, as in AeroSpace. SUPER+SHIFT+N sends the window
without following, SUPER+Tab goes back, SUPER+ALT/SHIFT+Tab sends the
workspace to the next monitor. pc's three screens get workspaces 1-3 as
their homes again.

New windows no longer open maximized: kitty 0.49 requests maximize ~250ms
after mapping, so suppress app maximize requests (SUPER+F still works).

Also bumps zen-browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Mads Fagerlund 2026-10-06 18:37:13 +02:00
parent 420cbd770f
commit e2085e0e86
No known key found for this signature in database
3 changed files with 68 additions and 68 deletions

View file

@ -2,20 +2,6 @@
# Monitor layout is host-specific; written by the flake per host.
source = ~/.config/hypr/monitors.conf
# Virtual desktops plugin. A vdesk spans every monitor, so SUPER+1..5 switches
# all screens together instead of workspaces being pinned per monitor.
source = ~/.config/hypr/plugins.conf
plugin {
virtual-desktops {
# The startup toast would fire on every login now that mako exists.
notifyinit = 0
rememberlayout = size
# Deliberately NOT enabling cycleworkspaces: upstream states it does
# not work with more than 2 monitors, and pc has 3.
}
}
device {
name = apple-spi-keyboard
kb_options = caps:ctrl_modifier,grp:win_space_toggle
@ -62,6 +48,10 @@ dwindle {
force_split = 2
}
# Apps don't get to maximize themselves: kitty (0.49) asks ~250ms after it
# maps, so every new terminal opened maximized. SUPER+F still works.
windowrule = match:class .*, suppress_event maximize
misc {
disable_hyprland_logo = true
disable_splash_rendering = true
@ -126,18 +116,28 @@ bind = SUPER SHIFT, j, movewindow, d
bind = SUPER SHIFT, k, movewindow, u
bind = SUPER SHIFT, l, movewindow, r
# -- Virtual desktops (plugin dispatchers, not Hyprland workspaces) --
bind = SUPER, 1, vdesk, 1
bind = SUPER, 2, vdesk, 2
bind = SUPER, 3, vdesk, 3
bind = SUPER, 4, vdesk, 4
bind = SUPER, 5, vdesk, 5
# -- Workspaces (AeroSpace-style) --
# Each workspace lives on one monitor. SUPER+N shows N on its monitor and
# focuses it, leaving the other screens alone; a new workspace opens on the
# focused monitor. SUPER+ALT+h/l carries a workspace to another monitor.
bind = SUPER, 1, workspace, 1
bind = SUPER, 2, workspace, 2
bind = SUPER, 3, workspace, 3
bind = SUPER, 4, workspace, 4
bind = SUPER, 5, workspace, 5
bind = SUPER SHIFT, 1, movetodesk, 1
bind = SUPER SHIFT, 2, movetodesk, 2
bind = SUPER SHIFT, 3, movetodesk, 3
bind = SUPER SHIFT, 4, movetodesk, 4
bind = SUPER SHIFT, 5, movetodesk, 5
# Send the window without following it, like AeroSpace's move-node-to-workspace.
bind = SUPER SHIFT, 1, movetoworkspacesilent, 1
bind = SUPER SHIFT, 2, movetoworkspacesilent, 2
bind = SUPER SHIFT, 3, movetoworkspacesilent, 3
bind = SUPER SHIFT, 4, movetoworkspacesilent, 4
bind = SUPER SHIFT, 5, movetoworkspacesilent, 5
# AeroSpace's alt-tab / alt-shift-tab: previous workspace, and send the
# current workspace to the next monitor (+1 wraps past the last one).
bind = SUPER, Tab, workspace, previous
bind = SUPER SHIFT, Tab, movecurrentworkspacetomonitor, +1
bind = SUPER ALT, Tab, movecurrentworkspacetomonitor, +1
# -- Move current workspace to another monitor --
bind = SUPER ALT, h, movecurrentworkspacetomonitor, l

View file

@ -78,26 +78,6 @@
"type": "github"
}
},
"hyprland-virtual-desktops": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1790338297,
"narHash": "sha256-iJP+CyrS/fw/dqysRN9GtVB8GlJZcja4rkjBeJsXFsg=",
"owner": "levnikmyskin",
"repo": "hyprland-virtual-desktops",
"rev": "74b88ee5209c4dcea74eedc4646e185b02a8f946",
"type": "github"
},
"original": {
"owner": "levnikmyskin",
"repo": "hyprland-virtual-desktops",
"type": "github"
}
},
"linear-tui-src": {
"flake": false,
"locked": {
@ -136,7 +116,6 @@
"apple-silicon": "apple-silicon",
"firefox-addons": "firefox-addons",
"home-manager": "home-manager",
"hyprland-virtual-desktops": "hyprland-virtual-desktops",
"linear-tui-src": "linear-tui-src",
"nixpkgs": "nixpkgs",
"zen-browser": "zen-browser"
@ -152,11 +131,11 @@
]
},
"locked": {
"lastModified": 1786347712,
"narHash": "sha256-yH+jWXH2TVd5h3J5KoZiE8/4O4HqoggfB0zl9uVj39w=",
"lastModified": 1791266850,
"narHash": "sha256-F/scdmE9Y03TfSQWrBHaJWTRTr3rRQBqvJSDgF48g1c=",
"owner": "0xc000022070",
"repo": "zen-browser-flake",
"rev": "945efbc704b7f8c1731a922aabbc5d95edc9eb74",
"rev": "d9e4e25d6ade5d541074045d332d887c044e11f5",
"type": "github"
},
"original": {

View file

@ -32,18 +32,9 @@
url = "github:k1-c/linear-tui/v0.13.0";
flake = false;
};
# Virtual desktops: one desktop spans all monitors, replacing Hyprland's
# workspace-pinned-to-monitor model. Its own nixpkgs pin is older than
# ours, and Hyprland plugins are ABI-locked to the compositor, so it must
# follow ours to compile against the hyprland we actually run.
hyprland-virtual-desktops = {
url = "github:levnikmyskin/hyprland-virtual-desktops";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, apple-silicon, home-manager, firefox-addons, zen-browser, linear-tui-src, hyprland-virtual-desktops, ... }:
outputs = { self, nixpkgs, apple-silicon, home-manager, firefox-addons, zen-browser, linear-tui-src, ... }:
let
# linear-tui, built from the pinned source input above. Upstream ships no
# flake and it is not in nixpkgs, but it has a committed Cargo.lock, so
@ -93,15 +84,14 @@
monitor = desc:Lenovo Group Limited P27q-20 V909LPNY, 2560x1440@60, 2560x0, 1.0
monitor = desc:Lenovo Group Limited P27q-20 V909LRDD, 2560x1440@60, 5120x0, 1.0
# Each screen's home workspace; SUPER+ALT+h/l can still move it.
workspace = 1, monitor:desc:Lenovo Group Limited P27q-20 V909LRFR, default:true
workspace = 2, monitor:desc:Lenovo Group Limited P27q-20 V909LPNY, default:true
workspace = 3, monitor:desc:Lenovo Group Limited P27q-20 V909LRDD, default:true
# Same panels, sized for 2560x1440 logical.
windowrule = match:class ^(panel-wifi|panel-bt)$, float on, size 1792 1440, center on
'';
# Plugin load line, as a store path. Sourced from hyprland.conf, the
# same pattern as monitors.conf. Upstream installs the library as
# libvirtual-desktops.so (not lib<pname>.so).
"hypr/plugins.conf".text = ''
plugin = ${hyprland-virtual-desktops.packages.${pkgs.stdenv.hostPlatform.system}.virtual-desktops}/lib/libvirtual-desktops.so
'';
"eww".source = ./config/eww;
"fuzzel".source = ./config/fuzzel;
};
@ -379,8 +369,25 @@
}
];
networking.networkmanager.enable = true;
networking.networkmanager.wifi.backend = "iwd";
# iwd owns Wi-Fi (driven from impala, which is iwd-only); networkd does
# DHCP and resolved does DNS. No NetworkManager: running it on top of
# iwd meant two managers with separate saved networks, which ended in
# "Secrets were required, but not provided".
networking.wireless.iwd.enable = true;
networking.useNetworkd = true; # DHCP on any ethernet/wifi by default
systemd.network.wait-online.anyInterface = true; # one link up is enough
# iwd only reads /var/lib/iwd. C = copy once, so a password added to
# the file later (or via impala) is never overwritten.
systemd.tmpfiles.rules = [
"C /var/lib/iwd/eduroam.8021x 0600 root root - ${pkgs.writeText "eduroam.8021x" ''
[Security]
EAP-Method=PEAP
EAP-Identity=447087@net.aau.dk
EAP-PEAP-Phase2-Method=MSCHAPV2
EAP-PEAP-Phase2-Identity=447087@net.aau.dk
''}"
];
# Secret Service (org.freedesktop.secrets) provider, D-Bus-activated on
# demand. Without it 1Password can't persist the 2FA device token
@ -515,7 +522,7 @@
users.users.mads = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "video" "docker" ];
extraGroups = [ "wheel" "video" "docker" ];
shell = pkgs.zsh;
initialPassword = "changeme"; # CHANGE after first boot (`passwd`)
};
@ -602,6 +609,7 @@
enable = true;
nssmdns4 = true;
};
services.resolved.settings.Resolve.MulticastDNS = false; # avahi owns mDNS
# Asahi kernel + Mesa cache, and the Raspberry Pi vendor
# kernel/firmware cache (else both compile from source).
@ -626,6 +634,19 @@
./hosts/pc/hardware-configuration.nix # generated on the PC
({ config, ... }: {
networking.hostName = "pc";
# GigE camera link: static, never a route out. The segment also
# carries AAU's IPv6 RAs, and taking them made v6 traffic default
# out this port into a dead end.
networking.interfaces.enp7s0 = {
useDHCP = false;
ipv4.addresses = [ { address = "192.168.12.1"; prefixLength = 24; } ];
};
systemd.network.networks."40-enp7s0" = {
networkConfig.IPv6AcceptRA = false;
networkConfig.ConfigureWithoutCarrier = true; # camera may be off
linkConfig.RequiredForOnline = "no";
};
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true; # normal x86 UEFI