diff --git a/README.md b/README.md index ac5b352..9c33c7e 100644 --- a/README.md +++ b/README.md @@ -32,4 +32,8 @@ CF_DNS_API_TOKEN= ## Credits -Wallpaper: +Desktop background — `modules/home-manager/sway/assets/cafe-at-night_00_3840x2160.png` +— is "cafe at night" from [tokyo-night/wallpapers][wp] (`misc/cityscape/`), used +unmodified under the MIT license. + +[wp]: https://github.com/tokyo-night/wallpapers/blob/main/misc/cityscape/cafe-at-night_00_3840x2160.png diff --git a/docs/gui.md b/docs/gui.md new file mode 100644 index 0000000..e294c18 --- /dev/null +++ b/docs/gui.md @@ -0,0 +1,116 @@ + + +# GUI (sway) + +Port of the old hyprland setup (removed in `3a09509`). `Mod` is Super; +inside the `swayvm` VM that's the macOS Cmd key (QEMU runs with +full-grab, so cmd shortcuts reach the guest — use Ctrl+Option+G to +release the grab when you need macOS back, e.g. for Cmd+Tab). + +## Keybindings + +| Key | Action | +| --- | --- | +| `Mod+0` | `workspace number 10` | +| `Mod+1` | `workspace number 1` | +| `Mod+2` | `workspace number 2` | +| `Mod+3` | `workspace number 3` | +| `Mod+4` | `workspace number 4` | +| `Mod+5` | `workspace number 5` | +| `Mod+6` | `workspace number 6` | +| `Mod+7` | `workspace number 7` | +| `Mod+8` | `workspace number 8` | +| `Mod+9` | `workspace number 9` | +| `Mod+Alt+q` | `exit` | +| `Mod+Ctrl+h` | `resize shrink width 20px` | +| `Mod+Ctrl+j` | `resize grow height 20px` | +| `Mod+Ctrl+k` | `resize shrink height 20px` | +| `Mod+Ctrl+l` | `resize grow width 20px` | +| `Mod+Shift+0` | `move container to workspace number 10` | +| `Mod+Shift+1` | `move container to workspace number 1` | +| `Mod+Shift+2` | `move container to workspace number 2` | +| `Mod+Shift+3` | `move container to workspace number 3` | +| `Mod+Shift+4` | `move container to workspace number 4` | +| `Mod+Shift+5` | `move container to workspace number 5` | +| `Mod+Shift+6` | `move container to workspace number 6` | +| `Mod+Shift+7` | `move container to workspace number 7` | +| `Mod+Shift+8` | `move container to workspace number 8` | +| `Mod+Shift+9` | `move container to workspace number 9` | +| `Mod+Shift+f` | `floating toggle` | +| `Mod+Shift+h` | `move left` | +| `Mod+Shift+j` | `move down` | +| `Mod+Shift+k` | `move up` | +| `Mod+Shift+l` | `move right` | +| `Mod+Shift+s` | `move scratchpad` | +| `Mod+Shift+space` | `exec rofi -show run` | +| `Mod+Shift+x` | `exec grim -g "$(slurp)" - \| wl-copy` | +| `Mod+b` | `exec pkill waybar \|\| waybar` | +| `Mod+e` | `exec nautilus -w` | +| `Mod+f` | `fullscreen toggle` | +| `Mod+h` | `focus left` | +| `Mod+i` | `exec brain` | +| `Mod+j` | `focus down` | +| `Mod+k` | `focus up` | +| `Mod+l` | `focus right` | +| `Mod+n` | `exec chromium` | +| `Mod+p` | `layout toggle split` | +| `Mod+q` | `kill` | +| `Mod+s` | `scratchpad show` | +| `Mod+space` | `exec rofi -show drun` | +| `Mod+t` | `exec alacritty` | +| `Mod+x` | `exec grim - \| wl-copy` | +| `XF86AudioLowerVolume` | `exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-` | +| `XF86AudioMute` | `exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle` | +| `XF86AudioNext` | `exec playerctl next` | +| `XF86AudioPlay` | `exec playerctl play-pause` | +| `XF86AudioPrev` | `exec playerctl previous` | +| `XF86AudioRaiseVolume` | `exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+` | + +Mouse, with `Mod` held: left-drag moves floating windows, right-drag +resizes them. + +| Wheel | Action | +| --- | --- | +| `Mod+button4` | `workspace prev` | +| `Mod+button5` | `workspace next` | + +## Theme + +Catppuccin mocha, defined once in `modules/home-manager/palette.nix` +and read by sway, waybar, rofi and mako so they can't drift apart. + +| Setting | Value | +| --- | --- | +| accent | `#cba6f7` (mauve) — focused border, selected rofi entry, current workspace, notification border | +| borders | `2px`, no titlebars | +| gaps | `8px` between windows, `4px` around the screen | + +## Window rules + +| Criteria | Command | +| --- | --- | +| `{"app_id":"1Password"}` | `floating enable` | +| `{"app_id":"org.gnome.Nautilus"}` | `floating enable` | + +## Bar (waybar) + +| Position | Modules | +| --- | --- | +| left | `sway/workspaces`, `sway/mode` | +| center | `clock` | +| right | `custom/spotify`, `backlight`, `pulseaudio`, `network`, `battery`, `tray` | + +`Mod+b` restarts it if it dies. + +## The VM + +``` +linux-builder start # only needed when the config changed +nix run .#swayvm +``` + +greetd auto-starts sway as `marco` (password `marco`). The VM boots +fullscreen because QEMU's cocoa display only reports its window size +to the guest at boot. Disk state lives in `swayvm.qcow2` in the +directory you run it from; delete it to reset. Ctrl+Option+G releases +the input grab. diff --git a/docs/hosts.md b/docs/hosts.md index b3c998e..6389a6c 100644 --- a/docs/hosts.md +++ b/docs/hosts.md @@ -15,6 +15,7 @@ | `live` | NixOS | `x86_64-linux` | `26.11` | | `mixer` | NixOS | `x86_64-linux` | `24.11` | | `qraspi` | NixOS | `aarch64-linux` | `26.05` | +| `swayvm` | NixOS | `aarch64-linux` | `25.11` | | `wooper` | NixOS | `aarch64-linux` | `25.05` | | `wraspi` | NixOS | `aarch64-linux` | `25.05` | diff --git a/flake.nix b/flake.nix index 3f42e2e..b9d17e7 100644 --- a/flake.nix +++ b/flake.nix @@ -67,6 +67,7 @@ ./hosts/lamp/configuration.nix self.darwinModules.default self.darwinModules.work + self.darwinModules.linux-builder home-manager.darwinModules.home-manager ]; specialArgs = {inherit inputs;}; @@ -173,6 +174,16 @@ specialArgs = {inherit inputs;}; }; + "swayvm" = nixpkgs.lib.nixosSystem { + system = "aarch64-linux"; + modules = [ + ./hosts/swayvm/configuration.nix + ./modules/nixos/home-manager.nix + self.nixosModules.minimal + ]; + specialArgs = {inherit inputs;}; + }; + "live" = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ @@ -197,6 +208,7 @@ darwinModules = { default = ./modules/darwin/base/default.nix; work = ./modules/darwin/work/default.nix; + linux-builder = ./modules/darwin/linux-builder.nix; }; homeConfigurations = let @@ -262,13 +274,17 @@ nixpkgs.legacyPackages.${system}.alejandra ); - packages = nixpkgs.lib.genAttrs systems (system: { - docs = import ./lib/docs { - pkgs = nixpkgs.legacyPackages.${system}; - inherit (nixpkgs) lib; - inherit inputs; - }; - }); + packages = nixpkgs.lib.genAttrs systems (system: + { + docs = import ./lib/docs { + pkgs = nixpkgs.legacyPackages.${system}; + inherit (nixpkgs) lib; + inherit inputs; + }; + } + // nixpkgs.lib.optionalAttrs (system == "aarch64-darwin") { + swayvm = self.nixosConfigurations.swayvm.config.system.build.vm; + }); apps = nixpkgs.lib.genAttrs systems (system: let pkgs = nixpkgs.legacyPackages.${system}; diff --git a/hosts/swayvm/configuration.nix b/hosts/swayvm/configuration.nix new file mode 100644 index 0000000..2d2ae0b --- /dev/null +++ b/hosts/swayvm/configuration.nix @@ -0,0 +1,87 @@ +{ + inputs, + pkgs, + lib, + ... +}: { + networking.hostName = "swayvm"; + + programs.sway.enable = true; + hardware.graphics.enable = true; + + modules.home-manager.enable = true; + home-manager.backupFileExtension = "bak"; + home-manager.users.marco = { + imports = [../../modules/home-manager/sway/default.nix]; + # qemu's cocoa display maps guest pixels to macos points (no hidpi + # backing), so fonts render double: halve the base config's 18 + programs.alacritty.settings.font.size = lib.mkForce 9; + }; + + fonts.packages = [pkgs.nerd-fonts.meslo-lg]; + + # the minimal profile ships almost no terminfo; alacritty's own comes + # with the package, this covers root shells and anything ssh'ing in + environment.enableAllTerminfo = true; + + services.pipewire = { + enable = true; + pulse.enable = true; + }; + security.rtkit.enable = true; + + xdg.portal = { + enable = true; + wlr.enable = true; + extraPortals = [pkgs.xdg-desktop-portal-gtk]; + }; + + services.greetd = { + enable = true; + settings.default_session = { + command = "${pkgs.sway}/bin/sway"; + user = "marco"; + }; + }; + + environment.sessionVariables = { + # no 3d acceleration in the vm, let wlroots use llvmpipe + WLR_RENDERER_ALLOW_SOFTWARE = "1"; + WLR_NO_HARDWARE_CURSORS = "1"; + }; + + system.autoUpgrade.enable = false; + + # dummy boot/fs config so the non-vm system still evaluates + fileSystems."/" = { + device = "/dev/disk/by-label/nixos"; + fsType = "ext4"; + }; + boot.loader.systemd-boot.enable = true; + + virtualisation.vmVariant = { + virtualisation = { + host.pkgs = inputs.nixpkgs.legacyPackages.aarch64-darwin; + # no gpu passthrough behind qemu's cocoa display, so everything is + # drawn by llvmpipe on the cpu — it scales with cores, and the ram + # keeps the 9p-mounted host store in page cache between launches + cores = 8; + memorySize = 8192; + diskSize = 8192; + qemu.options = [ + "-device virtio-gpu-pci" + "-device virtio-tablet-pci" + # cocoa only reports window size to the guest at boot, so start + # fullscreen; zoom-to-fit scales the picture if windowed later. + # full-grab captures cmd shortcuts so cmd works as sway's mod. + # no show-cursor: it sets cursor_hide=0 in ui/cocoa.m, which turns + # the [NSCursor hide] on grab into a no-op and leaves the macOS + # pointer floating over sway's own cursor + "-display cocoa,full-screen=on,zoom-to-fit=on,full-grab=on" + ]; + }; + users.users.marco.initialPassword = "marco"; + }; + + system.stateVersion = "25.11"; +} diff --git a/lib/docs/default.nix b/lib/docs/default.nix index db4293a..d100639 100644 --- a/lib/docs/default.nix +++ b/lib/docs/default.nix @@ -13,6 +13,10 @@ inherit lib; inherit (inputs) self; }; + "gui.md" = import ./gui.nix { + inherit lib; + inherit (inputs) self; + }; }; in pkgs.linkFarm "docs" (lib.mapAttrsToList (name: text: { diff --git a/lib/docs/gui.nix b/lib/docs/gui.nix new file mode 100644 index 0000000..ace26c0 --- /dev/null +++ b/lib/docs/gui.nix @@ -0,0 +1,97 @@ +# Renders docs/gui.md from the sway home-manager configuration. +{ + lib, + self, +}: let + md = import ./md.nix {inherit lib;}; + + hm = self.nixosConfigurations.swayvm.config.home-manager.users.marco; + cfg = hm.wayland.windowManager.sway.config; + + # /nix/store/-foo/bin/foo -> foo; context discarded so the docs + # don't depend on the vm's linux packages + clean = s: + lib.concatStrings (lib.filter builtins.isString + (builtins.split ''/nix/store/[^ )"']*/bin/'' (builtins.unsafeDiscardStringContext s))); + + prettyKey = k: md.code (lib.replaceStrings ["Mod4"] ["Mod"] k); + + bindRows = + lib.mapAttrsToList (k: cmd: [(prettyKey k) (md.code (clean cmd))]) + cfg.keybindings; + + wheelRows = + map ( + l: let + toks = lib.splitString " " (lib.removePrefix "bindsym --whole-window " (lib.trim l)); + in [ + (prettyKey (lib.head toks)) + (md.code (lib.concatStringsSep " " (lib.tail toks))) + ] + ) (lib.filter (l: lib.hasInfix "bindsym" l) + (lib.splitString "\n" hm.wayland.windowManager.sway.extraConfig)); + + ruleRows = + map ( + w: [(md.code (builtins.toJSON w.criteria)) (md.code w.command)] + ) + cfg.window.commands; + + waybar = lib.head hm.programs.waybar.settings; +in '' + ${md.banner "modules/home-manager/sway/default.nix"} + + # GUI (sway) + + Port of the old hyprland setup (removed in `3a09509`). `Mod` is Super; + inside the `swayvm` VM that's the macOS Cmd key (QEMU runs with + full-grab, so cmd shortcuts reach the guest — use Ctrl+Option+G to + release the grab when you need macOS back, e.g. for Cmd+Tab). + + ## Keybindings + + ${md.table ["Key" "Action"] bindRows} + + Mouse, with `Mod` held: left-drag moves floating windows, right-drag + resizes them. + + ${md.table ["Wheel" "Action"] wheelRows} + + ## Theme + + Catppuccin mocha, defined once in `modules/home-manager/palette.nix` + and read by sway, waybar, rofi and mako so they can't drift apart. + + ${md.table ["Setting" "Value"] [ + ["accent" "${md.code cfg.colors.focused.border} (mauve) — focused border, selected rofi entry, current workspace, notification border"] + ["borders" "${md.code "${toString cfg.window.border}px"}, no titlebars"] + ["gaps" "${md.code "${toString cfg.gaps.inner}px"} between windows, ${md.code "${toString cfg.gaps.outer}px"} around the screen"] + ]} + + ## Window rules + + ${md.table ["Criteria" "Command"] ruleRows} + + ## Bar (waybar) + + ${md.table ["Position" "Modules"] [ + ["left" (md.codeList waybar.modules-left)] + ["center" (md.codeList waybar.modules-center)] + ["right" (md.codeList waybar.modules-right)] + ]} + + `Mod+b` restarts it if it dies. + + ## The VM + + ``` + linux-builder start # only needed when the config changed + nix run .#swayvm + ``` + + greetd auto-starts sway as `marco` (password `marco`). The VM boots + fullscreen because QEMU's cocoa display only reports its window size + to the guest at boot. Disk state lives in `swayvm.qcow2` in the + directory you run it from; delete it to reset. Ctrl+Option+G releases + the input grab. +'' diff --git a/modules/darwin/linux-builder.nix b/modules/darwin/linux-builder.nix new file mode 100644 index 0000000..0429525 --- /dev/null +++ b/modules/darwin/linux-builder.nix @@ -0,0 +1,64 @@ +# on-demand linux builder: the vm doesn't run at boot, start it only +# around builds with the `linux-builder` helper command. +# +# note: the `config` block changes the vm image derivation, which is +# itself an aarch64-linux build. on an already-bootstrapped host just +# `linux-builder start` before switching and the builder rebuilds its +# own image. on a fresh host, comment out `config` for the first +# switch (the stock image comes from the binary cache), then restore +# it and switch again with the builder running. +{ + pkgs, + lib, + ... +}: let + port = 31022; # fixed in the darwin.linux-builder package + builderctl = pkgs.writeShellScriptBin "linux-builder" '' + set -e + case "''${1:-}" in + start) + sudo launchctl kickstart system/org.nixos.linux-builder + printf 'waiting for builder ssh' + until nc -z localhost ${toString port} 2>/dev/null; do + printf . + sleep 1 + done + echo " up" + ;; + stop) + sudo launchctl kill TERM system/org.nixos.linux-builder + ;; + status) + if nc -z localhost ${toString port} 2>/dev/null; then + echo running + else + echo stopped + fi + ;; + *) + echo "usage: linux-builder start|stop|status" >&2 + exit 1 + ;; + esac + ''; +in { + nix.linux-builder = { + enable = true; + ephemeral = true; # wiped on restart, safe to kill + maxJobs = 4; # concurrent derivations + config = { + virtualisation.cores = 8; + virtualisation.darwin-builder = { + memorySize = 12 * 1024; + diskSize = 40 * 1024; + }; + }; + }; + + launchd.daemons.linux-builder.serviceConfig = { + KeepAlive = lib.mkForce false; + RunAtLoad = lib.mkForce false; + }; + + environment.systemPackages = [builderctl]; +} diff --git a/modules/home-manager/base/default.nix b/modules/home-manager/base/default.nix index d725983..5516e51 100644 --- a/modules/home-manager/base/default.nix +++ b/modules/home-manager/base/default.nix @@ -104,6 +104,7 @@ in { programs.zsh = { enable = true; + dotDir = config.home.homeDirectory; # lock legacy default, silences warning autosuggestion.enable = true; syntaxHighlighting.enable = true; oh-my-zsh = { @@ -280,7 +281,6 @@ in { { font.normal.family = "MesloLGSDZ Nerd Font"; font.size = 18; - env.TERM = "xterm-256color"; } // lib.importTOML ./config/alacritty-catppuccin-mocha.toml; }; diff --git a/modules/home-manager/palette.nix b/modules/home-manager/palette.nix new file mode 100644 index 0000000..32debcc --- /dev/null +++ b/modules/home-manager/palette.nix @@ -0,0 +1,32 @@ +# catppuccin mocha. the single source of gui colors: sway, waybar, rofi +# and mako all read from here so they can't drift apart. +{ + base = "#1e1e2e"; + mantle = "#181825"; + crust = "#11111b"; + + surface0 = "#313244"; + surface1 = "#45475a"; + surface2 = "#585b70"; + overlay0 = "#6c7086"; + overlay1 = "#7f849c"; + + text = "#cdd6f4"; + subtext0 = "#a6adc8"; + subtext1 = "#bac2de"; + + rosewater = "#f5e0dc"; + flamingo = "#f2cdcd"; + pink = "#f5c2e7"; + mauve = "#cba6f7"; + red = "#f38ba8"; + maroon = "#eba0ac"; + peach = "#fab387"; + yellow = "#f9e2af"; + green = "#a6e3a1"; + teal = "#94e2d5"; + sky = "#89dceb"; + sapphire = "#74c7ec"; + blue = "#89b4fa"; + lavender = "#b4befe"; +} diff --git a/modules/home-manager/sway/assets/cafe-at-night_00_3840x2160.png b/modules/home-manager/sway/assets/cafe-at-night_00_3840x2160.png new file mode 100644 index 0000000..6fd03f6 Binary files /dev/null and b/modules/home-manager/sway/assets/cafe-at-night_00_3840x2160.png differ diff --git a/modules/home-manager/sway/default.nix b/modules/home-manager/sway/default.nix new file mode 100644 index 0000000..97ea91e --- /dev/null +++ b/modules/home-manager/sway/default.nix @@ -0,0 +1,336 @@ +# sway port of the old hyprland setup (removed in 3a09509), same +# keybinds, tiling behavior and waybar +{ + pkgs, + config, + lib, + ... +}: let + c = import ../palette.nix; + cursorSize = 20; + mod = "Mod4"; + font = "MesloLGSDZ Nerd Font"; + waybar = lib.getExe config.programs.waybar.package; + rofi = lib.getExe config.programs.rofi.package; + brain = pkgs.writeShellScriptBin "brain" '' + # Path to the log file + LOG_FILE="''${HOME}/brain.log" + + # Function to log input + log_input() { + echo "$(date +"%Y-%m-%d %H:%M:%S") - $1" >> "$LOG_FILE" + } + + # Run Rofi and log input + rofi -dmenu -p "log this -> " | while IFS= read -r input; do + if [[ -n $input ]]; then + log_input "$input" + fi + done + ''; +in { + imports = [ + ../waybar + ]; + + home.packages = with pkgs; [ + alacritty + chromium + mako + libnotify + playerctl + nautilus + mpv + grim + slurp + wl-clipboard + pavucontrol + ]; + + xdg.enable = true; + + home.pointerCursor = { + enable = true; + gtk.enable = true; + x11.enable = true; + package = pkgs.bibata-cursors; + name = "Bibata-Modern-Classic"; + size = cursorSize; + }; + + gtk = { + enable = true; + theme = { + package = pkgs.adw-gtk3; + name = "adw-gtk3-dark"; + }; + iconTheme = { + package = pkgs.catppuccin-papirus-folders.override { + flavor = "mocha"; + accent = "mauve"; + }; + name = "Papirus-Dark"; + }; + font = { + name = "Noto Sans"; + size = 11; + }; + gtk3.extraConfig.gtk-application-prefer-dark-theme = true; + # adw-gtk3 is a gtk3 theme; gtk4/libadwaita apps style themselves and + # follow the color-scheme below. null is the >=26.05 default, set + # explicitly to silence the deprecation warning on older stateVersions + gtk4.theme = null; + gtk4.extraConfig.gtk-application-prefer-dark-theme = true; + }; + # libadwaita apps (nautilus) ignore the gtk theme and follow this + dconf.settings."org/gnome/desktop/interface".color-scheme = "prefer-dark"; + + programs.rofi = { + enable = true; # wayland support is in the main rofi package now + font = "${font} 11"; + terminal = lib.getExe pkgs.alacritty; + extraConfig = { + modi = "drun,run,window"; + show-icons = true; + drun-display-format = "{name}"; + display-drun = "󰀻"; + display-run = ""; + display-window = ""; + }; + theme = let + inherit (config.lib.formats.rasi) mkLiteral; + in { + "*" = { + background-color = mkLiteral "transparent"; + text-color = mkLiteral c.text; + }; + "window" = { + width = mkLiteral "36%"; + padding = mkLiteral "16px"; + border = mkLiteral "2px"; + border-radius = mkLiteral "16px"; + border-color = mkLiteral c.mauve; + background-color = mkLiteral c.base; + }; + "mainbox" = { + spacing = mkLiteral "12px"; + children = map mkLiteral ["inputbar" "listview"]; + }; + "inputbar" = { + padding = mkLiteral "10px 14px"; + spacing = mkLiteral "10px"; + border-radius = mkLiteral "10px"; + background-color = mkLiteral c.surface0; + children = map mkLiteral ["prompt" "entry"]; + }; + "prompt" = { + text-color = mkLiteral c.mauve; + }; + "entry" = { + placeholder = "search"; + placeholder-color = mkLiteral c.overlay0; + }; + "listview" = { + lines = 8; + spacing = mkLiteral "4px"; + fixed-height = false; + scrollbar = false; + }; + "element" = { + padding = mkLiteral "8px 12px"; + spacing = mkLiteral "10px"; + border-radius = mkLiteral "10px"; + text-color = mkLiteral c.subtext1; + }; + "element selected" = { + background-color = mkLiteral c.mauve; + text-color = mkLiteral c.crust; + }; + "element-icon" = { + size = mkLiteral "22px"; + vertical-align = mkLiteral "0.5"; + }; + "element-text" = { + vertical-align = mkLiteral "0.5"; + text-color = mkLiteral "inherit"; + }; + }; + }; + + services.mako = { + enable = true; + settings = { + font = "${font} 10"; + width = 380; + height = 160; + margin = "16"; + padding = "14"; + border-size = 2; + border-radius = 12; + max-icon-size = 48; + default-timeout = 5000; + background-color = "${c.mantle}f2"; + text-color = c.text; + border-color = c.mauve; + progress-color = "over ${c.surface1}"; + "urgency=low" = { + border-color = c.surface1; + text-color = c.subtext0; + }; + "urgency=high" = { + border-color = c.red; + default-timeout = 0; + }; + }; + }; + + wayland.windowManager.sway = { + enable = true; + package = null; # use the system sway from programs.sway + checkConfig = false; + + config = { + modifier = mod; + floating.modifier = mod; + + fonts = { + names = [font]; + size = 10.0; + }; + + gaps = { + inner = 8; + outer = 4; + }; + + # thin borders, no titlebars: the border colour is the only + # focus indicator + window.border = 2; + window.titlebar = false; + floating.border = 2; + floating.titlebar = false; + + colors = let + entry = border: text: indicator: { + inherit border text indicator; + childBorder = border; + background = c.base; + }; + in { + focused = entry c.mauve c.text c.lavender; + focusedInactive = entry c.surface0 c.subtext0 c.surface0; + unfocused = entry c.surface0 c.overlay1 c.surface0; + urgent = entry c.red c.crust c.red; + placeholder = entry c.surface0 c.subtext0 c.surface0; + background = c.base; + }; + + window.commands = [ + { + criteria.app_id = "1Password"; + command = "floating enable"; + } + { + criteria.app_id = "org.gnome.Nautilus"; + command = "floating enable"; + } + ]; + + input = { + "type:keyboard" = { + xkb_options = "caps:swapescape,compose:rctrl"; + repeat_rate = "50"; + repeat_delay = "250"; + }; + "type:pointer" = { + natural_scroll = "enabled"; + }; + "type:touchpad" = { + natural_scroll = "enabled"; + scroll_factor = "0.2"; + }; + }; + + # tokyo-night/wallpapers, misc/cityscape, MIT — credited in README + output."*".bg = "${./assets/cafe-at-night_00_3840x2160.png} fill"; + + bars = []; # waybar instead of swaybar + startup = [{command = waybar;}]; + + keybindings = { + "${mod}+t" = "exec ${lib.getExe pkgs.alacritty}"; + "${mod}+n" = "exec ${lib.getExe pkgs.chromium}"; + "${mod}+q" = "kill"; + "${mod}+e" = "exec ${lib.getExe pkgs.nautilus} -w"; + "${mod}+Shift+f" = "floating toggle"; + "${mod}+f" = "fullscreen toggle"; + "${mod}+p" = "layout toggle split"; + "${mod}+x" = "exec ${lib.getExe pkgs.grim} - | wl-copy"; + "${mod}+Shift+x" = "exec ${lib.getExe pkgs.grim} -g \"$(${lib.getExe pkgs.slurp})\" - | wl-copy"; + "${mod}+space" = "exec ${rofi} -show drun"; + "${mod}+Shift+space" = "exec ${rofi} -show run"; + "${mod}+i" = "exec ${brain}/bin/brain"; + "${mod}+b" = "exec pkill waybar || ${waybar}"; + "${mod}+Alt+q" = "exit"; + + "${mod}+h" = "focus left"; + "${mod}+l" = "focus right"; + "${mod}+k" = "focus up"; + "${mod}+j" = "focus down"; + "${mod}+Shift+h" = "move left"; + "${mod}+Shift+l" = "move right"; + "${mod}+Shift+k" = "move up"; + "${mod}+Shift+j" = "move down"; + + "${mod}+1" = "workspace number 1"; + "${mod}+2" = "workspace number 2"; + "${mod}+3" = "workspace number 3"; + "${mod}+4" = "workspace number 4"; + "${mod}+5" = "workspace number 5"; + "${mod}+6" = "workspace number 6"; + "${mod}+7" = "workspace number 7"; + "${mod}+8" = "workspace number 8"; + "${mod}+9" = "workspace number 9"; + "${mod}+0" = "workspace number 10"; + "${mod}+Shift+1" = "move container to workspace number 1"; + "${mod}+Shift+2" = "move container to workspace number 2"; + "${mod}+Shift+3" = "move container to workspace number 3"; + "${mod}+Shift+4" = "move container to workspace number 4"; + "${mod}+Shift+5" = "move container to workspace number 5"; + "${mod}+Shift+6" = "move container to workspace number 6"; + "${mod}+Shift+7" = "move container to workspace number 7"; + "${mod}+Shift+8" = "move container to workspace number 8"; + "${mod}+Shift+9" = "move container to workspace number 9"; + "${mod}+Shift+0" = "move container to workspace number 10"; + + # special workspace "magic" -> scratchpad + "${mod}+s" = "scratchpad show"; + "${mod}+Shift+s" = "move scratchpad"; + + "${mod}+Ctrl+h" = "resize shrink width 20px"; + "${mod}+Ctrl+l" = "resize grow width 20px"; + "${mod}+Ctrl+k" = "resize shrink height 20px"; + "${mod}+Ctrl+j" = "resize grow height 20px"; + + "XF86AudioRaiseVolume" = "exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"; + "XF86AudioLowerVolume" = "exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"; + "XF86AudioMute" = "exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; + "XF86AudioPlay" = "exec playerctl play-pause"; + "XF86AudioNext" = "exec playerctl next"; + "XF86AudioPrev" = "exec playerctl previous"; + }; + }; + + extraConfig = '' + # sway names the first workspace after whichever `workspace number` + # binding it happens to hash first, which lands on 10 as often as + # not. start on 1. + workspace number 1 + + # workspace switching with the scroll wheel, like hyprland's + # mod+mouse_down/mouse_up + bindsym --whole-window ${mod}+button4 workspace prev + bindsym --whole-window ${mod}+button5 workspace next + ''; + }; +} diff --git a/modules/home-manager/waybar/default.nix b/modules/home-manager/waybar/default.nix new file mode 100644 index 0000000..e0a9d0c --- /dev/null +++ b/modules/home-manager/waybar/default.nix @@ -0,0 +1,235 @@ +{lib, ...}: let + c = import ../palette.nix; + font = "MesloLGSDZ Nerd Font"; + # every palette entry becomes a gtk named color, so the stylesheet + # below never spells out a hex + colorDefs = lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: hex: "@define-color ${name} ${hex};") c + ); +in { + programs.waybar = { + enable = true; + style = '' + ${colorDefs} + + * { + font-family: "${font}", sans-serif; + font-size: 11pt; + font-weight: 500; + min-height: 0; + border: none; + box-shadow: none; + text-shadow: none; + } + + window#waybar { + background: transparent; + color: @text; + } + + /* one floating pill per section instead of one per module */ + .modules-left, + .modules-center, + .modules-right { + background-color: alpha(@base, 0.88); + border: 1px solid alpha(@surface1, 0.6); + border-radius: 14px; + padding: 0 4px; + } + + #workspaces button { + color: @overlay1; + padding: 0 9px; + margin: 4px 2px; + border-radius: 10px; + transition: background-color 0.2s ease, color 0.2s ease; + } + #workspaces button:hover { + background-color: alpha(@surface0, 0.9); + color: @text; + } + #workspaces button.visible { + color: @subtext1; + } + #workspaces button.focused { + background-color: @mauve; + color: @crust; + } + #workspaces button.urgent { + background-color: @red; + color: @crust; + } + + #mode { + color: @crust; + background-color: @peach; + padding: 0 10px; + margin: 4px 2px; + border-radius: 10px; + } + + #clock { + color: @text; + font-weight: 600; + padding: 0 16px; + } + + #custom-spotify, + #backlight, + #pulseaudio, + #network, + #battery, + #tray { + padding: 0 10px; + margin: 4px 0; + } + /* thin separators inside the right pill */ + #backlight, + #pulseaudio, + #network, + #battery { + border-left: 1px solid alpha(@surface1, 0.5); + } + + #custom-spotify { + color: @green; + } + #backlight { + color: @peach; + } + #pulseaudio { + color: @sky; + } + #pulseaudio.muted { + color: @overlay1; + } + #network { + color: @teal; + } + #network.disconnected { + color: @overlay1; + } + #battery { + color: @green; + } + #battery.warning { + color: @yellow; + } + #battery.critical { + color: @red; + } + /* only a nearly dead battery earns the right to flash */ + #battery.critical:not(.charging) { + animation: blink 1s linear infinite alternate; + } + @keyframes blink { + to { + background-color: @red; + color: @crust; + } + } + + tooltip { + background-color: @mantle; + border: 1px solid @surface1; + border-radius: 10px; + } + tooltip label { + color: @text; + padding: 4px; + } + ''; + settings = [ + { + layer = "top"; + position = "top"; + height = 34; + margin-top = 6; + margin-left = 10; + margin-right = 10; + spacing = 6; + modules-left = [ + "sway/workspaces" + "sway/mode" + ]; + modules-center = [ + "clock" + ]; + modules-right = [ + "custom/spotify" + "backlight" + "pulseaudio" + "network" + "battery" + "tray" + ]; + pulseaudio = { + scroll-step = 1; + format = "{icon} {volume}%"; + format-muted = "󰖁 muted"; + format-icons = { + headphone = "󰋋"; + default = ["󰕿" "󰖀" "󰕾"]; + }; + on-click = "pavucontrol"; + tooltip = false; + }; + clock = { + interval = 30; + format = "{:%H:%M}"; + format-alt = "{:%a %d %b %H:%M}"; + tooltip-format = "{calendar}"; + calendar = { + mode = "month"; + format = { + today = "{}"; + days = "{}"; + weekdays = "{}"; + }; + }; + }; + network = { + format-disconnected = "󰯡 offline"; + format-ethernet = "󰈀 {ifname}"; + format-linked = "󰖪 {essid}"; + format-wifi = "󰖩 {essid}"; + interval = 5; + tooltip-format = "{ifname} {ipaddr}/{cidr}"; + }; + tray = { + spacing = 8; + icon-size = 16; + }; + "custom/spotify" = { + format = " {}"; + escape = true; + interval = 5; + max-length = 40; + exec = "playerctl -f '{{artist}} - {{title}}' -p spotify metadata"; + exec-if = "pgrep spotify"; + }; + "sway/workspaces" = { + format = "{name}"; + }; + "sway/mode" = { + format = "{}"; + }; + battery = { + states = { + warning = 30; + critical = 15; + }; + format = "{icon} {capacity}%"; + format-charging = "󰂄 {capacity}%"; + format-plugged = "󰚥 {capacity}%"; + format-icons = ["󰁺" "󰁼" "󰁾" "󰂀" "󰂂" "󰁹"]; + tooltip-format = "{timeTo}"; + }; + backlight = { + format = "{icon} {percent}%"; + format-icons = ["󰃞" "󰃟" "󰃠"]; + }; + } + ]; + }; +}