diff --git a/common/desktop_environment/hyprland/default.nix b/common/desktop_environment/hyprland/default.nix index 9726da5..60b8c47 100644 --- a/common/desktop_environment/hyprland/default.nix +++ b/common/desktop_environment/hyprland/default.nix @@ -29,6 +29,12 @@ with lib; default = { }; description = "Extra options for Hyprland configuration."; }; + swaync = { + enable = lib.mkEnableOption "Enable Swaync (notification center for Hyprland)"; + }; + waybar = { + enable = lib.mkEnableOption "Enable Waybar (status bar for Hyprland)"; + }; }; config = lib.mkIf cfg.enable { diff --git a/common/desktop_environment/hyprland/home_manager/default.nix b/common/desktop_environment/hyprland/home_manager/default.nix index 1dbebcb..9533253 100644 --- a/common/desktop_environment/hyprland/home_manager/default.nix +++ b/common/desktop_environment/hyprland/home_manager/default.nix @@ -3,6 +3,7 @@ imports = [ ./theme.nix ./hyprland.nix + # ./quickshell.nix ./waybar.nix ./hyprpolkitagent.nix ./wofi.nix diff --git a/common/desktop_environment/hyprland/home_manager/hyprland.nix b/common/desktop_environment/hyprland/home_manager/hyprland.nix index 4f3de04..409d94f 100644 --- a/common/desktop_environment/hyprland/home_manager/hyprland.nix +++ b/common/desktop_environment/hyprland/home_manager/hyprland.nix @@ -29,8 +29,17 @@ in # "waybar" # ]; - # Default monitor configuration - monitor = "monitor = , preferred, auto, 1"; + # Default monitor configuration + monitor = "monitor = , preferred, auto, 1"; + + # Make workspaces 7-10 always on MONITOR-2 (replace DP-2 if your secondary isn't DP-2) + # You can get the name of your monitor via `hyprctl monitors` + workspace = [ + "7, monitor:DP-2, persistent:true" + "8, monitor:DP-2, persistent:true" + "9, monitor:DP-2, persistent:true" + "10, monitor:DP-2, persistent:true" + ]; windowrulev2 = [ "float, class:^(?i)chrome-nngceckbapebfimnlniiiahkandclblb-Default$, initialtitle:^_crx_nngceckbapebfimnlniiiahkandclblb$" diff --git a/common/desktop_environment/hyprland/home_manager/quickshell.nix b/common/desktop_environment/hyprland/home_manager/quickshell.nix index 8d6f389..2f5032c 100644 --- a/common/desktop_environment/hyprland/home_manager/quickshell.nix +++ b/common/desktop_environment/hyprland/home_manager/quickshell.nix @@ -2,6 +2,7 @@ osConfig, lib, pkgs, + upkgs, ... }: let @@ -15,8 +16,7 @@ let in { home.packages = with pkgs; [ - quickshell - + upkgs.quickshell pulseaudio brightnessctl networkmanager @@ -27,4 +27,67 @@ in systemd hyprlock ]; + + # Ensure CLI quickshell can resolve modules when not using --config-path + home.sessionVariables = { + QML_IMPORT_PATH = "$HOME/.config/quickshell"; + QML2_IMPORT_PATH = "$HOME/.config/quickshell"; + }; + + # install config files + home.file = { + ".config/quickshell/shell.qml".source = ./quickshell/shell.qml; + ".config/quickshell/panels/TopBar.qml".source = ./quickshell/panels/TopBar.qml; + ".config/quickshell/notifications/NotificationServer.qml".source = + ./quickshell/notifications/NotificationServer.qml; + ".config/quickshell/notifications/NotificationPopup.qml".source = + ./quickshell/notifications/NotificationPopup.qml; + ".config/quickshell/notifications/NotificationCenter.qml".source = + ./quickshell/notifications/NotificationCenter.qml; + ".config/quickshell/widgets/status/Workspaces.qml".source = + ./quickshell/widgets/status/Workspaces.qml; + ".config/quickshell/widgets/status/Clock.qml".source = ./quickshell/widgets/status/Clock.qml; + ".config/quickshell/widgets/status/SystemTrayWidget.qml".source = + ./quickshell/widgets/status/SystemTrayWidget.qml; + ".config/quickshell/widgets/status/Battery.qml".source = ./quickshell/widgets/status/Battery.qml; + ".config/quickshell/widgets/controls/QuickSettings.qml".source = + ./quickshell/widgets/controls/QuickSettings.qml; + ".config/quickshell/widgets/controls/Audio.qml".source = ./quickshell/widgets/controls/Audio.qml; + ".config/quickshell/widgets/controls/Network.qml".source = + ./quickshell/widgets/controls/Network.qml; + ".config/quickshell/widgets/controls/Bluetooth.qml".source = + ./quickshell/widgets/controls/Bluetooth.qml; + ".config/quickshell/widgets/controls/Brightness.qml".source = + ./quickshell/widgets/controls/Brightness.qml; + ".config/quickshell/widgets/controls/PowerProfilesWidget.qml".source = + ./quickshell/widgets/controls/PowerProfilesWidget.qml; + ".config/quickshell/panels/qmldir".source = ./quickshell/panels/qmldir; + ".config/quickshell/notifications/qmldir".source = ./quickshell/notifications/qmldir; + ".config/quickshell/widgets/status/qmldir".source = ./quickshell/widgets/status/qmldir; + ".config/quickshell/widgets/controls/qmldir".source = ./quickshell/widgets/controls/qmldir; + # optional: .qmlls.ini should be gitignored; create empty to enable LSP + ".config/quickshell/.qmlls.ini".text = ""; + }; + + systemd.user.services.quickshell = { + Unit = { + Description = "Quickshell Desktop Shell"; + PartOf = [ "graphical-session.target" ]; + After = [ "graphical-session.target" ]; + }; + Service = { + ExecStart = "${upkgs.quickshell}/bin/quickshell --config-path %h/.config/quickshell"; + Restart = "on-failure"; + RestartSec = 2; + Environment = [ + "QML_IMPORT_PATH=%h/.config/quickshell" + "QT_QPA_PLATFORM=wayland" + # Ensure we find icons + "XDG_CURRENT_DESKTOP=quickshell" + ]; + }; + Install = { + WantedBy = [ "graphical-session.target" ]; + }; + }; } diff --git a/common/desktop_environment/hyprland/home_manager/swaync.nix b/common/desktop_environment/hyprland/home_manager/swaync.nix index 6891a76..b26fa58 100644 --- a/common/desktop_environment/hyprland/home_manager/swaync.nix +++ b/common/desktop_environment/hyprland/home_manager/swaync.nix @@ -1,237 +1,251 @@ { + lib, + osConfig, ... }: +let + ccfg = import ../../../config.nix; + cfg_path = [ + ccfg.custom_config_key + "desktopEnvironment" + "hyprland" + "swaync" + ]; + cfg = lib.attrsets.getAttrFromPath cfg_path osConfig; +in { - services.swaync = { - enable = true; - settings = { - ignore = [ - "com.spotify.Client" - ]; + config = lib.mkIf cfg.enable { + services.swaync = { + enable = true; + settings = { + ignore = [ + "com.spotify.Client" + ]; - positionX = "right"; - positionY = "top"; - layer = "overlay"; - control-center-layer = "top"; - layer-shell = true; - cssPriority = "application"; + positionX = "right"; + positionY = "top"; + layer = "overlay"; + control-center-layer = "top"; + layer-shell = true; + cssPriority = "application"; - control-center-margin-top = 0; - control-center-margin-bottom = 0; - control-center-margin-right = 0; - control-center-margin-left = 0; + control-center-margin-top = 0; + control-center-margin-bottom = 0; + control-center-margin-right = 0; + control-center-margin-left = 0; - notification-2fa-action = true; - notification-inline-replies = false; - notification-icon-size = 64; - notification-body-image-height = 100; - notification-body-image-width = 200; + notification-2fa-action = true; + notification-inline-replies = false; + notification-icon-size = 64; + notification-body-image-height = 100; + notification-body-image-width = 200; - timeout = 10; - timeout-low = 5; - timeout-critical = 0; + timeout = 10; + timeout-low = 5; + timeout-critical = 0; - control-center-width = 500; - control-center-height = 600; - notification-window-width = 500; + control-center-width = 500; + control-center-height = 600; + notification-window-width = 500; - keyboard-shortcuts = true; - image-visibility = "when-available"; - transition-time = 200; - hide-on-clear = false; - hide-on-action = true; - script-fail-notify = true; + keyboard-shortcuts = true; + image-visibility = "when-available"; + transition-time = 200; + hide-on-clear = false; + hide-on-action = true; + script-fail-notify = true; - widgets = [ - "inhibitors" - "title" - "dnd" - "volume" - "backlight" - "mpris" - "buttons-grid#quick" - "notifications" - ]; + widgets = [ + "inhibitors" + "title" + "dnd" + "volume" + "backlight" + "mpris" + "buttons-grid#quick" + "notifications" + ]; - # Widget configurations - widget-config = { - inhibitors = { - text = "Inhibitors"; - button-text = "Clear All"; - clear-all-button = true; - }; - title = { - text = "Notifications"; - clear-all-button = true; - button-text = "Clear All"; - }; - dnd.text = "Do Not Disturb"; - mpris = { - image-size = 96; - image-radius = 12; - }; - volume = { - label = "󰕾"; - show-per-app = true; - }; - backlight = { - label = "󰃟"; - device = "intel_backlight"; - }; - "buttons-grid#quick" = { - columns = 4; # adjust: 3/4/5 - icon-size = 20; # tweak to taste - actions = [ - # Power - { - label = "󰐥"; - tooltip = "Shutdown"; - command = "confirm-action 'systemctl poweroff' 'Shutdown?'"; - } - { - label = "󰜉"; - tooltip = "Reboot"; - command = "confirm-action 'systemctl reboot' 'Reboot?'"; - } - { - label = "󰍃"; - tooltip = "Logout"; - command = "confirm-action 'hyprctl dispatch exit' 'Logout?'"; - } - ]; + # Widget configurations + widget-config = { + inhibitors = { + text = "Inhibitors"; + button-text = "Clear All"; + clear-all-button = true; + }; + title = { + text = "Notifications"; + clear-all-button = true; + button-text = "Clear All"; + }; + dnd.text = "Do Not Disturb"; + mpris = { + image-size = 96; + image-radius = 12; + }; + volume = { + label = "󰕾"; + show-per-app = true; + }; + backlight = { + label = "󰃟"; + device = "intel_backlight"; + }; + "buttons-grid#quick" = { + columns = 4; # adjust: 3/4/5 + icon-size = 20; # tweak to taste + actions = [ + # Power + { + label = "󰐥"; + tooltip = "Shutdown"; + command = "confirm-action 'systemctl poweroff' 'Shutdown?'"; + } + { + label = "󰜉"; + tooltip = "Reboot"; + command = "confirm-action 'systemctl reboot' 'Reboot?'"; + } + { + label = "󰍃"; + tooltip = "Logout"; + command = "confirm-action 'hyprctl dispatch exit' 'Logout?'"; + } + ]; + }; }; }; + + # Custom CSS for the control center + style = '' + .control-center { + background: #1a1b26; + border: 2px solid #7dcae4; + border-radius: 12px; + } + + .control-center-list { + background: transparent; + } + + .control-center .notification-row:focus, + .control-center .notification-row:hover { + opacity: 1; + background: #24283b; + } + + .notification { + border-radius: 8px; + margin: 6px 12px; + box-shadow: 0 0 0 1px rgba(125, 196, 228, 0.3), 0 1px 3px 1px rgba(0, 0, 0, 0.7), 0 2px 6px 2px rgba(0, 0, 0, 0.3); + padding: 0; + } + + /* Widget styling */ + .widget-title { + margin: 8px; + font-size: 1.5rem; + color: #c0caf5; + } + + .widget-dnd { + margin: 8px; + font-size: 1.1rem; + color: #c0caf5; + } + + .widget-dnd > switch { + font-size: initial; + border-radius: 8px; + background: #414868; + border: 1px solid #7dcae4; + } + + .widget-dnd > switch:checked { + background: #7dcae4; + } + + .widget-mpris { + color: #c0caf5; + background: #24283b; + padding: 8px; + margin: 8px; + border-radius: 8px; + } + + .widget-mpris-player { + padding: 8px; + margin: 8px; + } + + .widget-mpris-title { + font-weight: bold; + font-size: 1.25rem; + } + + .widget-mpris-subtitle { + font-size: 1.1rem; + color: #9ece6a; + } + + .widget-volume { + background: #24283b; + padding: 8px; + margin: 8px; + border-radius: 8px; + color: #c0caf5; + } + + .widget-backlight { + background: #24283b; + padding: 8px; + margin: 8px; + border-radius: 8px; + color: #c0caf5; + } + + .widget-menubar { + background: #24283b; + padding: 8px; + margin: 8px; + border-radius: 8px; + color: #c0caf5; + } + + .widget-menubar .menu-item button { + background: #1f2335; + color: #c0caf5; + border-radius: 8px; + padding: 6px 10px; + margin: 4px; + border: 1px solid #2e3440; + font-family: "JetBrainsMonoNL Nerd Font"; + } + + .widget-menubar .menu-item button:hover { + background: #414868; + border-color: #7dcae4; + } + + .topbar-buttons button { + border: none; + background: transparent; + color: #c0caf5; + font-size: 1.1rem; + border-radius: 8px; + margin: 0 4px; + padding: 8px; + } + + .topbar-buttons button:hover { + background: #414868; + } + + .topbar-buttons button:active { + background: #7dcae4; + color: #1a1b26; + } + ''; }; - - # Custom CSS for the control center - style = '' - .control-center { - background: #1a1b26; - border: 2px solid #7dcae4; - border-radius: 12px; - } - - .control-center-list { - background: transparent; - } - - .control-center .notification-row:focus, - .control-center .notification-row:hover { - opacity: 1; - background: #24283b; - } - - .notification { - border-radius: 8px; - margin: 6px 12px; - box-shadow: 0 0 0 1px rgba(125, 196, 228, 0.3), 0 1px 3px 1px rgba(0, 0, 0, 0.7), 0 2px 6px 2px rgba(0, 0, 0, 0.3); - padding: 0; - } - - /* Widget styling */ - .widget-title { - margin: 8px; - font-size: 1.5rem; - color: #c0caf5; - } - - .widget-dnd { - margin: 8px; - font-size: 1.1rem; - color: #c0caf5; - } - - .widget-dnd > switch { - font-size: initial; - border-radius: 8px; - background: #414868; - border: 1px solid #7dcae4; - } - - .widget-dnd > switch:checked { - background: #7dcae4; - } - - .widget-mpris { - color: #c0caf5; - background: #24283b; - padding: 8px; - margin: 8px; - border-radius: 8px; - } - - .widget-mpris-player { - padding: 8px; - margin: 8px; - } - - .widget-mpris-title { - font-weight: bold; - font-size: 1.25rem; - } - - .widget-mpris-subtitle { - font-size: 1.1rem; - color: #9ece6a; - } - - .widget-volume { - background: #24283b; - padding: 8px; - margin: 8px; - border-radius: 8px; - color: #c0caf5; - } - - .widget-backlight { - background: #24283b; - padding: 8px; - margin: 8px; - border-radius: 8px; - color: #c0caf5; - } - - .widget-menubar { - background: #24283b; - padding: 8px; - margin: 8px; - border-radius: 8px; - color: #c0caf5; - } - - .widget-menubar .menu-item button { - background: #1f2335; - color: #c0caf5; - border-radius: 8px; - padding: 6px 10px; - margin: 4px; - border: 1px solid #2e3440; - font-family: "JetBrainsMonoNL Nerd Font"; - } - - .widget-menubar .menu-item button:hover { - background: #414868; - border-color: #7dcae4; - } - - .topbar-buttons button { - border: none; - background: transparent; - color: #c0caf5; - font-size: 1.1rem; - border-radius: 8px; - margin: 0 4px; - padding: 8px; - } - - .topbar-buttons button:hover { - background: #414868; - } - - .topbar-buttons button:active { - background: #7dcae4; - color: #1a1b26; - } - ''; }; } diff --git a/common/desktop_environment/hyprland/home_manager/waybar.nix b/common/desktop_environment/hyprland/home_manager/waybar.nix index c67d449..25c5986 100644 --- a/common/desktop_environment/hyprland/home_manager/waybar.nix +++ b/common/desktop_environment/hyprland/home_manager/waybar.nix @@ -1,253 +1,267 @@ { + lib, + osConfig, ... }: +let + ccfg = import ../../../config.nix; + cfg_path = [ + ccfg.custom_config_key + "desktopEnvironment" + "hyprland" + "waybar" + ]; + cfg = lib.attrsets.getAttrFromPath cfg_path osConfig; +in { - programs.waybar = { - enable = true; - systemd.enable = true; - settings = { - mainBar = { - layer = "top"; - position = "top"; - height = 30; - spacing = 6; - margin-top = 0; - margin-bottom = 0; - margin-left = 10; - margin-right = 10; + config = lib.mkIf cfg.enable { + programs.waybar = { + enable = true; + systemd.enable = true; + settings = { + mainBar = { + layer = "top"; + position = "top"; + height = 30; + spacing = 6; + margin-top = 0; + margin-bottom = 0; + margin-left = 10; + margin-right = 10; - modules-left = [ - "hyprland/workspaces" - ]; + modules-left = [ + "hyprland/workspaces" + ]; - modules-center = [ - "clock" - "temperature" - "cpu" - "memory" - "disk" - ]; + modules-center = [ + "clock" + "temperature" + "cpu" + "memory" + "disk" + ]; - modules-right = [ - "pulseaudio" - "network" - "bluetooth" - "custom/notifications" - "hyprland/language" - ]; + modules-right = [ + "pulseaudio" + "network" + "bluetooth" + "custom/notifications" + "hyprland/language" + ]; - # Workspaces configuration - "hyprland/workspaces" = { - format = "{icon}"; - format-icons = { - "1" = "一"; - "2" = "二"; - "3" = "三"; - "4" = "四"; - "5" = "五"; - "6" = "六"; - "7" = "七"; - "8" = "八"; - "9" = "九"; - "10" = "十"; - "11" = "十一"; - "12" = "十二"; - "13" = "十三"; - "14" = "十四"; - "15" = "十五"; - "16" = "十六"; - "17" = "十七"; - "18" = "十八"; - "19" = "十九"; - "20" = "二十"; + # Workspaces configuration + "hyprland/workspaces" = { + format = "{icon}"; + format-icons = { + "1" = "一"; + "2" = "二"; + "3" = "三"; + "4" = "四"; + "5" = "五"; + "6" = "六"; + "7" = "七"; + "8" = "八"; + "9" = "九"; + "10" = "十"; + "11" = "十一"; + "12" = "十二"; + "13" = "十三"; + "14" = "十四"; + "15" = "十五"; + "16" = "十六"; + "17" = "十七"; + "18" = "十八"; + "19" = "十九"; + "20" = "二十"; + }; + show-special = false; }; - show-special = false; - }; - pulseaudio = { - format = "{icon} {volume}%"; - format-bluetooth = "󰂰 {volume}%"; - format-bluetooth-muted = "󰂲 "; - format-muted = "󰖁 "; - format-source = "󰍬 {volume}%"; - format-source-muted = "󰍭 "; - format-icons = { - headphone = "󰋋"; - hands-free = "󰂑"; - headset = "󰂑"; - phone = "󰏲"; - portable = "󰦧"; - car = "󰄋"; - default = [ - "󰕿" - "󰖀" - "󰕾" - ]; + pulseaudio = { + format = "{icon} {volume}%"; + format-bluetooth = "󰂰 {volume}%"; + format-bluetooth-muted = "󰂲 "; + format-muted = "󰖁 "; + format-source = "󰍬 {volume}%"; + format-source-muted = "󰍭 "; + format-icons = { + headphone = "󰋋"; + hands-free = "󰂑"; + headset = "󰂑"; + phone = "󰏲"; + portable = "󰦧"; + car = "󰄋"; + default = [ + "󰕿" + "󰖀" + "󰕾" + ]; + }; + scroll-step = 5; + on-click = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; + on-click-right = "swaync-client -t -sw"; }; - scroll-step = 5; - on-click = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; - on-click-right = "swaync-client -t -sw"; - }; - "custom/notifications" = { - format = "{icon} {}"; - format-icons = { - notification = ""; - none = ""; - dnd-notification = "󰂛"; - dnd-none = "󰂛"; - inhibited-notification = ""; - inhibited-none = ""; - dnd-inhibited-notification = "󰂛"; - dnd-inhibited-none = "󰂛"; + "custom/notifications" = { + format = "{icon} {}"; + format-icons = { + notification = ""; + none = ""; + dnd-notification = "󰂛"; + dnd-none = "󰂛"; + inhibited-notification = ""; + inhibited-none = ""; + dnd-inhibited-notification = "󰂛"; + dnd-inhibited-none = "󰂛"; + }; + return-type = "json"; + exec-if = "which swaync-client"; + exec = "swaync-client -swb"; + on-click = "swaync-client -t -sw"; + on-click-right = "swaync-client -d -sw"; + escape = true; + tooltip = false; }; - return-type = "json"; - exec-if = "which swaync-client"; - exec = "swaync-client -swb"; - on-click = "swaync-client -t -sw"; - on-click-right = "swaync-client -d -sw"; - escape = true; - tooltip = false; - }; - # Clock - clock = { - format = "{:%b %d, %H:%M}"; - }; + # Clock + clock = { + format = "{:%b %d, %H:%M}"; + }; - temperature = { - thermal-zone = 2; - hwmon-path = "/sys/class/hwmon/hwmon2/temp1_input"; - critical-threshold = 80; - format-critical = "󰔏 {temperatureC}°C"; - format = "󰔏 {temperatureC}°C"; - }; + temperature = { + thermal-zone = 2; + hwmon-path = "/sys/class/hwmon/hwmon2/temp1_input"; + critical-threshold = 80; + format-critical = "󰔏 {temperatureC}°C"; + format = "󰔏 {temperatureC}°C"; + }; - cpu = { - format = "󰻠 {usage}%"; - tooltip = false; - on-click = "btop"; - }; + cpu = { + format = "󰻠 {usage}%"; + tooltip = false; + on-click = "btop"; + }; - memory = { - format = "󰍛 {}%"; - on-click = "btop"; - }; + memory = { + format = "󰍛 {}%"; + on-click = "btop"; + }; - disk = { - interval = 30; - format = "󰋊 {percentage_used}%"; - path = "/"; - on-click = "btop"; - }; + disk = { + interval = 30; + format = "󰋊 {percentage_used}%"; + path = "/"; + on-click = "btop"; + }; - network = { - format-wifi = "󰤨 {essid} ({signalStrength}%)"; - format-ethernet = "󰈀 {ipaddr}/{cidr}"; - tooltip-format = "{ifname} via {gwaddr} "; - format-linked = "󰈀 {ifname} (No IP)"; - format-disconnected = "󰖪 Disconnected"; - on-click = "wofi-wifi-menu"; - on-click-right = "nmcli radio wifi toggle"; - }; + network = { + format-wifi = "󰤨 {essid} ({signalStrength}%)"; + format-ethernet = "󰈀 {ipaddr}/{cidr}"; + tooltip-format = "{ifname} via {gwaddr} "; + format-linked = "󰈀 {ifname} (No IP)"; + format-disconnected = "󰖪 Disconnected"; + on-click = "wofi-wifi-menu"; + on-click-right = "nmcli radio wifi toggle"; + }; - bluetooth = { - format = "󰂯 {status}"; - format-connected = "󰂱 {device_alias}"; - format-connected-battery = "󰂱 {device_alias} {device_battery_percentage}%"; - tooltip-format = "{controller_alias}\t{controller_address}\n\n{num_connections} connected"; - tooltip-format-connected = "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}"; - tooltip-format-enumerate-connected = "{device_alias}\t{device_address}"; - tooltip-format-enumerate-connected-battery = "{device_alias}\t{device_address}\t{device_battery_percentage}%"; - on-click = "wofi-bluetooth-menu"; - on-click-right = "bluetoothctl power toggle"; - }; + bluetooth = { + format = "󰂯 {status}"; + format-connected = "󰂱 {device_alias}"; + format-connected-battery = "󰂱 {device_alias} {device_battery_percentage}%"; + tooltip-format = "{controller_alias}\t{controller_address}\n\n{num_connections} connected"; + tooltip-format-connected = "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}"; + tooltip-format-enumerate-connected = "{device_alias}\t{device_address}"; + tooltip-format-enumerate-connected-battery = "{device_alias}\t{device_address}\t{device_battery_percentage}%"; + on-click = "wofi-bluetooth-menu"; + on-click-right = "bluetoothctl power toggle"; + }; - # Keyboard input (language) - "hyprland/language" = { - format = "{}"; - format-en = "EN"; - format-ja = "JP"; + # Keyboard input (language) + "hyprland/language" = { + format = "{}"; + format-en = "EN"; + format-ja = "JP"; + }; }; }; + + style = '' + * { + font-family: "JetBrainsMonoNL Nerd Font"; + font-size: 12px; + border: none; + border-radius: 0; + min-height: 0; + } + + window#waybar { + background: transparent; + border-radius: 10px; + margin: 0px; + } + + .modules-left, + .modules-center, + .modules-right { + background: rgba(26, 27, 38, 0.8); + border-radius: 10px; + margin: 4px; + padding: 0 10px; + } + + #workspaces { + padding: 0 5px; + } + + #workspaces button { + padding: 0 8px; + background: transparent; + color: #c0caf5; + border-radius: 5px; + margin: 2px; + } + + #workspaces button:hover { + background: rgba(125, 196, 228, 0.2); + color: #7dcae4; + } + + #workspaces button.active { + background: #7dcae4; + color: #1a1b26; + } + + #pulseaudio, + #custom-notifications, + #clock, + #temperature, + #cpu, + #memory, + #disk, + #network, + #bluetooth, + #language { + padding: 0 8px; + color: #c0caf5; + margin: 2px; + } + + #temperature.critical { + color: #f7768e; + } + + #network.disconnected { + color: #f7768e; + } + + #bluetooth.disabled { + color: #565f89; + } + + #pulseaudio.muted { + color: #565f89; + } + ''; }; - - style = '' - * { - font-family: "JetBrainsMonoNL Nerd Font"; - font-size: 12px; - border: none; - border-radius: 0; - min-height: 0; - } - - window#waybar { - background: transparent; - border-radius: 10px; - margin: 0px; - } - - .modules-left, - .modules-center, - .modules-right { - background: rgba(26, 27, 38, 0.8); - border-radius: 10px; - margin: 4px; - padding: 0 10px; - } - - #workspaces { - padding: 0 5px; - } - - #workspaces button { - padding: 0 8px; - background: transparent; - color: #c0caf5; - border-radius: 5px; - margin: 2px; - } - - #workspaces button:hover { - background: rgba(125, 196, 228, 0.2); - color: #7dcae4; - } - - #workspaces button.active { - background: #7dcae4; - color: #1a1b26; - } - - #pulseaudio, - #custom-notifications, - #clock, - #temperature, - #cpu, - #memory, - #disk, - #network, - #bluetooth, - #language { - padding: 0 8px; - color: #c0caf5; - margin: 2px; - } - - #temperature.critical { - color: #f7768e; - } - - #network.disconnected { - color: #f7768e; - } - - #bluetooth.disabled { - color: #565f89; - } - - #pulseaudio.muted { - color: #565f89; - } - ''; }; } diff --git a/common/flake.lock b/common/flake.lock index c2fa076..a1d6c11 100644 --- a/common/flake.lock +++ b/common/flake.lock @@ -85,11 +85,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1753592768, - "narHash": "sha256-oV695RvbAE4+R9pcsT9shmp6zE/+IZe6evHWX63f2Qg=", + "lastModified": 1755928099, + "narHash": "sha256-OILVkfhRCm8u18IZ2DKR8gz8CVZM2ZcJmQBXmjFLIfk=", "owner": "rycee", "repo": "home-manager", - "rev": "fc3add429f21450359369af74c2375cb34a2d204", + "rev": "4a44fb9f7555da362af9d499817084f4288a957f", "type": "github" }, "original": { diff --git a/hosts/lio/flake.nix b/hosts/lio/flake.nix index 4e0bfd5..dc9be42 100644 --- a/hosts/lio/flake.nix +++ b/hosts/lio/flake.nix @@ -29,7 +29,13 @@ nixosConfigurations = { "${configuration_name}" = ( lib.nixosSystem { - specialArgs = { inherit inputs; }; + specialArgs = { + inherit inputs; + upkgs = import inputs.nixpkgs-unstable { + system = "x86_64-linux"; + config.allowUnfree = true; + }; + }; modules = [ common.nixosModules.default ros_neovim.nixosModules.default @@ -37,10 +43,12 @@ ./hardware-configuration.nix (import ./containers.nix { inherit inputs; }) # ./jails_text.nix + ./hyprland_customizations.nix ( { config, pkgs, + upkgs, lib, ... }: @@ -80,6 +88,11 @@ # Allow emulation of aarch64-linux binaries for cross compiling boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; + home-manager.extraSpecialArgs = { + inherit inputs; + inherit upkgs; + }; + ringofstorms_common = { systemName = configuration_name; boot.systemd.enable = true; @@ -90,26 +103,8 @@ }; desktopEnvironment.hyprland = { enable = true; - extraOptions = - let - main = "desc:ASUSTek COMPUTER INC ASUS PG43U 0x01010101"; - secondary = "desc:Samsung Electric Company C34J79x HTRM900776"; - in - { - # hyprctl monitors all - monitor = [ - "${main},3840x2160@97.98,0x0,1,transform,0" - "${secondary},3440x1440@99.98,-1440x-640,1,transform,1" - ]; - # Pin workspaces 1-6 to main monitor and rest on other monitor - workspace = - let - inherit (builtins) map toString; - inherit (lib) range; - mkWs = monitor: i: "${toString i},monitor:${monitor}"; - in - (map (mkWs main) (range 1 6)) ++ (map (mkWs secondary) (range 7 10)); - }; + waybar.enable = true; + swaync.enable = true; }; programs = { qFlipper.enable = true; diff --git a/hosts/lio/hyprland_customizations.nix b/hosts/lio/hyprland_customizations.nix new file mode 100644 index 0000000..5b979b6 --- /dev/null +++ b/hosts/lio/hyprland_customizations.nix @@ -0,0 +1,137 @@ +{ lib, pkgs, ... }: +let + # Exact descriptions as reported by: hyprctl -j monitors | jq '.[].description' + mainDesc = "ASUSTek COMPUTER INC ASUS PG43U 0x01010101"; + secondaryDesc = "Samsung Electric Company C34J79x HTRM900776"; + + mainMonitor = "desc:${mainDesc}"; + secondaryMonitor = "desc:${secondaryDesc}"; + + hyprlandExtraOptions = { + monitor = [ + "${mainMonitor},3840x2160@97.98,0x0,1,transform,0" + "${secondaryMonitor},3440x1440@99.98,-1440x-640,1,transform,1" + ]; + workspace = + let + inherit (builtins) map toString; + inherit (lib) range; + mkWs = monitor: i: "${toString i},monitor:${monitor},persistent:true"; + in + (map (mkWs mainMonitor) (range 1 6)) ++ (map (mkWs secondaryMonitor) (range 7 10)); + }; + + moveScript = pkgs.writeShellScriptBin "hyprland-move-workspaces" '' + #!/usr/bin/env bash + set -euo pipefail + + HYPRCTL='${pkgs.hyprland}/bin/hyprctl' + JQ='${pkgs.jq}/bin/jq' + SOCAT='${pkgs.socat}/bin/socat' + + MAIN_DESC='${mainDesc}' + SEC_DESC='${secondaryDesc}' + + get_socket() { + # socket2 carries the event stream + echo "${"$"}{XDG_RUNTIME_DIR}/hypr/${"$"}{HYPRLAND_INSTANCE_SIGNATURE}/.socket2.sock" + } + + wait_for_hypr() { + # Wait until hyprctl works (Hyprland is up) + until ''${HYPRCTL} -j monitors >/dev/null 2>&1; do + sleep 0.5 + done + } + + mon_name_by_desc() { + # Resolve Hyprland "name" (e.g., DP-2) from human-friendly description + local desc="${"$"}1" + ''${HYPRCTL} -j monitors \ + | ''${JQ} -r --arg d "${"$"}desc" '.[] | select(.description == $d) | .name' \ + | head -n1 + } + + place_workspaces() { + local mainName secName + mainName="$(mon_name_by_desc "${"$"}MAIN_DESC")" + secName="$(mon_name_by_desc "${"$"}SEC_DESC" || true)" + + # Always keep 1–6 on the main monitor + for ws in 1 2 3 4 5 6; do + ''${HYPRCTL} dispatch moveworkspacetomonitor "${"$"}ws" "${"$"}mainName" || true + done + + if [ -n "${"$"}{secName:-}" ]; then + # Secondary is present ➜ put 7–10 on secondary + for ws in 7 8 9 10; do + ''${HYPRCTL} dispatch moveworkspacetomonitor "${"$"}ws" "${"$"}secName" || true + done + else + # No secondary ➜ keep 7–10 on main + for ws in 7 8 9 10; do + ''${HYPRCTL} dispatch moveworkspacetomonitor "${"$"}ws" "${"$"}mainName" || true + done + fi + } + + watch_events() { + local sock + sock="$(get_socket)" + + # If socket2 is missing for some reason, fall back to polling + if [ ! -S "${"$"}sock" ]; then + while :; do + place_workspaces + sleep 5 + done + return + fi + + # Subscribe to Hyprland events and react to monitor changes + ''${SOCAT} - "UNIX-CONNECT:${"$"}sock" | while IFS= read -r line; do + case "${"$"}line" in + monitoradded*|monitorremoved*|activemonitor*|layoutchange*) + place_workspaces + ;; + esac + done + } + + if [ "${"$"}{1:-}" = "--oneshot" ]; then + wait_for_hypr + place_workspaces + else + wait_for_hypr + place_workspaces + watch_events + fi + ''; +in +{ + options = { }; + + config = { + environment.systemPackages = [ moveScript ]; + + # Pass the options to home-manager for hyprland (as you already do) + ringofstorms_common.desktopEnvironment.hyprland.extraOptions = hyprlandExtraOptions; + + # User-level systemd service that follows your Hyprland session and watches for monitor changes + systemd.user.services.hyprland-move-workspaces = { + description = "Keep workspaces 1–6 on main and 7–10 on secondary; react to monitor changes"; + wants = [ "graphical-session.target" ]; + after = [ "graphical-session.target" ]; + partOf = [ "graphical-session.target" ]; + + serviceConfig = { + Type = "simple"; + ExecStart = "${moveScript}/bin/hyprland-move-workspaces"; + Restart = "always"; + RestartSec = "2s"; + }; + + wantedBy = [ "graphical-session.target" ]; + }; + }; +}