From 6e87d8e18693eefea40d30fccd7a61aa518e69c0 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 28 Jun 2026 06:48:07 +0200 Subject: [PATCH] feat(desktop): Hyprland, Waybar, autostart/bindings/idle Co-Authored-By: Claude Opus 4.8 --- modules/desktop/README.md | 333 ++++++++++++++ modules/desktop/hyprland.nix | 575 +++++++++++++++++++++++++ modules/desktop/hyprland/autostart.nix | 61 +++ modules/desktop/hyprland/bindings.nix | 169 ++++++++ modules/desktop/hyprland/idle.nix | 179 ++++++++ 5 files changed, 1317 insertions(+) create mode 100644 modules/desktop/README.md create mode 100644 modules/desktop/hyprland.nix create mode 100644 modules/desktop/hyprland/autostart.nix create mode 100644 modules/desktop/hyprland/bindings.nix create mode 100644 modules/desktop/hyprland/idle.nix diff --git a/modules/desktop/README.md b/modules/desktop/README.md new file mode 100644 index 0000000..9ac2fd1 --- /dev/null +++ b/modules/desktop/README.md @@ -0,0 +1,333 @@ +# Desktop Directory - Desktop Environment Configuration + +The `modules/desktop/` directory contains the desktop environment configuration for Omnixient, centered around the Hyprland compositor. This directory manages the complete desktop experience including window management, user interface, and desktop interactions. + +## Desktop Architecture + +The desktop system is built in layers: +``` +User Interaction Layer (keybindings, gestures) + ↓ +Application Layer (autostart, window rules) + ↓ +Compositor Layer (Hyprland core) + ↓ +System Integration Layer (services, hardware) +``` + +## Core Desktop Module + +### `hyprland.nix` +**Purpose**: Main Hyprland compositor configuration and coordination +**What it provides**: +- Core Hyprland configuration +- Integration with other desktop components +- Theme-aware window management +- Performance optimizations + +**Key Features**: +- Wayland-native compositor +- Dynamic tiling window management +- Smooth animations and effects +- GPU-accelerated rendering +- Extensive customization options + +**Module Structure**: +```nix +imports = [ + ./hyprland/bindings.nix + ./hyprland/autostart.nix + ./hyprland/idle.nix +]; +``` + +## Hyprland Sub-Modules + +### `hyprland/bindings.nix` +**Purpose**: Keyboard shortcuts and input bindings +**What it configures**: +- Window management shortcuts +- Application launching bindings +- Workspace navigation +- System control shortcuts + +**Key Binding Categories**: + +#### Window Management +- `Super + Q`: Close window +- `Super + F`: Toggle fullscreen +- `Super + Space`: Toggle floating +- `Super + V`: Toggle split direction +- `Super + Arrow Keys`: Move window focus +- `Super + Shift + Arrow Keys`: Move windows + +#### Application Launching +- `Super + Return`: Terminal (Alacritty) +- `Super + B`: Web browser +- `Super + E`: File manager +- `Super + D`: Application launcher +- `Super + R`: Run dialog + +#### Workspace Management +- `Super + 1-9`: Switch to workspace +- `Super + Shift + 1-9`: Move window to workspace +- `Super + Mouse Wheel`: Cycle through workspaces +- `Super + Tab`: Application switcher + +#### System Controls +- `Super + L`: Lock screen +- `Super + Shift + E`: Logout menu +- `Volume Keys`: Audio control +- `Brightness Keys`: Display brightness +- `Print`: Screenshot region +- `Shift + Print`: Screenshot full screen + +#### Advanced Bindings +- `Super + Alt + Arrow Keys`: Resize windows +- `Super + Mouse`: Move/resize windows +- `Super + Shift + S`: Screenshot with selection +- `Super + P`: Power menu + +### `hyprland/autostart.nix` +**Purpose**: Applications and services started with the desktop session +**What it manages**: +- Essential desktop services +- User applications +- Background processes +- System tray applications + +**Autostart Categories**: + +#### Essential Services +- **Waybar**: Desktop panel/taskbar +- **Mako**: Notification daemon +- **Authentication Agent**: Polkit authentication +- **Network Manager Applet**: Network connectivity + +#### Background Services +- **Clipboard Manager**: Clipboard history +- **Wallpaper Setter**: Dynamic wallpapers +- **Idle Manager**: Screen timeout and locking +- **Audio Control**: Volume control daemon + +#### User Applications (Optional) +- **File Manager**: Background file operations +- **Chat Applications**: Discord, Slack, etc. +- **Productivity Tools**: Note-taking, calendar +- **Development Tools**: IDEs, terminals + +**Configuration Example**: +```nix +wayland.windowManager.hyprland.settings = { + exec-once = [ + "waybar" + "mako" + "nm-applet --indicator" + "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1" + ]; +}; +``` + +### `hyprland/idle.nix` +**Purpose**: Idle management and screen locking +**What it configures**: +- Screen timeout settings +- Automatic screen locking +- Display power management +- Suspend/hibernate behavior + +**Idle Management Features**: + +#### Screen Locking +- Automatic lock after inactivity +- Manual lock with keybinding +- Grace period for quick unlock +- Secure lock screen (swaylock) + +#### Display Management +- Screen dimming before lock +- Display turn-off timing +- Multiple monitor handling +- Brightness restoration + +#### Power Management +- Suspend after extended idle +- Hibernate for long inactivity +- Wake-on-input configuration +- Battery-aware timeouts + +**Configuration Options**: +```nix +services.hypridle = { + enable = true; + settings = { + general = { + after_sleep_cmd = "hyprctl dispatch dpms on"; + before_sleep_cmd = "loginctl lock-session"; + ignore_dbus_inhibit = false; + lock_cmd = "pidof hyprlock || hyprlock"; + }; + + listener = [ + { + timeout = 300; # 5 minutes + on-timeout = "brightnessctl -s set 10"; + on-resume = "brightnessctl -r"; + } + { + timeout = 600; # 10 minutes + on-timeout = "loginctl lock-session"; + } + ]; + }; +}; +``` + +## Window Management Features + +### Tiling Behavior +- **Dynamic Tiling**: Automatic window arrangement +- **Manual Tiling**: User-controlled window placement +- **Floating Windows**: Support for floating applications +- **Split Layouts**: Horizontal and vertical splits + +### Window Rules +- **Application-Specific Rules**: Size, position, workspace assignment +- **Floating Applications**: Always-float for certain apps +- **Workspace Assignment**: Auto-assign apps to specific workspaces +- **Focus Behavior**: Control focus stealing and new window focus + +### Animation System +- **Window Animations**: Smooth open/close transitions +- **Workspace Transitions**: Fluid workspace switching +- **Resize Animations**: Smooth window resizing +- **Fade Effects**: Window fade in/out + +## Desktop Integration + +### Theme Integration +Desktop components automatically adapt to the selected theme: +- Window border colors +- Panel/taskbar theming +- Icon themes +- Cursor themes + +### Hardware Integration +- **GPU Acceleration**: Optimal performance on all graphics hardware +- **Multi-Monitor**: Automatic detection and configuration +- **HiDPI Support**: Proper scaling for high-resolution displays +- **Input Devices**: Touchpad gestures, mouse sensitivity + +### Audio Integration +- **Media Keys**: Hardware media key support +- **Volume Control**: On-screen volume indicators +- **Audio Device Switching**: Quick audio output switching +- **Notification Sounds**: System sound integration + +## Performance Optimization + +### GPU Optimization +- **Hardware Acceleration**: GPU-accelerated compositing +- **VSync Configuration**: Tear-free rendering +- **Frame Rate Management**: Adaptive refresh rates +- **Multi-GPU Support**: Optimal GPU selection + +### Memory Management +- **Efficient Compositing**: Minimal memory usage +- **Background Process Limits**: Control background applications +- **Cache Management**: Optimal caching strategies +- **Resource Monitoring**: System resource awareness + +### Battery Optimization (Laptops) +- **Power-Aware Rendering**: Reduced effects on battery +- **CPU Scaling**: Dynamic performance scaling +- **Display Brightness**: Automatic brightness adjustment +- **Background Process Management**: Suspend non-essential processes + +## Customization Options + +### Layout Customization +```nix +wayland.windowManager.hyprland.settings = { + general = { + gaps_in = 5; + gaps_out = 10; + border_size = 2; + layout = "dwindle"; # or "master" + }; + + decoration = { + rounding = 10; + blur = { + enabled = true; + size = 8; + passes = 1; + }; + drop_shadow = true; + shadow_range = 4; + shadow_render_power = 3; + }; +}; +``` + +### Animation Customization +```nix +animation = { + enabled = true; + bezier = [ + "wind, 0.05, 0.9, 0.1, 1.05" + "winIn, 0.1, 1.1, 0.1, 1.1" + "winOut, 0.3, -0.3, 0, 1" + ]; + + animation = [ + "windows, 1, 6, wind, slide" + "windowsIn, 1, 6, winIn, slide" + "windowsOut, 1, 5, winOut, slide" + "fade, 1, 10, default" + "workspaces, 1, 5, wind" + ]; +}; +``` + +## Desktop Components Integration + +### Panel (Waybar) +- System status display +- Workspace indicators +- System tray integration +- Custom module support + +### Application Launcher +- Quick application access +- Search functionality +- Recent application history +- Customizable appearance + +### File Manager Integration +- Desktop file operations +- Trash management +- Network location access +- Archive handling + +### Notification System +- Desktop notifications +- Notification history +- Do-not-disturb modes +- Custom notification rules + +## Troubleshooting + +### Common Issues +- **Performance Problems**: Check GPU acceleration +- **Input Issues**: Verify input device configuration +- **Display Problems**: Check monitor configuration +- **Audio Issues**: Verify PipeWire integration + +### Debugging Tools +- `hyprctl`: Hyprland control utility +- `waybar-log`: Panel debugging +- `journalctl`: System logs +- `htop`: Resource monitoring + +This desktop configuration provides a modern, efficient, and highly customizable desktop environment that adapts to user preferences while maintaining excellent performance across various hardware configurations. \ No newline at end of file diff --git a/modules/desktop/hyprland.nix b/modules/desktop/hyprland.nix new file mode 100644 index 0000000..37537d0 --- /dev/null +++ b/modules/desktop/hyprland.nix @@ -0,0 +1,575 @@ +{ + config, + pkgs, + lib, + inputs, + ... +}: + +let + inherit (lib) + mkEnableOption + mkOption + mkIf + mkMerge + types + optionals + optionalString + ; + cfg = config.omni.desktop; + omni = config.omni.lib; + user = config.omni.user; +in +{ + imports = [ + ./hyprland/autostart.nix + ./hyprland/bindings.nix + ./hyprland/idle.nix + ]; + + options.omni.desktop = { + enable = mkEnableOption "Omnixient Hyprland desktop environment"; + + monitors = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "DP-1,1920x1080@144,0x0,1" ]; + description = "Monitor configuration for Hyprland"; + }; + + defaultTerminal = mkOption { + type = types.str; + default = "ghostty"; + description = "Default terminal emulator"; + }; + + defaultBrowser = mkOption { + type = types.str; + default = "chromium"; + description = "Default web browser"; + }; + + wallpaper = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to wallpaper image (optional)"; + }; + + idleSuspend = mkOption { + type = types.bool; + default = false; + description = "Enable system suspend after idle timeout"; + }; + }; + + config = mkIf (cfg.enable) { + # Enable Hyprland at the system level. + # + # `programs.hyprland.portalPackage` is set to the xdg-desktop-portal-hyprland + # built by the same Hyprland flake input that provides `package`, so + # the auto-added portal (from the upstream programs.hyprland module) + # matches the hyprland binary's source. Mixing sources causes two + # copies of the same package in the closure and a systemd user-unit + # symlink collision when both try to install + # xdg-desktop-portal-hyprland.service. + programs.hyprland = { + enable = true; + xwayland.enable = true; + package = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.hyprland; + portalPackage = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.xdg-desktop-portal-hyprland; + }; + + # XDG portal for Wayland. We DON'T add xdg-desktop-portal-hyprland + # here — `programs.hyprland.enable = true` already adds it via the + # upstream module's `xdg.portal.extraPortals = [ cfg.portalPackage ]`. + # Adding it again would create a duplicate. + xdg.portal = { + enable = true; + extraPortals = [ + pkgs.xdg-desktop-portal-gtk + ]; + config.common.default = "*"; + }; + + # Session variables + environment.sessionVariables = { + NIXOS_OZONE_WL = "1"; + MOZ_ENABLE_WAYLAND = "1"; + QT_QPA_PLATFORM = "wayland"; + QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"; + GDK_BACKEND = "wayland"; + WLR_NO_HARDWARE_CURSORS = "1"; + TERMINAL = cfg.defaultTerminal; + BROWSER = cfg.defaultBrowser; + EDITOR = "nvim"; + }; + + # Essential packages for Hyprland desktop + environment.systemPackages = with pkgs; [ + wayland + wayland-protocols + wayland-utils + wlroots + hyprland-protocols + hyprpaper + hyprlock + hypridle + hyprpicker + waybar + wofi + rofi + walker + mako + libnotify + wl-clipboard + cliphist + wlr-randr + kanshi + grim + slurp + satty + wf-recorder + networkmanagerapplet + blueman + pasystray + impala + pamixer + pulsemixer + pavucontrol + wiremix + awww + nautilus + pkgs.thunar + ranger + yazi + polkit_gnome + adwaita-icon-theme + papirus-icon-theme + bibata-cursors + brightnessctl + playerctl + ]; + + # --- Home-manager Hyprland config (structured, mergeable by themes) --- + home-manager.users.${user} = { + wayland.windowManager.hyprland = { + enable = true; + package = config.programs.hyprland.package; + # Keep the legacy hyprlang config format (home.stateVersion < 26.05). + # Switching to "lua" is a separate, deliberate migration. + configType = "hyprlang"; + systemd.enable = true; + + settings = { + # Monitor config — user monitors + fallback + monitor = cfg.monitors ++ [ ",preferred,auto,1" ]; + + input = { + kb_layout = "us"; + kb_options = "caps:escape"; + follow_mouse = 1; + mouse_refocus = false; + sensitivity = 0; + touchpad = { + natural_scroll = true; + disable_while_typing = true; + tap-to-click = true; + scroll_factor = 0.5; + }; + }; + + general = { + gaps_in = 5; + gaps_out = 10; + border_size = 2; + layout = "dwindle"; + allow_tearing = false; + }; + + decoration = { + rounding = 10; + blur = { + enabled = true; + size = 6; + passes = 2; + new_optimizations = true; + ignore_opacity = true; + }; + # Hyprland 0.40+ moved shadow options from flat + # `drop_shadow`/`shadow_range`/`shadow_render_power` into a + # nested `shadow { enabled; range; render_power; color }` + # block. Theme modules contribute `shadow.color`. + shadow = { + enabled = true; + range = 20; + render_power = 3; + }; + dim_inactive = false; + dim_strength = 0.1; + }; + + animations = { + enabled = true; + bezier = [ + "overshot, 0.05, 0.9, 0.1, 1.05" + "smoothOut, 0.5, 0, 0.99, 0.99" + "smoothIn, 0.5, -0.5, 0.68, 1.5" + ]; + animation = [ + "windows, 1, 5, overshot, slide" + "windowsOut, 1, 4, smoothOut, slide" + "windowsIn, 1, 4, smoothIn, slide" + "windowsMove, 1, 4, default" + "border, 1, 10, default" + "borderangle, 1, 8, default" + "fade, 1, 7, default" + "workspaces, 1, 6, default" + ]; + }; + + dwindle = { + preserve_split = true; + force_split = 2; + }; + + # NOTE: Hyprland 0.49+ removed the entire `gestures` section + # in favor of new `gesture` keyword bindings (similar to + # `bind`). Touchpad workspace-swipe will need to be re-added + # via the new syntax — tracking as a follow-up. The old + # block was: + # gestures = { workspace_swipe = true; ... }; + + misc = { + force_default_wallpaper = 0; + disable_hyprland_logo = true; + disable_splash_rendering = true; + mouse_move_enables_dpms = true; + key_press_enables_dpms = true; + enable_swallow = true; + swallow_regex = "^(ghostty|alacritty|kitty|footclient)$"; + }; + + windowrule = [ + "opacity 0.9 override 0.9 override, match:class (ghostty|Alacritty|kitty)" + "opacity 0.9 override 0.9 override, match:class (Code|code-oss)" + "float on, match:class (pavucontrol|nm-connection-editor|blueman-manager)" + "float on, match:class (org.gnome.Calculator|gnome-calculator)" + "float on, match:title (Picture-in-Picture)" + "pin on, match:title (Picture-in-Picture)" + "float on, match:class (imv|mpv|vlc)" + "center on, match:class (imv|mpv|vlc)" + "float on, match:class (com.gabm.satty)" + "size 800 600, match:class (com.gabm.satty)" + "center on, match:class (com.gabm.satty)" + "workspace 4, match:class (Spotify)" + "workspace 5, match:class (org.telegram.desktop)" + ]; + }; + }; + + # Waybar + programs.waybar = { + enable = true; + systemd.enable = true; + settings = [ + { + reload_style_on_change = true; + layer = "top"; + position = "top"; + spacing = 0; + height = 26; + modules-left = [ + "custom/omni" + "hyprland/workspaces" + ]; + modules-center = [ "clock" ]; + modules-right = [ + "group/tray-expander" + "custom/screenshot" + "bluetooth" + "network" + "pulseaudio" + "cpu" + "backlight" + "battery" + "custom/power" + ]; + + "custom/omni" = { + format = "󱄅"; + on-click = "walker"; + on-click-right = "${cfg.defaultTerminal}"; + tooltip-format = "App Launcher"; + }; + "custom/screenshot" = { + format = "󰹑"; + on-click = "bash -c 'f=~/Pictures/Screenshots/screenshot_$(date +%Y-%m-%d-%H%M%S).png; grim -g \"$(slurp)\" \"$f\" && satty --filename \"$f\" --output-filename \"$f\" --copy-command wl-copy'"; + on-click-right = "bash -c 'f=~/Pictures/Screenshots/screenshot_$(date +%Y-%m-%d-%H%M%S).png; grim \"$f\" && satty --filename \"$f\" --output-filename \"$f\" --copy-command wl-copy'"; + tooltip-format = "Screenshot (right-click: fullscreen)"; + }; + "hyprland/workspaces" = { + on-click = "activate"; + format = "{icon}"; + format-icons = { + "1" = "1"; + "2" = "2"; + "3" = "3"; + "4" = "4"; + "5" = "5"; + "6" = "6"; + "7" = "7"; + "8" = "8"; + "9" = "9"; + "10" = "0"; + active = "󱓻"; + default = ""; + }; + persistent-workspaces = { + "1" = [ ]; + "2" = [ ]; + "3" = [ ]; + "4" = [ ]; + "5" = [ ]; + }; + }; + clock = { + format = "{:L%A %H:%M}"; + format-alt = "{:L%d %B W%V %Y}"; + tooltip = false; + }; + network = { + format-icons = [ + "󰤯" + "󰤟" + "󰤢" + "󰤥" + "󰤨" + ]; + format = "{icon}"; + format-wifi = "{icon}"; + format-ethernet = "󰀂"; + format-disconnected = "󰤮"; + tooltip-format-wifi = "{essid} ({frequency} GHz)\n⇣{bandwidthDownBytes} ⇡{bandwidthUpBytes}"; + tooltip-format-ethernet = "⇣{bandwidthDownBytes} ⇡{bandwidthUpBytes}"; + tooltip-format-disconnected = "Disconnected"; + interval = 3; + on-click = "${cfg.defaultTerminal} -e impala"; + }; + bluetooth = { + format = ""; + format-off = "󰂲"; + format-disabled = "󰂲"; + format-connected = "󰂱"; + format-no-controller = ""; + tooltip-format = "Devices connected: {num_connections}"; + on-click = "${cfg.defaultTerminal} -e bluetui"; + }; + pulseaudio = { + format = "{icon}"; + format-muted = "󰝟"; + format-icons = { + headphone = "󰋋"; + headset = "󰋎"; + default = [ + "󰕿" + "󰖀" + "󰕾" + ]; + }; + tooltip-format = "Playing at {volume}%"; + scroll-step = 5; + on-click = "${cfg.defaultTerminal} -e wiremix"; + on-click-right = "pamixer -t"; + }; + cpu = { + interval = 5; + format = "󰍛"; + on-click = "${cfg.defaultTerminal} -e btop"; + }; + backlight = { + format = "{icon}"; + format-icons = [ + "" + "" + "" + "" + "" + "" + "" + "" + "" + ]; + tooltip-format = "Brightness: {percent}%"; + }; + battery = { + format = "{icon}"; + format-discharging = "{icon}"; + format-charging = "{icon}"; + format-plugged = ""; + format-full = "󰂅"; + format-icons = { + charging = [ + "󰢜" + "󰂆" + "󰂇" + "󰂈" + "󰢝" + "󰂉" + "󰢞" + "󰂊" + "󰂋" + "󰂅" + ]; + default = [ + "󰁺" + "󰁻" + "󰁼" + "󰁽" + "󰁾" + "󰁿" + "󰂀" + "󰂁" + "󰂂" + "󰁹" + ]; + }; + tooltip-format-discharging = "{power:>1.0f}W↓ {capacity}%"; + tooltip-format-charging = "{power:>1.0f}W↑ {capacity}%"; + interval = 5; + states = { + warning = 20; + critical = 10; + }; + on-click = "${pkgs.writeShellScript "power-menu" '' + choice=$(printf "Lock\nLogout\nReboot\nShutdown" | ${pkgs.wofi}/bin/wofi --dmenu --prompt "Power" --width 200 --height 200) + case "$choice" in + Lock) hyprlock ;; + Logout) hyprctl dispatch exit ;; + Reboot) systemctl reboot ;; + Shutdown) systemctl poweroff ;; + esac + ''}"; + }; + "group/tray-expander" = { + orientation = "inherit"; + drawer = { + transition-duration = 600; + children-class = "tray-group-item"; + }; + modules = [ + "custom/expand-icon" + "tray" + ]; + }; + "custom/expand-icon" = { + format = ""; + tooltip = false; + }; + tray = { + icon-size = 12; + spacing = 17; + }; + "custom/power" = { + format = "⏻"; + tooltip-format = "Power menu"; + on-click = "${pkgs.writeShellScript "power-menu-btn" '' + choice=$(printf "Lock\nLogout\nReboot\nShutdown" | ${pkgs.wofi}/bin/wofi --dmenu --prompt "Power" --width 200 --height 200) + case "$choice" in + Lock) hyprlock ;; + Logout) hyprctl dispatch exit ;; + Reboot) systemctl reboot ;; + Shutdown) systemctl poweroff ;; + esac + ''}"; + }; + } + ]; + + style = lib.mkDefault '' + * { + font-family: "JetBrainsMono Nerd Font"; + font-size: 12px; + border: none; + border-radius: 0; + min-height: 0; + } + window#waybar { + background: rgba(30, 30, 46, 0.9); + color: #cdd6f4; + } + .modules-left { + margin-left: 8px; + } + .modules-right { + margin-right: 8px; + } + #workspaces button { + all: initial; + color: #cdd6f4; + padding: 0 6px; + margin: 0 1.5px; + min-width: 9px; + } + #workspaces button.empty { + opacity: 0.5; + } + #workspaces button.active { + color: #cba6f7; + } + #custom-omni { + margin: 0 12px 0 4px; + font-size: 14px; + color: #cba6f7; + } + #cpu, + #battery, + #pulseaudio, + #backlight { + min-width: 12px; + margin: 0 7.5px; + } + #custom-power { + min-width: 12px; + margin: 0 4px 0 7.5px; + color: #f38ba8; + } + #bluetooth { + margin-right: 17px; + } + #network { + margin-right: 13px; + } + #custom-screenshot { + margin-right: 17px; + } + #custom-expand-icon { + margin-right: 18px; + } + #tray { + margin-right: 16px; + } + #clock { + margin-left: 8.75px; + } + #custom-power:hover { + color: #1e1e2e; + background: #f38ba8; + border-radius: 5px; + } + #battery.charging { color: #a6e3a1; } + #battery.warning:not(.charging) { color: #fab387; } + #battery.critical:not(.charging) { + color: #f38ba8; + animation: blink 0.5s linear infinite alternate; + } + @keyframes blink { + to { background-color: #f38ba8; color: #1e1e2e; } + } + tooltip { + padding: 2px; + } + ''; + }; + }; + }; +} diff --git a/modules/desktop/hyprland/autostart.nix b/modules/desktop/hyprland/autostart.nix new file mode 100644 index 0000000..efcbeba --- /dev/null +++ b/modules/desktop/hyprland/autostart.nix @@ -0,0 +1,61 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + inherit (lib) mkIf optionals; + cfg = config.omni.desktop; + omni = config.omni.lib; + user = config.omni.user; +in +{ + config = mkIf (cfg.enable) { + home-manager.users.${user} = { + wayland.windowManager.hyprland.settings = { + exec-once = [ + # NOTE: do NOT add "waybar" here. Waybar is managed by the + # systemd user service from `programs.waybar.systemd.enable + # = true` in modules/desktop/hyprland.nix. Launching it + # from `exec-once` AS WELL produces two stacked bars on + # screen. The keybinding to restart waybar + # (`systemctl --user restart waybar` in bindings.nix) also + # assumes the systemd-managed instance. + "gnome-keyring-daemon --start --components=secrets,pkcs11" + "mako" + "awww init" + "nm-applet --indicator" + "blueman-applet" + "wl-paste --type text --watch cliphist store" + "wl-paste --type image --watch cliphist store" + "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1" + "mkdir -p ~/Pictures/Screenshots" + "hypridle" + "swayosd-server" + "[workspace 1 silent] ${cfg.defaultTerminal} -e tmux" + "[workspace 1 silent] ${cfg.defaultBrowser}" + ] + ++ optionals (omni.isEnabled "media") [ + "[workspace 4 silent] spotify" + ] + ++ optionals (omni.isEnabled "communication") [ + "[workspace 5 silent] signal-desktop --password-store=gnome-libsecret" + "[workspace 5 silent] element-desktop --password-store=gnome-libsecret" + "[workspace 5 silent] ferdium --password-store=gnome-libsecret" + # nixpkgs ships the binary as `Telegram` (capital T), not + # `telegram-desktop`. It's Qt, so it doesn't take the + # --password-store flag the Electron apps below use. + "[workspace 5 silent] Telegram" + ]; + + exec = + [ ] + ++ optionals (cfg.wallpaper != null) [ + "awww img ${toString cfg.wallpaper} --transition-type wipe" + ]; + }; + }; + }; +} diff --git a/modules/desktop/hyprland/bindings.nix b/modules/desktop/hyprland/bindings.nix new file mode 100644 index 0000000..e5097db --- /dev/null +++ b/modules/desktop/hyprland/bindings.nix @@ -0,0 +1,169 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + inherit (lib) mkIf optionals; + cfg = config.omni.desktop; + omni = config.omni.lib; + user = config.omni.user; + + terminal = cfg.defaultTerminal; + browser = cfg.defaultBrowser; +in +{ + config = mkIf (cfg.enable) { + home-manager.users.${user} = { + wayland.windowManager.hyprland.settings = { + "$mainMod" = "SUPER"; + + bind = [ + # Application launches + "$mainMod, Return, exec, ${terminal}" + "$mainMod, B, exec, ${browser}" + "$mainMod, E, exec, thunar" + "$mainMod, R, exec, walker" + "$mainMod, D, exec, walker" + + # Window management + "$mainMod, Q, killactive" + "$mainMod, F, fullscreen, 1" + "$mainMod SHIFT, F, fullscreen, 0" + "$mainMod, Space, togglefloating" + "$mainMod, P, pseudo" + "$mainMod, J, layoutmsg, togglesplit" + "$mainMod, G, togglegroup" + "$mainMod, Tab, changegroupactive" + + # Focus (vim keys) + "$mainMod, h, movefocus, l" + "$mainMod, l, movefocus, r" + "$mainMod, k, movefocus, u" + "$mainMod, j, movefocus, d" + + # Focus (arrow keys) + "$mainMod, left, movefocus, l" + "$mainMod, right, movefocus, r" + "$mainMod, up, movefocus, u" + "$mainMod, down, movefocus, d" + + # Move windows (vim keys) + "$mainMod SHIFT, h, movewindow, l" + "$mainMod SHIFT, l, movewindow, r" + "$mainMod SHIFT, k, movewindow, u" + "$mainMod SHIFT, j, movewindow, d" + + # Move windows (arrow keys) + "$mainMod SHIFT, left, movewindow, l" + "$mainMod SHIFT, right, movewindow, r" + "$mainMod SHIFT, up, movewindow, u" + "$mainMod SHIFT, down, movewindow, d" + + # Workspaces + "$mainMod, 1, workspace, 1" + "$mainMod, 2, workspace, 2" + "$mainMod, 3, workspace, 3" + "$mainMod, 4, workspace, 4" + "$mainMod, 5, workspace, 5" + "$mainMod, 6, workspace, 6" + "$mainMod, 7, workspace, 7" + "$mainMod, 8, workspace, 8" + "$mainMod, 9, workspace, 9" + "$mainMod, 0, workspace, 10" + + # Move to workspace + "$mainMod SHIFT, 1, movetoworkspace, 1" + "$mainMod SHIFT, 2, movetoworkspace, 2" + "$mainMod SHIFT, 3, movetoworkspace, 3" + "$mainMod SHIFT, 4, movetoworkspace, 4" + "$mainMod SHIFT, 5, movetoworkspace, 5" + "$mainMod SHIFT, 6, movetoworkspace, 6" + "$mainMod SHIFT, 7, movetoworkspace, 7" + "$mainMod SHIFT, 8, movetoworkspace, 8" + "$mainMod SHIFT, 9, movetoworkspace, 9" + "$mainMod SHIFT, 0, movetoworkspace, 10" + + # Scroll workspaces + "$mainMod, mouse_down, workspace, e+1" + "$mainMod, mouse_up, workspace, e-1" + + # Screenshots (grim + satty for annotation) + ", Print, exec, grim -g \"$(slurp)\" - | satty -f - --output-filename ~/Pictures/Screenshots/$(date +'screenshot_%Y-%m-%d-%H%M%S.png')" + "SHIFT, Print, exec, grim - | satty -f - --output-filename ~/Pictures/Screenshots/$(date +'screenshot_%Y-%m-%d-%H%M%S.png')" + "$mainMod, Print, exec, grim -g \"$(slurp)\" - | wl-copy" + + # Screen recording + "$mainMod SHIFT, R, exec, wf-recorder -g \"$(slurp)\" -f ~/Videos/recording_$(date +'%Y-%m-%d-%H%M%S.mp4')" + + # Lock screen + "$mainMod, L, exec, hyprlock" + + # System / Power + "$mainMod SHIFT, Q, exit," + "$mainMod SHIFT, P, exec, systemctl poweroff" + "$mainMod SHIFT, B, exec, systemctl reboot" + "$mainMod ALT, R, exec, systemctl --user restart waybar" + + # Clipboard + "$mainMod, Y, exec, cliphist list | walker --dmenu | cliphist decode | wl-copy" + "$mainMod, V, exec, cliphist list | wofi --dmenu | cliphist decode | wl-copy" + + # Color picker + "$mainMod SHIFT, C, exec, hyprpicker -a" + + # Scratchpad + "$mainMod, S, togglespecialworkspace, magic" + "$mainMod SHIFT, S, movetoworkspace, special:magic" + + # Theme cycling + "$mainMod ALT, T, exec, omni-theme-bg-next" + "$mainMod ALT SHIFT, T, exec, omni-theme-next" + ] + # Coding shortcuts + ++ optionals (omni.isEnabled "coding") [ + "$mainMod, C, exec, code" + "$mainMod SHIFT, C, exec, ${terminal} -e nvim" + ] + # Communication shortcuts + ++ optionals (omni.isEnabled "communication") [ + "$mainMod CTRL, D, exec, discord" + "$mainMod CTRL SHIFT, D, exec, slack" + ]; + + # Resize with vim/arrow keys + binde = [ + "$mainMod CTRL, h, resizeactive, -50 0" + "$mainMod CTRL, l, resizeactive, 50 0" + "$mainMod CTRL, k, resizeactive, 0 -50" + "$mainMod CTRL, j, resizeactive, 0 50" + "$mainMod CTRL, left, resizeactive, -50 0" + "$mainMod CTRL, right, resizeactive, 50 0" + "$mainMod CTRL, up, resizeactive, 0 -50" + "$mainMod CTRL, down, resizeactive, 0 50" + ]; + + # Mouse bindings + bindm = [ + "$mainMod, mouse:272, movewindow" + "$mainMod, mouse:273, resizewindow" + ]; + + # Media/brightness keys (locked — work even when input inhibited) + bindl = [ + ", 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" + ", XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle" + ", XF86MonBrightnessUp, exec, brightnessctl set 10%+" + ", XF86MonBrightnessDown, exec, brightnessctl set 10%-" + ", XF86AudioPlay, exec, playerctl play-pause" + ", XF86AudioNext, exec, playerctl next" + ", XF86AudioPrev, exec, playerctl previous" + ]; + }; + }; + }; +} diff --git a/modules/desktop/hyprland/idle.nix b/modules/desktop/hyprland/idle.nix new file mode 100644 index 0000000..f6ccd9f --- /dev/null +++ b/modules/desktop/hyprland/idle.nix @@ -0,0 +1,179 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + inherit (lib) mkIf optionals; + cfg = config.omni.desktop; + user = config.omni.user; +in +{ + config = mkIf (cfg.enable) { + home-manager.users.${user} = { + # Hypridle — screen dimming, locking, DPMS, optional suspend + services.hypridle = { + enable = true; + settings = { + general = { + lock_cmd = "pidof hyprlock || hyprlock"; + before_sleep_cmd = "loginctl lock-session"; + after_sleep_cmd = "hyprctl dispatch dpms on"; + }; + + listener = [ + # Dim screen after 5 minutes + { + timeout = 300; + on-timeout = "brightnessctl -s set 10%"; + on-resume = "brightnessctl -r"; + } + # Lock screen after 10 minutes + { + timeout = 600; + on-timeout = "loginctl lock-session"; + } + # Turn off screen after 15 minutes + { + timeout = 900; + on-timeout = "hyprctl dispatch dpms off"; + on-resume = "hyprctl dispatch dpms on"; + } + ] + ++ optionals cfg.idleSuspend [ + # Suspend after 30 minutes + { + timeout = 1800; + on-timeout = "systemctl suspend"; + } + ]; + }; + }; + + # Hyprlock — lock screen appearance + programs.hyprlock = { + enable = true; + settings = { + general = { + disable_loading_bar = true; + grace = 5; + hide_cursor = false; + no_fade_in = false; + }; + + background = [ + { + monitor = ""; + path = if cfg.wallpaper != null then toString cfg.wallpaper else "~/Pictures/wallpaper.jpg"; + blur_passes = 3; + blur_size = 8; + noise = 0.0117; + contrast = 0.8916; + brightness = 0.8172; + vibrancy = 0.1696; + vibrancy_darkness = 0.0; + } + ]; + + image = [ + { + monitor = ""; + path = "~/.face"; + size = 150; + rounding = -1; + border_size = 4; + border_color = "rgb(221, 221, 221)"; + position = "0, 200"; + halign = "center"; + valign = "center"; + } + ]; + + label = [ + # Time + { + monitor = ""; + text = ''cmd[update:30000] echo "$TIME"''; + color = "rgba(200, 200, 200, 1.0)"; + font_size = 55; + font_family = "JetBrainsMono Nerd Font"; + position = "-100, -40"; + halign = "right"; + valign = "bottom"; + shadow_passes = 5; + shadow_size = 10; + } + # Date + { + monitor = ""; + text = ''cmd[update:43200000] echo "$(date +"%A, %d %B %Y")"''; + color = "rgba(200, 200, 200, 1.0)"; + font_size = 25; + font_family = "JetBrainsMono Nerd Font"; + position = "-100, -150"; + halign = "right"; + valign = "bottom"; + shadow_passes = 5; + shadow_size = 10; + } + # Username + { + monitor = ""; + text = user; + color = "rgba(200, 200, 200, 1.0)"; + font_size = 20; + font_family = "JetBrainsMono Nerd Font"; + position = "0, 80"; + halign = "center"; + valign = "center"; + shadow_passes = 5; + shadow_size = 10; + } + # Hostname + { + monitor = ""; + text = ''cmd[update:60000] echo "$(uname -n)"''; + color = "rgba(200, 200, 200, 0.8)"; + font_size = 16; + font_family = "JetBrainsMono Nerd Font"; + position = "100, 40"; + halign = "left"; + valign = "bottom"; + shadow_passes = 5; + shadow_size = 10; + } + ]; + + input-field = [ + { + monitor = ""; + size = "200, 50"; + outline_thickness = 3; + dots_size = 0.33; + dots_spacing = 0.15; + dots_center = false; + dots_rounding = -1; + outer_color = "rgb(151515)"; + inner_color = "rgb(200, 200, 200)"; + font_color = "rgb(10, 10, 10)"; + fade_on_empty = true; + fade_timeout = 1000; + placeholder_text = "Input Password..."; + hide_input = false; + rounding = -1; + check_color = "rgb(204, 136, 34)"; + fail_color = "rgb(204, 34, 34)"; + fail_text = "$FAIL ($ATTEMPTS)"; + fail_timeout = 2000; + position = "0, -20"; + halign = "center"; + valign = "center"; + } + ]; + }; + }; + }; + }; +}