diff --git a/flake.lock b/flake.lock index ca2b67b..2d38dbe 100644 --- a/flake.lock +++ b/flake.lock @@ -74,6 +74,75 @@ "type": "github" } }, + "hyprland": { + "inputs": { + "hyprland-protocols": "hyprland-protocols", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems", + "wlroots": "wlroots", + "xdph": "xdph" + }, + "locked": { + "lastModified": 1695935601, + "narHash": "sha256-LLlL4EXxupanb3GwSMcogCCsx7WAfd7/u13QkAwyBgQ=", + "owner": "hyprwm", + "repo": "hyprland", + "rev": "3f09b14381e8b28dd2cc1d292763374f2d6c8484", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprland", + "type": "github" + } + }, + "hyprland-protocols": { + "inputs": { + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1691753796, + "narHash": "sha256-zOEwiWoXk3j3+EoF3ySUJmberFewWlagvewDRuWYAso=", + "owner": "hyprwm", + "repo": "hyprland-protocols", + "rev": "0c2ce70625cb30aef199cb388f99e19a61a6ce03", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprland-protocols", + "type": "github" + } + }, + "hyprwm-contrib": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1695455081, + "narHash": "sha256-AtAMze2J5Maol28OLQoCFgppRWEy06Mn9RhduXNmhiI=", + "owner": "hyprwm", + "repo": "contrib", + "rev": "33663f663e07b4ca52c9165f74e3d793f08b15e7", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "contrib", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1695346609, @@ -111,9 +180,74 @@ "firefox-addons": "firefox-addons", "hardware": "hardware", "home-manager": "home-manager", + "hyprland": "hyprland", + "hyprwm-contrib": "hyprwm-contrib", "nixpkgs": "nixpkgs", "nixpkgs-unstable": "nixpkgs-unstable" } + }, + "systems": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + }, + "wlroots": { + "flake": false, + "locked": { + "host": "gitlab.freedesktop.org", + "lastModified": 1695919988, + "narHash": "sha256-4RBgIZHaVqH0m1POnfzYRzwCWxifIKH4xQ0kCn2LGkA=", + "owner": "wlroots", + "repo": "wlroots", + "rev": "c2aa7fd965cb7ee8bed24f4122b720aca8f0fc1e", + "type": "gitlab" + }, + "original": { + "host": "gitlab.freedesktop.org", + "owner": "wlroots", + "repo": "wlroots", + "rev": "c2aa7fd965cb7ee8bed24f4122b720aca8f0fc1e", + "type": "gitlab" + } + }, + "xdph": { + "inputs": { + "hyprland-protocols": [ + "hyprland", + "hyprland-protocols" + ], + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1694628480, + "narHash": "sha256-Qg9hstRw0pvjGu5hStkr2UX1D73RYcQ9Ns/KnZMIm9w=", + "owner": "hyprwm", + "repo": "xdg-desktop-portal-hyprland", + "rev": "8f45a6435069b9e24ebd3160eda736d7a391cbf2", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "xdg-desktop-portal-hyprland", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index aa63f52..57322ef 100644 --- a/flake.nix +++ b/flake.nix @@ -5,6 +5,11 @@ # - https://github.com/Misterio77/nix-starter-configs/ # - https://github.com/Misterio77/nix-config/ + nixConfig = { + extra-substituters = ["https://hyprland.cachix.org"]; + extra-trusted-public-keys = ["hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc="]; + }; + inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-23.05"; # You can access packages and modules from different nixpkgs revs @@ -26,6 +31,16 @@ url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons"; inputs.nixpkgs.follows = "nixpkgs"; }; + + # Hyprland + hyprland = { + url = "github:hyprwm/hyprland"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + hyprwm-contrib = { + url = "github:hyprwm/contrib"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = { self, nixpkgs, home-manager, ... } @inputs: diff --git a/home/features/desktop/common/default.nix b/home/features/desktop/common/default.nix new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/home/features/desktop/common/default.nix @@ -0,0 +1,2 @@ +{ +} diff --git a/home/features/desktop/common/wayland-wm/default.nix b/home/features/desktop/common/wayland-wm/default.nix new file mode 100644 index 0000000..220570e --- /dev/null +++ b/home/features/desktop/common/wayland-wm/default.nix @@ -0,0 +1,36 @@ +{ pkgs, ... }: { + imports = [ + ./gammastep.nix + ./kitty.nix + ./mako.nix + ./swayidle.nix + ./swaylock.nix + ./waybar.nix + ./wofi.nix + ./zathura.nix + ]; + + xdg.mimeApps.enable = true; + home.packages = with pkgs; [ + grim + gtk3 + imv + mimeo + primary-xwayland + pulseaudio + slurp + waypipe + wf-recorder + wl-clipboard + wl-mirror + wl-mirror-pick + xdg-utils-spawn-terminal + ydotool + ]; + + home.sessionVariables = { + MOZ_ENABLE_WAYLAND = 1; + QT_QPA_PLATFORM = "wayland"; + LIBSEAT_BACKEND = "logind"; + }; +} \ No newline at end of file diff --git a/home/features/desktop/common/wayland-wm/gammastep.nix b/home/features/desktop/common/wayland-wm/gammastep.nix new file mode 100644 index 0000000..b63f992 --- /dev/null +++ b/home/features/desktop/common/wayland-wm/gammastep.nix @@ -0,0 +1,13 @@ +{ + services.gammastep = { + enable = true; + provider = "geoclue2"; + temperature = { + day = 6000; + night = 4600; + }; + settings = { + general.adjustment-method = "wayland"; + }; + }; +} \ No newline at end of file diff --git a/home/features/desktop/common/wayland-wm/kitty.nix b/home/features/desktop/common/wayland-wm/kitty.nix new file mode 100644 index 0000000..4d52767 --- /dev/null +++ b/home/features/desktop/common/wayland-wm/kitty.nix @@ -0,0 +1,30 @@ +{ config, pkgs, ... }: + +let + kitty-xterm = pkgs.writeShellScriptBin "xterm" '' + ${config.programs.kitty.package}/bin/kitty -1 "$@" + ''; +in +{ + home = { + packages = [ kitty-xterm ]; + sessionVariables = { + TERMINAL = "kitty -1"; + }; + }; + + + programs.kitty = { + enable = true; + font = { + name = config.fontProfiles.monospace.family; + size = 12; + }; + settings = { + shell_integration = "no-rc"; # I prefer to do it manually + scrollback_lines = 4000; + scrollback_pager_history_size = 2048; + window_padding_width = 15; + }; + }; +} diff --git a/home/features/desktop/common/wayland-wm/mako.nix b/home/features/desktop/common/wayland-wm/mako.nix new file mode 100644 index 0000000..9dcadb4 --- /dev/null +++ b/home/features/desktop/common/wayland-wm/mako.nix @@ -0,0 +1,13 @@ +{ config, ... }: { + services.mako = { + enable = true; + font = "${config.fontProfiles.regular.family} 12"; + padding = "10,20"; + anchor = "top-center"; + width = 400; + height = 150; + borderSize = 2; + defaultTimeout = 12000; + layer = "overlay"; + }; +} diff --git a/home/features/desktop/common/wayland-wm/swayidle.nix b/home/features/desktop/common/wayland-wm/swayidle.nix new file mode 100644 index 0000000..c2b9892 --- /dev/null +++ b/home/features/desktop/common/wayland-wm/swayidle.nix @@ -0,0 +1,54 @@ +{ pkgs, lib, config, ... }: + +let + swaylock = "${config.programs.swaylock.package}/bin/swaylock"; + pgrep = "${pkgs.procps}/bin/pgrep"; + pactl = "${pkgs.pulseaudio}/bin/pactl"; + hyprctl = "${config.wayland.windowManager.hyprland.package}/bin/hyprctl"; + swaymsg = "${config.wayland.windowManager.sway.package}/bin/swaymsg"; + + isLocked = "${pgrep} -x ${swaylock}"; + lockTime = 4 * 60; # TODO: configurable desktop (10 min)/laptop (4 min) + + # Makes two timeouts: one for when the screen is not locked (lockTime+timeout) and one for when it is. + afterLockTimeout = { timeout, command, resumeCommand ? null }: [ + { timeout = lockTime + timeout; inherit command resumeCommand; } + { command = "${isLocked} && ${command}"; inherit resumeCommand timeout; } + ]; +in +{ + services.swayidle = { + enable = true; + systemdTarget = "graphical-session.target"; + timeouts = + # Lock screen + [{ + timeout = lockTime; + command = "${swaylock} -i ${config.wallpaper} --daemonize"; + }] ++ + # Mute mic + (afterLockTimeout { + timeout = 10; + command = "${pactl} set-source-mute @DEFAULT_SOURCE@ yes"; + resumeCommand = "${pactl} set-source-mute @DEFAULT_SOURCE@ no"; + }) ++ + # Turn off RGB + (lib.optionals config.services.rgbdaemon.enable (afterLockTimeout { + timeout = 20; + command = "systemctl --user stop rgbdaemon"; + resumeCommand = "systemctl --user start rgbdaemon"; + })) ++ + # Turn off displays (hyprland) + (lib.optionals config.wayland.windowManager.hyprland.enable (afterLockTimeout { + timeout = 40; + command = "${hyprctl} dispatch dpms off"; + resumeCommand = "${hyprctl} dispatch dpms on"; + })) ++ + # Turn off displays (sway) + (lib.optionals config.wayland.windowManager.sway.enable (afterLockTimeout { + timeout = 40; + command = "${swaymsg} 'output * dpms off'"; + resumeCommand = "${swaymsg} 'output * dpms on'"; + })); + }; +} diff --git a/home/features/desktop/common/wayland-wm/swaylock.nix b/home/features/desktop/common/wayland-wm/swaylock.nix new file mode 100644 index 0000000..e9b8c65 --- /dev/null +++ b/home/features/desktop/common/wayland-wm/swaylock.nix @@ -0,0 +1,21 @@ +{ config, pkgs, ... }: { + programs.swaylock = { + enable = true; + package = pkgs.swaylock-effects; + settings = { + effect-blur = "20x3"; + fade-in = 0.1; + + font = config.fontProfiles.regular.family; + font-size = 15; + + line-uses-inside = true; + disable-caps-lock-text = true; + indicator-caps-lock = true; + indicator-radius = 40; + indicator-idle-visible = true; + indicator-y-position = 1000; + + }; + }; +} diff --git a/home/features/desktop/common/wayland-wm/waybar.nix b/home/features/desktop/common/wayland-wm/waybar.nix new file mode 100644 index 0000000..f0fa0a4 --- /dev/null +++ b/home/features/desktop/common/wayland-wm/waybar.nix @@ -0,0 +1,392 @@ +{ outputs, config, lib, pkgs, ... }: + +let + # Dependencies + cat = "${pkgs.coreutils}/bin/cat"; + cut = "${pkgs.coreutils}/bin/cut"; + find = "${pkgs.findutils}/bin/find"; + grep = "${pkgs.gnugrep}/bin/grep"; + pgrep = "${pkgs.procps}/bin/pgrep"; + tail = "${pkgs.coreutils}/bin/tail"; + wc = "${pkgs.coreutils}/bin/wc"; + xargs = "${pkgs.findutils}/bin/xargs"; + timeout = "${pkgs.coreutils}/bin/timeout"; + ping = "${pkgs.iputils}/bin/ping"; + + jq = "${pkgs.jq}/bin/jq"; + gamemoded = "${pkgs.gamemode}/bin/gamemoded"; + systemctl = "${pkgs.systemd}/bin/systemctl"; + journalctl = "${pkgs.systemd}/bin/journalctl"; + playerctl = "${pkgs.playerctl}/bin/playerctl"; + playerctld = "${pkgs.playerctl}/bin/playerctld"; + pavucontrol = "${pkgs.pavucontrol}/bin/pavucontrol"; + wofi = "${pkgs.wofi}/bin/wofi"; + + # Function to simplify making waybar outputs + jsonOutput = name: { pre ? "", text ? "", tooltip ? "", alt ? "", class ? "", percentage ? "" }: "${pkgs.writeShellScriptBin "waybar-${name}" '' + set -euo pipefail + ${pre} + ${jq} -cn \ + --arg text "${text}" \ + --arg tooltip "${tooltip}" \ + --arg alt "${alt}" \ + --arg class "${class}" \ + --arg percentage "${percentage}" \ + '{text:$text,tooltip:$tooltip,alt:$alt,class:$class,percentage:$percentage}' + ''}/bin/waybar-${name}"; +in +{ + programs.waybar = { + enable = true; + package = pkgs.waybar.overrideAttrs (oa: { + mesonFlags = (oa.mesonFlags or [ ]) ++ [ "-Dexperimental=true" ]; + }); + systemd.enable = true; + settings = { + primary = { + mode = "dock"; + layer = "top"; + height = 40; + margin = "6"; + position = "top"; + modules-left = [ + "custom/menu" + ] ++ (lib.optionals config.wayland.windowManager.sway.enable [ + "sway/workspaces" + "sway/mode" + ]) ++ (lib.optionals config.wayland.windowManager.hyprland.enable [ + "hyprland/workspaces" + "hyprland/submap" + ]) ++ [ + "custom/currentplayer" + "custom/player" + ]; + + modules-center = [ + "pulseaudio" + "battery" + "clock" + "custom/unread-mail" + "custom/gpg-agent" + ]; + + modules-right = [ + "network" + "custom/tailscale-ping" + "custom/gamemode" + # TODO: currently broken for some reason + # "custom/gammastep" + "tray" + "custom/hostname" + ]; + + clock = { + interval = 1; + format = "{:%d/%m %H:%M:%S}"; + format-alt = "{:%Y-%m-%d %H:%M:%S %z}"; + on-click-left = "mode"; + tooltip-format = '' + {:%Y %B} + {calendar}''; + }; + pulseaudio = { + format = "{icon} {volume}%"; + format-muted = " 0%"; + format-icons = { + headphone = "󰋋"; + headset = "󰋎"; + portable = ""; + default = [ "" "" "" ]; + }; + on-click = pavucontrol; + }; + idle_inhibitor = { + format = "{icon}"; + format-icons = { + activated = "󰒳"; + deactivated = "󰒲"; + }; + }; + battery = { + bat = "BAT0"; + interval = 10; + format-icons = [ "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹" ]; + format = "{icon} {capacity}%"; + format-charging = "󰂄 {capacity}%"; + onclick = ""; + }; + "sway/window" = { + max-length = 20; + }; + network = { + interval = 3; + format-wifi = " {essid}"; + format-ethernet = "󰈁 Connected"; + format-disconnected = ""; + tooltip-format = '' + {ifname} + {ipaddr}/{cidr} + Up: {bandwidthUpBits} + Down: {bandwidthDownBits}''; + on-click = ""; + }; + "custom/tailscale-ping" = { + interval = 10; + return-type = "json"; + exec = + let + inherit (builtins) concatStringsSep attrNames; + hosts = attrNames outputs.nixosConfigurations; + homeMachine = "merope"; + remoteMachine = "alcyone"; + in + jsonOutput "tailscale-ping" { + # Build variables for each host + pre = '' + set -o pipefail + ${concatStringsSep "\n" (map (host: '' + ping_${host}="$(${timeout} 2 ${ping} -c 1 -q ${host} 2>/dev/null | ${tail} -1 | ${cut} -d '/' -f5 | ${cut} -d '.' -f1)ms" || ping_${host}="Disconnected" + '') hosts)} + ''; + # Access a remote machine's and a home machine's ping + text = " $ping_${remoteMachine} /  $ping_${homeMachine}"; + # Show pings from all machines + tooltip = concatStringsSep "\n" (map (host: "${host}: $ping_${host}") hosts); + }; + format = "{}"; + on-click = ""; + }; + "custom/menu" = { + return-type = "json"; + exec = jsonOutput "menu" { + text = ""; + tooltip = ''$(${cat} /etc/os-release | ${grep} PRETTY_NAME | ${cut} -d '"' -f2)''; + }; + on-click = "${wofi} -S drun -x 10 -y 10 -W 25% -H 60%"; + }; + "custom/hostname" = { + exec = "echo $USER@$HOSTNAME"; + }; + "custom/unread-mail" = { + interval = 5; + return-type = "json"; + exec = jsonOutput "unread-mail" { + pre = '' + count=$(${find} ~/Mail/*/Inbox/new -type f | ${wc} -l) + if ${pgrep} mbsync &>/dev/null; then + status="syncing" + else if [ "$count" == "0" ]; then + status="read" + else + status="unread" + fi + fi + ''; + text = "$count"; + alt = "$status"; + }; + format = "{icon} ({})"; + format-icons = { + "read" = "󰇯"; + "unread" = "󰇮"; + "syncing" = "󰁪"; + }; + }; + "custom/gpg-agent" = { + interval = 2; + return-type = "json"; + exec = + let gpgCmds = import ../../../cli/gpg-commands.nix { inherit pkgs; }; + in + jsonOutput "gpg-agent" { + pre = ''status=$(${gpgCmds.isUnlocked} && echo "unlocked" || echo "locked")''; + alt = "$status"; + tooltip = "GPG is $status"; + }; + format = "{icon}"; + format-icons = { + "locked" = ""; + "unlocked" = ""; + }; + on-click = ""; + }; + "custom/gamemode" = { + exec-if = "${gamemoded} --status | ${grep} 'is active' -q"; + interval = 2; + return-type = "json"; + exec = jsonOutput "gamemode" { + tooltip = "Gamemode is active"; + }; + format = " "; + }; + "custom/gammastep" = { + interval = 5; + return-type = "json"; + exec = jsonOutput "gammastep" { + pre = '' + if unit_status="$(${systemctl} --user is-active gammastep)"; then + status="$unit_status ($(${journalctl} --user -u gammastep.service -g 'Period: ' | ${tail} -1 | ${cut} -d ':' -f6 | ${xargs}))" + else + status="$unit_status" + fi + ''; + alt = "\${status:-inactive}"; + tooltip = "Gammastep is $status"; + }; + format = "{icon}"; + format-icons = { + "activating" = "󰁪 "; + "deactivating" = "󰁪 "; + "inactive" = "? "; + "active (Night)" = " "; + "active (Nighttime)" = " "; + "active (Transition (Night)" = " "; + "active (Transition (Nighttime)" = " "; + "active (Day)" = " "; + "active (Daytime)" = " "; + "active (Transition (Day)" = " "; + "active (Transition (Daytime)" = " "; + }; + on-click = "${systemctl} --user is-active gammastep && ${systemctl} --user stop gammastep || ${systemctl} --user start gammastep"; + }; + "custom/currentplayer" = { + interval = 2; + return-type = "json"; + exec = jsonOutput "currentplayer" { + pre = '' + player="$(${playerctl} status -f "{{playerName}}" 2>/dev/null || echo "No player active" | ${cut} -d '.' -f1)" + count="$(${playerctl} -l | ${wc} -l)" + if ((count > 1)); then + more=" +$((count - 1))" + else + more="" + fi + ''; + alt = "$player"; + tooltip = "$player ($count available)"; + text = "$more"; + }; + format = "{icon}{}"; + format-icons = { + "No player active" = " "; + "Celluloid" = "󰎁 "; + "spotify" = "󰓇 "; + "ncspot" = "󰓇 "; + "qutebrowser" = "󰖟 "; + "firefox" = " "; + "discord" = " 󰙯 "; + "sublimemusic" = " "; + "kdeconnect" = "󰄡 "; + "chromium" = " "; + }; + on-click = "${playerctld} shift"; + on-click-right = "${playerctld} unshift"; + }; + "custom/player" = { + exec-if = "${playerctl} status"; + exec = ''${playerctl} metadata --format '{"text": "{{title}} - {{artist}}", "alt": "{{status}}", "tooltip": "{{title}} - {{artist}} ({{album}})"}' ''; + return-type = "json"; + interval = 2; + max-length = 30; + format = "{icon} {}"; + format-icons = { + "Playing" = "󰐊"; + "Paused" = "󰏤 "; + "Stopped" = "󰓛"; + }; + on-click = "${playerctl} play-pause"; + }; + }; + + }; + # Cheatsheet: + # x -> all sides + # x y -> vertical, horizontal + # x y z -> top, horizontal, bottom + # w x y z -> top, right, bottom, left + style = let inherit (config.colorscheme) colors; in /* css */ '' + * { + font-family: ${config.fontProfiles.regular.family}, ${config.fontProfiles.monospace.family}; + font-size: 12pt; + padding: 0 8px; + } + + .modules-right { + margin-right: -15px; + } + + .modules-left { + margin-left: -15px; + } + + window#waybar.top { + opacity: 0.95; + padding: 0; + background-color: #${colors.base00}; + border: 2px solid #${colors.base0C}; + border-radius: 10px; + } + window#waybar.bottom { + opacity: 0.90; + background-color: #${colors.base00}; + border: 2px solid #${colors.base0C}; + border-radius: 10px; + } + + window#waybar { + color: #${colors.base05}; + } + + #workspaces button { + background-color: #${colors.base01}; + color: #${colors.base05}; + padding: 5px 1px; + margin: 3px 0; + } + #workspaces button.hidden { + background-color: #${colors.base00}; + color: #${colors.base04}; + } + #workspaces button.focused, + #workspaces button.active { + background-color: #${colors.base0A}; + color: #${colors.base00}; + } + + #clock { + background-color: #${colors.base0C}; + color: #${colors.base00}; + padding-left: 15px; + padding-right: 15px; + margin-top: 0; + margin-bottom: 0; + border-radius: 10px; + } + + #custom-menu { + background-color: #${colors.base0C}; + color: #${colors.base00}; + padding-left: 15px; + padding-right: 22px; + margin: 0; + border-radius: 10px; + } + #custom-hostname { + background-color: #${colors.base0C}; + color: #${colors.base00}; + padding-left: 15px; + padding-right: 18px; + margin-right: 0; + margin-top: 0; + margin-bottom: 0; + border-radius: 10px; + } + #custom-currentplayer { + padding-right: 0; + } + #tray { + color: #${colors.base05}; + } + ''; + }; +} diff --git a/home/features/desktop/common/wayland-wm/wofi.nix b/home/features/desktop/common/wayland-wm/wofi.nix new file mode 100644 index 0000000..4bd5836 --- /dev/null +++ b/home/features/desktop/common/wayland-wm/wofi.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: { + programs.wofi = { + enable = true; + package = pkgs.wofi.overrideAttrs (oa: { + patches = (oa.patches or [ ]) ++ [ + ./wofi-run-shell.patch # Fix for https://todo.sr.ht/~scoopta/wofi/174 + ]; + }); + settings = { + image_size = 48; + columns = 3; + allow_images = true; + insensitive = true; + run-always_parse_args = true; + run-cache_file = "/dev/null"; + run-exec_search = true; + }; + }; + + home.packages = + let + inherit (config.programs.password-store) package enable; + in + lib.optional enable (pkgs.pass-wofi.override { pass = package; }); +} diff --git a/home/features/desktop/common/wayland-wm/zathura.nix b/home/features/desktop/common/wayland-wm/zathura.nix new file mode 100644 index 0000000..97f8d24 --- /dev/null +++ b/home/features/desktop/common/wayland-wm/zathura.nix @@ -0,0 +1,9 @@ +{ config, ... }: { + programs.zathura = { + enable = true; + options = { + selection-clipboard = "clipboard"; + font = "${config.fontProfiles.regular.family} 12"; + }; + }; +} diff --git a/home/features/desktop/hyprland/basic-binds.nix b/home/features/desktop/hyprland/basic-binds.nix new file mode 100644 index 0000000..2c8f84e --- /dev/null +++ b/home/features/desktop/hyprland/basic-binds.nix @@ -0,0 +1,70 @@ +{ lib, ... }: +let + workspaces = + (map toString (lib.range 0 9)) ++ + (map (n: "F${toString n}") (lib.range 1 12)); + # Map keys to hyprland directions + directions = rec { + left = "l"; right = "r"; up = "u"; down = "d"; + h = left; l = right; k = up; j = down; + }; +in { + wayland.windowManager.hyprland.settings = { + bindm = [ + "SUPER,mouse:272,movewindow" + "SUPER,mouse:273,resizewindow" + ]; + + bind = [ + "SUPERSHIFT,q,killactive" + "SUPERSHIFT,e,exit" + + "SUPER,s,togglesplit" + "SUPER,f,fullscreen,1" + "SUPERSHIFT,f,fullscreen,0" + "SUPERSHIFT,space,togglefloating" + + "SUPER,minus,splitratio,-0.25" + "SUPERSHIFT,minus,splitratio,-0.3333333" + + "SUPER,equal,splitratio,0.25" + "SUPERSHIFT,equal,splitratio,0.3333333" + + "SUPER,g,togglegroup" + "SUPER,t,lockactivegroup,toggle" + "SUPER,apostrophe,changegroupactive,f" + "SUPERSHIFT,apostrophe,changegroupactive,b" + + "SUPER,u,togglespecialworkspace" + "SUPERSHIFT,u,movetoworkspace,special" + ] ++ + # Change workspace + (map (n: + "SUPER,${n},workspace,name:${n}" + ) workspaces) ++ + # Move window to workspace + (map (n: + "SUPERSHIFT,${n},movetoworkspacesilent,name:${n}" + ) workspaces) ++ + # Move focus + (lib.mapAttrsToList (key: direction: + "SUPER,${key},movefocus,${direction}" + ) directions) ++ + # Swap windows + (lib.mapAttrsToList (key: direction: + "SUPERSHIFT,${key},swapwindow,${direction}" + ) directions) ++ + # Move windows + (lib.mapAttrsToList (key: direction: + "SUPERCONTROL,${key},movewindoworgroup,${direction}" + ) directions) ++ + # Move monitor focus + (lib.mapAttrsToList (key: direction: + "SUPERALT,${key},focusmonitor,${direction}" + ) directions) ++ + # Move workspace to other monitor + (lib.mapAttrsToList (key: direction: + "SUPERALTSHIFT,${key},movecurrentworkspacetomonitor,${direction}" + ) directions); + }; +} diff --git a/home/features/desktop/hyprland/default.nix b/home/features/desktop/hyprland/default.nix new file mode 100644 index 0000000..98248bd --- /dev/null +++ b/home/features/desktop/hyprland/default.nix @@ -0,0 +1,187 @@ +{ lib, config, pkgs, ... }: { + imports = [ + ../common + ../common/wayland-wm + + ./tty-init.nix + ./basic-binds.nix + ./systemd-fixes.nix + ]; + + home.packages = with pkgs; [ + inputs.hyprwm-contrib.grimblast + hyprslurp + hyprpicker + ]; + + wayland.windowManager.hyprland = { + enable = true; + package = pkgs.inputs.hyprland.hyprland; + + settings = { + general = { + gaps_in = 15; + gaps_out = 20; + border_size = 2.7; + cursor_inactive_timeout = 4; + "col.active_border" = "0xff${config.colorscheme.colors.base0C}"; + "col.inactive_border" = "0xff${config.colorscheme.colors.base02}"; + "col.group_border_active" = "0xff${config.colorscheme.colors.base0B}"; + "col.group_border" = "0xff${config.colorscheme.colors.base04}"; + }; + input = { + kb_layout = "br,us"; + touchpad.disable_while_typing = false; + }; + dwindle.split_width_multiplier = 1.35; + misc.vfr = true; + + decoration = { + active_opacity = 0.92; + inactive_opacity = 0.75; + fullscreen_opacity = 1.0; + rounding = 5; + blur = { + enabled = true; + size = 5; + passes = 3; + new_optimizations = true; + ignore_opacity = true; + }; + drop_shadow = true; + shadow_range = 12; + shadow_offset = "3 3"; + "col.shadow" = "0x44000000"; + "col.shadow_inactive" = "0x66000000"; + }; + animations = { + enabled = true; + bezier = [ + "easein,0.11, 0, 0.5, 0" + "easeout,0.5, 1, 0.89, 1" + "easeinback,0.36, 0, 0.66, -0.56" + "easeoutback,0.34, 1.56, 0.64, 1" + ]; + + animation = [ + "windowsIn,1,3,easeoutback,slide" + "windowsOut,1,3,easeinback,slide" + "windowsMove,1,3,easeoutback" + "workspaces,1,2,easeoutback,slide" + "fadeIn,1,3,easeout" + "fadeOut,1,3,easein" + "fadeSwitch,1,3,easeout" + "fadeShadow,1,3,easeout" + "fadeDim,1,3,easeout" + "border,1,3,easeout" + ]; + }; + + exec = [ + "${pkgs.swaybg}/bin/swaybg -i ${config.wallpaper} --mode fill" + ]; + + bind = let + swaylock = "${config.programs.swaylock.package}/bin/swaylock"; + playerctl = "${config.services.playerctld.package}/bin/playerctl"; + playerctld = "${config.services.playerctld.package}/bin/playerctld"; + makoctl = "${config.services.mako.package}/bin/makoctl"; + wofi = "${config.programs.wofi.package}/bin/wofi"; + pass-wofi = "${pkgs.pass-wofi.override { + pass = config.programs.password-store.package; + }}/bin/pass-wofi"; + + grimblast = "${pkgs.inputs.hyprwm-contrib.grimblast}/bin/grimblast"; + pactl = "${pkgs.pulseaudio}/bin/pactl"; + tly = "${pkgs.tly}/bin/tly"; + gtk-play = "${pkgs.libcanberra-gtk3}/bin/canberra-gtk-play"; + notify-send = "${pkgs.libnotify}/bin/notify-send"; + + gtk-launch = "${pkgs.gtk3}/bin/gtk-launch"; + xdg-mime = "${pkgs.xdg-utils}/bin/xdg-mime"; + defaultApp = type: "${gtk-launch} $(${xdg-mime} query default ${type})"; + + terminal = config.home.sessionVariables.TERMINAL; + browser = defaultApp "x-scheme-handler/https"; + editor = defaultApp "text/plain"; + in [ + # Program bindings + "SUPER,Return,exec,${terminal}" + "SUPER,e,exec,${editor}" + "SUPER,v,exec,${editor}" + "SUPER,b,exec,${browser}" + # Brightness control (only works if the system has lightd) + ",XF86MonBrightnessUp,exec,light -A 10" + ",XF86MonBrightnessDown,exec,light -U 10" + # Volume + ",XF86AudioRaiseVolume,exec,${pactl} set-sink-volume @DEFAULT_SINK@ +5%" + ",XF86AudioLowerVolume,exec,${pactl} set-sink-volume @DEFAULT_SINK@ -5%" + ",XF86AudioMute,exec,${pactl} set-sink-mute @DEFAULT_SINK@ toggle" + "SHIFT,XF86AudioMute,exec,${pactl} set-source-mute @DEFAULT_SOURCE@ toggle" + ",XF86AudioMicMute,exec,${pactl} set-source-mute @DEFAULT_SOURCE@ toggle" + # Screenshotting + ",Print,exec,${grimblast} --notify --freeze copy output" + "SHIFT,Print,exec,${grimblast} --notify --freeze copy active" + "CONTROL,Print,exec,${grimblast} --notify --freeze copy screen" + "SUPER,Print,exec,${grimblast} --notify --freeze copy area" + "ALT,Print,exec,${grimblast} --notify --freeze copy area" + # Tally counter + "SUPER,z,exec,${notify-send} -t 1000 $(${tly} time) && ${tly} add && ${gtk-play} -i dialog-information" # Add new entry + "SUPERCONTROL,z,exec,${notify-send} -t 1000 $(${tly} time) && ${tly} undo && ${gtk-play} -i dialog-warning" # Undo last entry + "SUPERCONTROLSHIFT,z,exec,${tly} reset && ${gtk-play} -i complete" # Reset + "SUPERSHIFT,z,exec,${notify-send} -t 1000 $(${tly} time)" # Show current time + ] ++ + + (lib.optionals config.services.playerctld.enable [ + # Media control + ",XF86AudioNext,exec,${playerctl} next" + ",XF86AudioPrev,exec,${playerctl} previous" + ",XF86AudioPlay,exec,${playerctl} play-pause" + ",XF86AudioStop,exec,${playerctl} stop" + "ALT,XF86AudioNext,exec,${playerctld} shift" + "ALT,XF86AudioPrev,exec,${playerctld} unshift" + "ALT,XF86AudioPlay,exec,systemctl --user restart playerctld" + ]) ++ + # Screen lock + (lib.optionals config.programs.swaylock.enable [ + ",XF86Launch5,exec,${swaylock} -i ${config.wallpaper}" + ",XF86Launch4,exec,${swaylock} -i ${config.wallpaper}" + "SUPER,backspace,exec,${swaylock} -i ${config.wallpaper}" + ]) ++ + # Notification manager + (lib.optionals config.services.mako.enable [ + "SUPER,w,exec,${makoctl} dismiss" + ]) ++ + + # Launcher + (lib.optionals config.programs.wofi.enable [ + "SUPER,x,exec,${wofi} -S drun -x 10 -y 10 -W 25% -H 60%" + "SUPER,d,exec,${wofi} -S run" + ] ++ (lib.optionals config.programs.password-store.enable [ + ",Scroll_Lock,exec,${pass-wofi}" # fn+k + ",XF86Calculator,exec,${pass-wofi}" # fn+f12 + "SUPER,semicolon,exec,pass-wofi" + ])); + + monitor = map (m: let + resolution = "${toString m.width}x${toString m.height}@${toString m.refreshRate}"; + position = "${toString m.x}x${toString m.y}"; + in + "${m.name},${if m.enabled then "${resolution},${position},1" else "disable"}" + ) (config.monitors); + + workspace = map (m: + "${m.name},${m.workspace}" + ) (lib.filter (m: m.enabled && m.workspace != null) config.monitors); + + }; + # This is order sensitive, so it has to come here. + extraConfig = '' + # Passthrough mode (e.g. for VNC) + bind=SUPER,P,submap,passthrough + submap=passthrough + bind=SUPER,P,submap,reset + submap=reset + ''; + }; +} diff --git a/home/features/desktop/hyprland/systemd-fixes.nix b/home/features/desktop/hyprland/systemd-fixes.nix new file mode 100644 index 0000000..f2b5e9b --- /dev/null +++ b/home/features/desktop/hyprland/systemd-fixes.nix @@ -0,0 +1,30 @@ +{ lib, config, ... }: +let + cfg = config.wayland.windowManager.hyprland; +in +{ + config = lib.mkIf (cfg.enable && cfg.systemdIntegration) { + # Stolen from https://github.com/alebastr/sway-systemd/commit/0fdb2c4b10beb6079acd6073c5b3014bd58d3b74 + systemd.user.targets.hyprland-session-shutdown = { + Unit = { + Description = "Shutdown running Hyprland session"; + DefaultDependencies = "no"; + StopWhenUnneeded = "true"; + + Conflicts = [ + "graphical-session.target" + "graphical-session-pre.target" + "hyprland-session.target" + ]; + After = [ + "graphical-session.target" + "graphical-session-pre.target" + "hyprland-session.target" + ]; + }; + }; + wayland.windowManager.hyprland.settings.bind = lib.mkAfter [ + "SUPERSHIFT,e,exec,systemctl --user start hyprland-session-shutdown.target; hyprctl dispatch exit" + ]; + }; +} diff --git a/home/features/desktop/hyprland/tty-init.nix b/home/features/desktop/hyprland/tty-init.nix new file mode 100644 index 0000000..16763a9 --- /dev/null +++ b/home/features/desktop/hyprland/tty-init.nix @@ -0,0 +1,19 @@ +{ + programs = { + fish.loginShellInit = '' + if test (tty) = "/dev/tty1" + exec Hyprland &> /dev/null + end + ''; + zsh.loginExtra = '' + if [ "$(tty)" = "/dev/tty1" ]; then + exec Hyprland &> /dev/null + fi + ''; + zsh.profileExtra = '' + if [ "$(tty)" = "/dev/tty1" ]; then + exec Hyprland &> /dev/null + fi + ''; + }; +} diff --git a/home/pixnix.nix b/home/pixnix.nix index 277e25f..2da1687 100644 --- a/home/pixnix.nix +++ b/home/pixnix.nix @@ -3,10 +3,19 @@ ./global ./features/cli/extras ./features/desktop + ./features/desktop/hyprland ]; # packages home.packages = with pkgs; [ vagrant ]; + + monitors = [{ + name = "eDP-1"; + width = 2400; + height = 1600; + workspace = "1"; + primary = true; + }]; } diff --git a/hosts/common/global/nix.nix b/hosts/common/global/nix.nix index 1766ed3..6eec252 100644 --- a/hosts/common/global/nix.nix +++ b/hosts/common/global/nix.nix @@ -2,6 +2,8 @@ { nix = { settings = { + substituters = ["https://hyprland.cachix.org"]; + trusted-public-keys = ["hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc="]; trusted-users = [ "root" "@wheel" ]; auto-optimise-store = lib.mkDefault true; experimental-features = [ "nix-command" "flakes" "repl-flake" ]; diff --git a/hosts/common/optional/gnome.nix b/hosts/common/optional/gnome.nix index bce4bbf..daf4dd3 100644 --- a/hosts/common/optional/gnome.nix +++ b/hosts/common/optional/gnome.nix @@ -14,23 +14,6 @@ layout = "us"; xkbVariant = ""; }; - logind.lidSwitch = "ignore"; - }; - security.polkit.extraConfig = '' - polkit.addRule(function(action, subject) { - if (action.id == "org.freedesktop.login1.suspend" || - action.id == "org.freedesktop.login1.suspend-multiple-sessions" || - action.id == "org.freedesktop.login1.hibernate" || - action.id == "org.freedesktop.login1.hibernate-multiple-sessions") - { - return polkit.Result.NO; - } - }); - ''; - systemd.targets = { - sleep.enable = false; - suspend.enable = false; - hibernate.enable = false; - hybrid-sleep.enable = false; + geoclue2.enable = true; }; } \ No newline at end of file diff --git a/hosts/common/optional/x11-no-suspend.nix b/hosts/common/optional/x11-no-suspend.nix new file mode 100644 index 0000000..0005eed --- /dev/null +++ b/hosts/common/optional/x11-no-suspend.nix @@ -0,0 +1,7 @@ +{ + services.xserver.serverFlagsSection = '' + Option "StandbyTime" "0" + Option "SuspendTime" "0" + Option "OffTime" "0" + ''; +} diff --git a/hosts/common/users/john/default.nix b/hosts/common/users/john/default.nix index 433fee9..7ea7c3f 100644 --- a/hosts/common/users/john/default.nix +++ b/hosts/common/users/john/default.nix @@ -24,4 +24,6 @@ in home-manager.users.john = import ../../../../home/${config.networking.hostName}.nix; + services.geoclue2.enable = true; + security.pam.services = { swaylock = {}; }; } \ No newline at end of file diff --git a/hosts/pixnix/default.nix b/hosts/pixnix/default.nix index bfa20e3..8bb4f8c 100644 --- a/hosts/pixnix/default.nix +++ b/hosts/pixnix/default.nix @@ -9,7 +9,7 @@ ../common/users/john ../common/optional/docker.nix - ../common/optional/gnome.nix + #../common/optional/gnome.nix ../common/optional/libvirtd.nix ../common/optional/pipewire.nix ../common/optional/printing.nix @@ -21,6 +21,22 @@ networkmanager.enable = true; }; + programs = { + light.enable = true; + adb.enable = true; + dconf.enable = true; + }; + + servies.logind = { + lidSwitch = "suspend"; + lidSwitchExternalPower = "lock"; + }; + + xdg.portal = { + enable = true; + wlr.enable = true; + }; + system.stateVersion = "23.05"; } diff --git a/modules/home-manager/default.nix b/modules/home-manager/default.nix index e41f7d9..1d8f911 100644 --- a/modules/home-manager/default.nix +++ b/modules/home-manager/default.nix @@ -4,4 +4,5 @@ { # List your module files here # my-module = import ./my-module.nix; + monitors = import ./monitors.nix; } diff --git a/modules/home-manager/monitors.nix b/modules/home-manager/monitors.nix new file mode 100644 index 0000000..0dbdefc --- /dev/null +++ b/modules/home-manager/monitors.nix @@ -0,0 +1,58 @@ +{ lib, config, ... }: + +let + inherit (lib) mkOption types; + cfg = config.monitors; +in +{ + options.monitors = mkOption { + type = types.listOf (types.submodule { + options = { + name = mkOption { + type = types.str; + example = "DP-1"; + }; + primary = mkOption { + type = types.bool; + default = false; + }; + width = mkOption { + type = types.int; + example = 1920; + }; + height = mkOption { + type = types.int; + example = 1080; + }; + refreshRate = mkOption { + type = types.int; + default = 60; + }; + x = mkOption { + type = types.int; + default = 0; + }; + y = mkOption { + type = types.int; + default = 0; + }; + enabled = mkOption { + type = types.bool; + default = true; + }; + workspace = mkOption { + type = types.nullOr types.str; + default = null; + }; + }; + }); + default = [ ]; + }; + config = { + assertions = [{ + assertion = ((lib.length config.monitors) != 0) -> + ((lib.length (lib.filter (m: m.primary) config.monitors)) == 1); + message = "Exactly one monitor must be set to primary."; + }]; + }; +}