From f957d6965a8e6fb604f3e649ad6efc73192e7823 Mon Sep 17 00:00:00 2001 From: Igor Camilo <11697241+igorcamilo@users.noreply.github.com> Date: Sun, 26 Jul 2026 23:49:57 +0200 Subject: [PATCH 1/6] Add AMD GPU support and enhance Hyprland config Enable redistributable firmware and graphics support for AMD GPUs. Configure PipeWire as the audio server and add Bluetooth support. Enhance Hyprland keybindings and include necessary packages for media control and application launching. Update splash screen settings to ensure proper GPU driver loading during boot. --- configuration.nix | 37 ++++++++++++++++++ dotfiles/hypr/hyprland.conf | 77 ++++++++++++++++++++++++++++++++++++- home.nix | 10 +++++ modules/boot/splash.nix | 5 +++ 4 files changed, 128 insertions(+), 1 deletion(-) diff --git a/configuration.nix b/configuration.nix index a396a3b..2770b7e 100644 --- a/configuration.nix +++ b/configuration.nix @@ -34,6 +34,36 @@ zramSwap.enable = true; + # AMD GPU: Mesa provides RADV (Vulkan) and RadeonSI (OpenGL) for Hyprland's + # rendering, and the redistributable firmware carries the microcode the + # amdgpu kernel driver loads for display and video decode/encode. + hardware = { + enableRedistributableFirmware = true; + graphics = { + enable = true; + enable32Bit = true; # Steam and other 32-bit apps need these too. + }; + bluetooth = { + enable = true; + powerOnBoot = true; + }; + }; + + # PipeWire is the current NixOS-recommended audio server, replacing both + # PulseAudio and JACK. + security.rtkit.enable = true; # Realtime scheduling for PipeWire. + services.pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + }; + + # Pairing UI and polkit rules for the Bluetooth radio enabled above. + services.blueman.enable = true; + + services.fwupd.enable = true; + # Desktop: Hyprland, with Quickshell as the shell layer (bar, wallpaper, # lock screen). Wallpaper is drawn by Quickshell itself (a background # layer-shell surface), so no separate wallpaper daemon is installed. @@ -43,9 +73,16 @@ # Registers zsh as a valid login shell; users.users.igor.shell above # is what makes it igor's actual default. zsh.enable = true; + # Needs the 32-bit graphics support enabled above. + steam.enable = true; }; qt.enable = true; + # GUI privilege prompts (mounting a drive, some NetworkManager actions) + # need an authentication agent; home.nix installs Hyprland's own + # (hyprpolkitagent) and hyprland.conf starts it. + security.polkit.enable = true; + # greetd runs a throwaway Hyprland+Quickshell "greeter" session (see # dotfiles/hypr/greeter.conf and dotfiles/quickshell/greeter) that # authenticates over greetd's own IPC protocol, then hands off to the diff --git a/dotfiles/hypr/hyprland.conf b/dotfiles/hypr/hyprland.conf index 23c5fbd..7c1567b 100644 --- a/dotfiles/hypr/hyprland.conf +++ b/dotfiles/hypr/hyprland.conf @@ -1,6 +1,81 @@ $mainMod = SUPER +$terminal = ghostty +$fileManager = dolphin +$menu = wofi --show drun + +# Keyboard layout. Change kb_layout if this isn't a US physical layout. +input { + kb_layout = us +} exec-once = quickshell +exec-once = hyprpolkitagent +bind = $mainMod, Return, exec, $terminal +bind = $mainMod, Q, exec, $terminal +bind = $mainMod, C, killactive, +bind = $mainMod, M, exit, +bind = $mainMod, E, exec, $fileManager # no file manager installed yet +bind = $mainMod, V, togglefloating, +bind = $mainMod, R, exec, $menu +bind = $mainMod, P, pseudo, +bind = $mainMod, J, togglesplit, bind = $mainMod, L, exec, systemctl --user start quickshell-lock.service -bind = $mainMod, Return, exec, ghostty + +# Move focus with mainMod + arrow keys +bind = $mainMod, left, movefocus, l +bind = $mainMod, right, movefocus, r +bind = $mainMod, up, movefocus, u +bind = $mainMod, down, movefocus, d + +# Switch workspace with mainMod + [0-9] +bind = $mainMod, 1, workspace, 1 +bind = $mainMod, 2, workspace, 2 +bind = $mainMod, 3, workspace, 3 +bind = $mainMod, 4, workspace, 4 +bind = $mainMod, 5, workspace, 5 +bind = $mainMod, 6, workspace, 6 +bind = $mainMod, 7, workspace, 7 +bind = $mainMod, 8, workspace, 8 +bind = $mainMod, 9, workspace, 9 +bind = $mainMod, 0, workspace, 10 + +# Move the focused window to a workspace with mainMod + SHIFT + [0-9] +bind = $mainMod SHIFT, 1, movetoworkspace, 1 +bind = $mainMod SHIFT, 2, movetoworkspace, 2 +bind = $mainMod SHIFT, 3, movetoworkspace, 3 +bind = $mainMod SHIFT, 4, movetoworkspace, 4 +bind = $mainMod SHIFT, 5, movetoworkspace, 5 +bind = $mainMod SHIFT, 6, movetoworkspace, 6 +bind = $mainMod SHIFT, 7, movetoworkspace, 7 +bind = $mainMod SHIFT, 8, movetoworkspace, 8 +bind = $mainMod SHIFT, 9, movetoworkspace, 9 +bind = $mainMod SHIFT, 0, movetoworkspace, 10 + +# Scratchpad-style special workspace +bind = $mainMod, S, togglespecialworkspace, magic +bind = $mainMod SHIFT, S, movetoworkspace, special:magic + +# Scroll through workspaces with mainMod + scroll wheel +bind = $mainMod, mouse_down, workspace, e+1 +bind = $mainMod, mouse_up, workspace, e-1 + +# Move/resize windows with mainMod + drag +bindm = $mainMod, mouse:272, movewindow +bindm = $mainMod, mouse:273, resizewindow + +# Volume and mic mute +bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ +bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- +bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle +bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle + +# Screen brightness; laptop panels only, likely a no-op on desktop monitors +bindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+ +bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%- + +# Media playback +bindl = ,XF86AudioNext, exec, playerctl next +bindl = ,XF86AudioPause, exec, playerctl play-pause +bindl = ,XF86AudioPlay, exec, playerctl play-pause +bindl = ,XF86AudioPrev, exec, playerctl previous diff --git a/home.nix b/home.nix index 32dd24f..7f42bc3 100644 --- a/home.nix +++ b/home.nix @@ -16,6 +16,16 @@ homeDirectory = "/home/igor"; stateVersion = "26.05"; + # hyprpolkitagent: GUI polkit agent, started from hyprland.conf. wofi: + # app launcher bound in hyprland.conf. playerctl and wireplumber + # (wpctl): back the media-key binds in hyprland.conf. + packages = [ + pkgs.hyprpolkitagent + pkgs.wofi + pkgs.playerctl + pkgs.wireplumber + ]; + # Hyprland session and Quickshell shells (bar + lock screen). The # greeter has its own copy of Quickshell's config, published via # environment.etc in configuration.nix instead of home-manager, since diff --git a/modules/boot/splash.nix b/modules/boot/splash.nix index 36628dc..000a864 100644 --- a/modules/boot/splash.nix +++ b/modules/boot/splash.nix @@ -7,6 +7,11 @@ _: theme = "bgrt"; }; + # Load the GPU driver during the initrd, before Plymouth starts, so the + # splash renders at its final resolution instead of flickering when the + # driver takes over later in boot. + initrd.kernelModules = [ "amdgpu" ]; + # Keep routine boot messages behind the splash. Press Escape during boot # to switch between Plymouth and the text details. consoleLogLevel = 3; From 101df563ce115d034e3d9a46573a400c162e9cfd Mon Sep 17 00:00:00 2001 From: Igor Camilo <11697241+igorcamilo@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:54:55 +0200 Subject: [PATCH 2/6] Add Quickshell launcher and improve Qt theming Introduce a new Quickshell launcher for enhanced app access and streamline the user interface with Qt theming support. The configuration now includes the necessary service for Quickshell and integrates Qt5/6 theming through qt5ct and Kvantum. This improves the overall aesthetic and usability of the desktop environment. --- configuration.nix | 14 ++- dotfiles/hypr/hyprland.conf | 5 +- dotfiles/quickshell/launcher/shell.qml | 149 +++++++++++++++++++++++++ home.nix | 38 ++++++- 4 files changed, 196 insertions(+), 10 deletions(-) create mode 100644 dotfiles/quickshell/launcher/shell.qml diff --git a/configuration.nix b/configuration.nix index 2770b7e..aca8edb 100644 --- a/configuration.nix +++ b/configuration.nix @@ -76,7 +76,19 @@ # Needs the 32-bit graphics support enabled above. steam.enable = true; }; - qt.enable = true; + # qt5ct/qt6ct (platformTheme) is the standard way to theme Qt apps outside + # a running KDE Plasma session; Kvantum (style) is the engine that actually + # renders the theme qt5ct/qt6ct picks. home.nix carries the GTK3/4 side. + qt = { + enable = true; + platformTheme = "qt5ct"; + style = "kvantum"; + }; + + # xdg-desktop-portal-hyprland (added automatically by programs.hyprland + # above) only covers Screenshot/ScreenCast. Dolphin's own portal fills in + # FileChooser and other KDE-flavored requests. + xdg.portal.extraPortals = [ pkgs.kdePackages.xdg-desktop-portal-kde ]; # GUI privilege prompts (mounting a drive, some NetworkManager actions) # need an authentication agent; home.nix installs Hyprland's own diff --git a/dotfiles/hypr/hyprland.conf b/dotfiles/hypr/hyprland.conf index 7c1567b..44e6421 100644 --- a/dotfiles/hypr/hyprland.conf +++ b/dotfiles/hypr/hyprland.conf @@ -1,7 +1,6 @@ $mainMod = SUPER $terminal = ghostty $fileManager = dolphin -$menu = wofi --show drun # Keyboard layout. Change kb_layout if this isn't a US physical layout. input { @@ -15,9 +14,9 @@ bind = $mainMod, Return, exec, $terminal bind = $mainMod, Q, exec, $terminal bind = $mainMod, C, killactive, bind = $mainMod, M, exit, -bind = $mainMod, E, exec, $fileManager # no file manager installed yet +bind = $mainMod, E, exec, $fileManager bind = $mainMod, V, togglefloating, -bind = $mainMod, R, exec, $menu +bind = $mainMod, R, exec, systemctl --user start quickshell-launcher.service bind = $mainMod, P, pseudo, bind = $mainMod, J, togglesplit, bind = $mainMod, L, exec, systemctl --user start quickshell-lock.service diff --git a/dotfiles/quickshell/launcher/shell.qml b/dotfiles/quickshell/launcher/shell.qml new file mode 100644 index 0000000..141e3f7 --- /dev/null +++ b/dotfiles/quickshell/launcher/shell.qml @@ -0,0 +1,149 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Widgets + +ShellRoot { + PanelWindow { + id: launcher + + WlrLayershell.layer: WlrLayer.Overlay + exclusionMode: ExclusionMode.Ignore + focusable: true + anchors { + top: true + bottom: true + left: true + right: true + } + color: "transparent" + + property string query: "" + + function launchSelected() { + if (list.currentItem && list.currentItem.modelData) { + list.currentItem.modelData.execute(); + Qt.quit(); + } + } + + // Click outside the card to dismiss. + MouseArea { + anchors.fill: parent + onClicked: Qt.quit() + } + + Rectangle { + anchors.centerIn: parent + width: 560 + height: 360 + radius: 8 + color: "#1a1b26" + + MouseArea { + // Swallow clicks so they don't reach the dismiss MouseArea behind. + anchors.fill: parent + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 12 + spacing: 8 + + TextField { + id: input + Layout.fillWidth: true + placeholderText: "Run..." + font.pixelSize: 18 + color: "#c0caf5" + focus: true + padding: 12 + + background: Rectangle { + color: "transparent" + } + + onTextChanged: { + launcher.query = text; + list.currentIndex = filtered.values.length > 0 ? 0 : -1; + } + + Keys.onEscapePressed: Qt.quit() + Keys.onPressed: event => { + if (event.key === Qt.Key_Up) { + event.accepted = true; + if (list.currentIndex > 0) + list.currentIndex--; + } else if (event.key === Qt.Key_Down) { + event.accepted = true; + if (list.currentIndex < list.count - 1) + list.currentIndex++; + } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + event.accepted = true; + launcher.launchSelected(); + } + } + } + + ScriptModel { + id: filtered + values: { + const all = [...DesktopEntries.applications.values]; + const q = launcher.query.trim().toLowerCase(); + return q === "" ? all : all.filter(d => d.name && d.name.toLowerCase().includes(q)); + } + } + + ListView { + id: list + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + model: filtered.values + currentIndex: filtered.values.length > 0 ? 0 : -1 + highlight: Rectangle { + radius: 4 + color: "#c0caf5" + opacity: 0.2 + } + + delegate: Item { + id: entry + required property var modelData + required property int index + width: ListView.view.width + height: 36 + + MouseArea { + anchors.fill: parent + onClicked: list.currentIndex = entry.index + onDoubleClicked: launcher.launchSelected() + } + + Row { + anchors.fill: parent + anchors.margins: 8 + spacing: 10 + + IconImage { + source: Quickshell.iconPath(modelData.icon, true) + width: 20 + height: 20 + } + + Text { + text: modelData.name + color: "#c0caf5" + font.pixelSize: 14 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + } + } + } + } + } + } +} diff --git a/home.nix b/home.nix index 7f42bc3..a7f5c43 100644 --- a/home.nix +++ b/home.nix @@ -11,29 +11,38 @@ }; }; + systemd.user.services.quickshell-launcher = { + Unit.Description = "Quickshell app launcher"; + Service = { + Type = "simple"; + ExecStart = "${pkgs.quickshell}/bin/quickshell -c launcher"; + }; + }; + home = { username = "igor"; homeDirectory = "/home/igor"; stateVersion = "26.05"; - # hyprpolkitagent: GUI polkit agent, started from hyprland.conf. wofi: - # app launcher bound in hyprland.conf. playerctl and wireplumber - # (wpctl): back the media-key binds in hyprland.conf. + # hyprpolkitagent: GUI polkit agent, started from hyprland.conf. dolphin: + # file manager bound in hyprland.conf. playerctl and wireplumber (wpctl): + # back the media-key binds in hyprland.conf. packages = [ pkgs.hyprpolkitagent - pkgs.wofi + pkgs.kdePackages.dolphin pkgs.playerctl pkgs.wireplumber ]; - # Hyprland session and Quickshell shells (bar + lock screen). The - # greeter has its own copy of Quickshell's config, published via + # Hyprland session and Quickshell shells (bar, lock screen, launcher). + # The greeter has its own copy of Quickshell's config, published via # environment.etc in configuration.nix instead of home-manager, since # it runs as a separate system user with no home directory here. file = { ".config/hypr/hyprland.conf".source = ./dotfiles/hypr/hyprland.conf; ".config/quickshell/shell.qml".source = ./dotfiles/quickshell/bar/shell.qml; ".config/quickshell/lockscreen".source = ./dotfiles/quickshell/lockscreen; + ".config/quickshell/launcher".source = ./dotfiles/quickshell/launcher; ".config/quickshell/shared".source = ./dotfiles/quickshell/shared; }; }; @@ -73,6 +82,23 @@ }; }; + # GTK4/libadwaita apps ignore a full theme override (see the gtk.gtk4.theme + # warning in home-manager's own module), so only GTK3 gets one: adw-gtk3 + # mimics libadwaita's look, keeping GTK3 and GTK4 apps consistent without + # fighting GTK4 for it. colorScheme and iconTheme apply to both versions. + gtk = { + enable = true; + theme = { + name = "adw-gtk3-dark"; + package = pkgs.adw-gtk3; + }; + iconTheme = { + name = "Papirus-Dark"; + package = pkgs.papirus-icon-theme; + }; + colorScheme = "dark"; + }; + xdg.autostart.enable = true; # Auto-lock on idle: lock at 5 minutes, blank the display 30 seconds From ee36a350afe00629881d11191328314e01756dcd Mon Sep 17 00:00:00 2001 From: Igor Camilo <11697241+igorcamilo@users.noreply.github.com> Date: Mon, 27 Jul 2026 01:15:18 +0200 Subject: [PATCH 3/6] Add Notifications support and Firefox config This commit introduces a new Notifications component in the Quickshell bar, enhancing user experience by displaying tracked notifications. Additionally, it configures Firefox to use the adw-gtk3-dark theme and Papirus icons, ensuring consistent theming across applications. No breaking changes introduced. --- dotfiles/quickshell/bar/Notifications.qml | 104 ++++++++++++++++++++++ dotfiles/quickshell/bar/shell.qml | 2 + home.nix | 9 ++ 3 files changed, 115 insertions(+) create mode 100644 dotfiles/quickshell/bar/Notifications.qml diff --git a/dotfiles/quickshell/bar/Notifications.qml b/dotfiles/quickshell/bar/Notifications.qml new file mode 100644 index 0000000..c29adb3 --- /dev/null +++ b/dotfiles/quickshell/bar/Notifications.qml @@ -0,0 +1,104 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Widgets +import Quickshell.Services.Notifications + +PanelWindow { + WlrLayershell.layer: WlrLayer.Overlay + exclusionMode: ExclusionMode.Ignore + anchors { + top: true + right: true + } + margins { + top: 40 + right: 10 + } + implicitWidth: 340 + implicitHeight: column.implicitHeight + color: "transparent" + + NotificationServer { + id: server + bodySupported: true + + onNotification: notif => { + notif.tracked = true; + } + } + + ColumnLayout { + id: column + width: parent.width + spacing: 8 + + Repeater { + model: server.trackedNotifications + + delegate: Rectangle { + id: card + required property var modelData + Layout.fillWidth: true + implicitHeight: content.implicitHeight + 24 + radius: 8 + color: "#1a1b26" + + RowLayout { + id: content + anchors.fill: parent + anchors.margins: 12 + spacing: 10 + + IconImage { + source: Quickshell.iconPath(card.modelData.appIcon, true) + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + Layout.alignment: Qt.AlignTop + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + + Text { + Layout.fillWidth: true + text: card.modelData.appName + color: "#c0caf5" + opacity: 0.6 + font.pixelSize: 11 + } + Text { + Layout.fillWidth: true + text: card.modelData.summary + color: "#c0caf5" + font.pixelSize: 14 + font.bold: true + wrapMode: Text.Wrap + } + Text { + Layout.fillWidth: true + visible: card.modelData.body !== "" + text: card.modelData.body + color: "#c0caf5" + font.pixelSize: 12 + wrapMode: Text.Wrap + } + } + } + + MouseArea { + anchors.fill: parent + onClicked: card.modelData.dismiss() + } + + Timer { + running: true + interval: card.modelData.expireTimeout > 0 ? card.modelData.expireTimeout * 1000 : 5000 + onTriggered: card.modelData.expire() + } + } + } + } +} diff --git a/dotfiles/quickshell/bar/shell.qml b/dotfiles/quickshell/bar/shell.qml index 05714f3..2010995 100644 --- a/dotfiles/quickshell/bar/shell.qml +++ b/dotfiles/quickshell/bar/shell.qml @@ -65,4 +65,6 @@ ShellRoot { } } } + + Notifications {} } diff --git a/home.nix b/home.nix index a7f5c43..bd43f3c 100644 --- a/home.nix +++ b/home.nix @@ -70,6 +70,15 @@ }; }; + # Firefox is GTK3, so it already picks up the adw-gtk3-dark theme and + # Papirus icons below for its native chrome (dialogs, scrollbars). Its + # own tab/toolbar chrome is a separate layer, themed via userChrome on + # the profile below once there's a real stylesheet to put there. + firefox = { + enable = true; + profiles.igor.isDefault = true; + }; + # home-manager-managed (not just enabled in configuration.nix) so that # Starship's shell hook below gets woven into ~/.zshrc automatically. zsh.enable = true; From a6dd1d967a8c0e8db94f7f0139ad3dbeba6ec5e5 Mon Sep 17 00:00:00 2001 From: Igor Camilo <11697241+igorcamilo@users.noreply.github.com> Date: Mon, 27 Jul 2026 08:31:21 +0200 Subject: [PATCH 4/6] Refactor NixOS services configuration for clarity Moved PipeWire and Bluetooth service configurations into a dedicated services block for better organization. Added greetd configuration to manage Hyprland sessions effectively. This improves readability and maintainability of the configuration file. --- configuration.nix | 58 +++++++++++++++++++++++++---------------------- lefthook.yml | 10 ++++++++ 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/configuration.nix b/configuration.nix index aca8edb..7ed2823 100644 --- a/configuration.nix +++ b/configuration.nix @@ -49,20 +49,40 @@ }; }; - # PipeWire is the current NixOS-recommended audio server, replacing both - # PulseAudio and JACK. security.rtkit.enable = true; # Realtime scheduling for PipeWire. - services.pipewire = { - enable = true; - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - }; - # Pairing UI and polkit rules for the Bluetooth radio enabled above. - services.blueman.enable = true; + services = { + # PipeWire is the current NixOS-recommended audio server, replacing both + # PulseAudio and JACK. + pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + }; - services.fwupd.enable = true; + # Pairing UI and polkit rules for the Bluetooth radio enabled above. + blueman.enable = true; + + fwupd.enable = true; + + # greetd runs a throwaway Hyprland+Quickshell "greeter" session (see + # dotfiles/hypr/greeter.conf and dotfiles/quickshell/greeter) that + # authenticates over greetd's own IPC protocol, then hands off to the + # user's normal session below. The greeter runs as its own system + # user (created automatically by this module), so its files are + # published system-wide via environment.etc below rather than + # home-manager. + greetd = { + enable = true; + settings.default_session = { + # Preserve compositor and greeter output after their runtime directory + # disappears. Read it with: journalctl -b -t greetd-greeter + command = "${pkgs.systemd}/bin/systemd-cat --identifier=greetd-greeter -- start-hyprland -- --config /etc/greetd/hyprland.conf"; + user = "greeter"; + }; + }; + }; # Desktop: Hyprland, with Quickshell as the shell layer (bar, wallpaper, # lock screen). Wallpaper is drawn by Quickshell itself (a background @@ -95,12 +115,6 @@ # (hyprpolkitagent) and hyprland.conf starts it. security.polkit.enable = true; - # greetd runs a throwaway Hyprland+Quickshell "greeter" session (see - # dotfiles/hypr/greeter.conf and dotfiles/quickshell/greeter) that - # authenticates over greetd's own IPC protocol, then hands off to the - # user's normal session below. The greeter runs as its own system - # user (created automatically by this module), so its files are - # published system-wide via environment.etc rather than home-manager. environment = { systemPackages = [ pkgs.git @@ -122,16 +136,6 @@ # just for igor, since fontconfig discovery works the same either way. fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ]; - services.greetd = { - enable = true; - settings.default_session = { - # Preserve compositor and greeter output after their runtime directory - # disappears. Read it with: journalctl -b -t greetd-greeter - command = "${pkgs.systemd}/bin/systemd-cat --identifier=greetd-greeter -- start-hyprland -- --config /etc/greetd/hyprland.conf"; - user = "greeter"; - }; - }; - nix.settings.experimental-features = [ "nix-command" "flakes" diff --git a/lefthook.yml b/lefthook.yml index 28bb060..3d3b927 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -2,3 +2,13 @@ pre-commit: commands: secrets: run: gitleaks protect --staged --redact + nixfmt: + glob: "*.nix" + run: nixfmt {staged_files} + stage_fixed: true + # Same invocation scripts/check.sh uses in CI: statix doesn't offer a + # reliable fix for every lint (e.g. repeated-key warnings are report-only), + # so this checks rather than auto-fixes, same as CI does. + statix: + glob: "*.nix" + run: statix check . From 455a590f51562eba28e4a35f962fdd4bbd9fc363 Mon Sep 17 00:00:00 2001 From: Igor Camilo <11697241+igorcamilo@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:06:09 +0200 Subject: [PATCH 5/6] Update CI config and enhance home.nix packages Change CI jobs to run on Ubuntu 26.04 for consistency across environments. Added ffmpegthumbs, ffmpeg, and libva-utils to home.nix for improved media handling and GPU video decode support. Set session variables for nano as the default editor and enabled VA-API in Firefox for better performance. --- .github/workflows/ci.yml | 6 +++--- home.nix | 46 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1fb532..7b4f076 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ concurrency: jobs: validate-system: name: Validate igor-desktop - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 120 steps: - name: Check out repository @@ -49,7 +49,7 @@ jobs: repository-quality: name: Repository quality - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 45 steps: - name: Check out repository @@ -83,7 +83,7 @@ jobs: history-secrets: name: Git history secrets - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 20 steps: - name: Check out repository and history diff --git a/home.nix b/home.nix index bd43f3c..3c959e3 100644 --- a/home.nix +++ b/home.nix @@ -25,15 +25,36 @@ stateVersion = "26.05"; # hyprpolkitagent: GUI polkit agent, started from hyprland.conf. dolphin: - # file manager bound in hyprland.conf. playerctl and wireplumber (wpctl): - # back the media-key binds in hyprland.conf. + # file manager bound in hyprland.conf, with ffmpegthumbs for its video + # thumbnails. playerctl and wireplumber (wpctl): back the media-key binds + # in hyprland.conf. ffmpeg: the system H.264/AAC decoder Firefox loads at + # run time (it can't bundle those codecs itself; see the firefox profile + # below). libva-utils: run `vainfo` to confirm GPU video decode is active. packages = [ pkgs.hyprpolkitagent pkgs.kdePackages.dolphin + pkgs.kdePackages.ffmpegthumbs + # Kate and KWrite ship as one package upstream; kwrite below is the + # lightweight one, kate the fuller editor, same binary set. + pkgs.kdePackages.kate pkgs.playerctl pkgs.wireplumber + pkgs.ffmpeg + pkgs.libva-utils + pkgs.vscode ]; + # nano is the plain terminal editor git commit/crontab -e/etc. expect + # from $EDITOR; it blocks the caller by default, no extra flag needed. + # NIXOS_OZONE_WL: VS Code (kept for manual use, not wired as a default) + # is Electron; without this it falls back to XWayland instead of native + # Wayland. + sessionVariables = { + EDITOR = "nano"; + VISUAL = "nano"; + NIXOS_OZONE_WL = "1"; + }; + # Hyprland session and Quickshell shells (bar, lock screen, launcher). # The greeter has its own copy of Quickshell's config, published via # environment.etc in configuration.nix instead of home-manager, since @@ -76,7 +97,13 @@ # the profile below once there's a real stylesheet to put there. firefox = { enable = true; - profiles.igor.isDefault = true; + profiles.igor = { + isDefault = true; + # Mesa's radeonsi VA-API driver comes from hardware.graphics.enable + # in configuration.nix; this just tells Firefox to actually use it + # instead of decoding video on the CPU. + settings."media.ffmpeg.vaapi.enabled" = true; + }; }; # home-manager-managed (not just enabled in configuration.nix) so that @@ -108,7 +135,18 @@ colorScheme = "dark"; }; - xdg.autostart.enable = true; + xdg = { + autostart.enable = true; + + # Equivalent to `xdg-mime default kwrite.desktop text/plain`, but + # declarative in mimeapps.list, so it survives rebuilds. Named explicitly + # (not via defaultApplicationPackages) since kate.desktop and + # kwrite.desktop ship in the same package and both claim text/plain. + mimeApps = { + enable = true; + defaultApplications."text/plain" = [ "kwrite.desktop" ]; + }; + }; # Auto-lock on idle: lock at 5 minutes, blank the display 30 seconds # after that, lock again before suspend regardless of idle time. From 950045b7d73c2386168075f2b4fd454fe6ca9024 Mon Sep 17 00:00:00 2001 From: Igor Camilo <11697241+igorcamilo@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:56:21 +0200 Subject: [PATCH 6/6] Update Hyprland config and enhance boot settings - Migrate Hyprland configuration from .conf to .lua format for improved functionality and clarity. - Update greeter session handling to use the new Lua script. - Adjust kernel package settings for AMD GPU support to ensure compatibility with the latest drivers. - Modify splash screen settings to load the amdgpu driver during initrd for better resolution rendering. - Increase size of ESP partition to accommodate signed UKI requirements. No breaking changes introduced. --- configuration.nix | 34 ++++++++++--- dotfiles/hypr/greeter.conf | 16 ------ dotfiles/hypr/greeter.lua | 28 +++++++++++ dotfiles/hypr/hyprland.conf | 80 ----------------------------- dotfiles/hypr/hyprland.lua | 92 ++++++++++++++++++++++++++++++++++ home.nix | 8 +-- modules/boot/splash.nix | 10 ++-- modules/storage/luks-btrfs.nix | 5 +- 8 files changed, 159 insertions(+), 114 deletions(-) delete mode 100644 dotfiles/hypr/greeter.conf create mode 100644 dotfiles/hypr/greeter.lua delete mode 100644 dotfiles/hypr/hyprland.conf create mode 100644 dotfiles/hypr/hyprland.lua diff --git a/configuration.nix b/configuration.nix index 7ed2823..05af8bd 100644 --- a/configuration.nix +++ b/configuration.nix @@ -20,6 +20,10 @@ nixpkgs.config.allowUnfree = true; + # RDNA4 (RX 9070 XT) needs a newer kernel and Mesa than the nixpkgs default + # kernel targets; track the latest stable release instead of the default. + boot.kernelPackages = pkgs.linuxPackages_latest; + users.users.igor = { isNormalUser = true; description = "Igor Camilo"; @@ -67,7 +71,7 @@ fwupd.enable = true; # greetd runs a throwaway Hyprland+Quickshell "greeter" session (see - # dotfiles/hypr/greeter.conf and dotfiles/quickshell/greeter) that + # dotfiles/hypr/greeter.lua and dotfiles/quickshell/greeter) that # authenticates over greetd's own IPC protocol, then hands off to the # user's normal session below. The greeter runs as its own system # user (created automatically by this module), so its files are @@ -78,10 +82,14 @@ settings.default_session = { # Preserve compositor and greeter output after their runtime directory # disappears. Read it with: journalctl -b -t greetd-greeter - command = "${pkgs.systemd}/bin/systemd-cat --identifier=greetd-greeter -- start-hyprland -- --config /etc/greetd/hyprland.conf"; + command = "${pkgs.systemd}/bin/systemd-cat --identifier=greetd-greeter -- start-hyprland -- --config /etc/greetd/hyprland.lua"; user = "greeter"; }; }; + + udisks2.enable = true; + + btrfs.autoScrub.enable = true; }; # Desktop: Hyprland, with Quickshell as the shell layer (bar, wallpaper, @@ -105,14 +113,24 @@ style = "kvantum"; }; - # xdg-desktop-portal-hyprland (added automatically by programs.hyprland - # above) only covers Screenshot/ScreenCast. Dolphin's own portal fills in - # FileChooser and other KDE-flavored requests. - xdg.portal.extraPortals = [ pkgs.kdePackages.xdg-desktop-portal-kde ]; + # programs.hyprland adds xdg-desktop-portal-hyprland and ships its own + # hyprland-portals.conf (default = hyprland, then gtk). That file lives in + # the package's own store path, so the override below at a higher-priority + # /etc path is needed to route FileChooser to Dolphin's KDE portal instead. + xdg.portal = { + extraPortals = [ pkgs.kdePackages.xdg-desktop-portal-kde ]; + config.hyprland = { + default = [ + "hyprland" + "gtk" + ]; + "org.freedesktop.impl.portal.FileChooser" = [ "kde" ]; + }; + }; # GUI privilege prompts (mounting a drive, some NetworkManager actions) # need an authentication agent; home.nix installs Hyprland's own - # (hyprpolkitagent) and hyprland.conf starts it. + # (hyprpolkitagent) and hyprland.lua starts it. security.polkit.enable = true; environment = { @@ -125,7 +143,7 @@ ]; etc = { "wallpaper.jpg".source = ./wallpapers/weic2216b.jpg; - "greetd/hyprland.conf".source = ./dotfiles/hypr/greeter.conf; + "greetd/hyprland.lua".source = ./dotfiles/hypr/greeter.lua; # Keep the greeter and its ../shared QML import in one Nix store tree. "greetd/quickshell".source = ./dotfiles/quickshell; }; diff --git a/dotfiles/hypr/greeter.conf b/dotfiles/hypr/greeter.conf deleted file mode 100644 index 3dc6901..0000000 --- a/dotfiles/hypr/greeter.conf +++ /dev/null @@ -1,16 +0,0 @@ -# Minimal compositor used only to host the login greeter. Once the -# greeter authenticates and hands off to the real session (see -# quickshell/greeter/shell.qml), this Hyprland instance has to exit so -# greetd can start the user's session on the same VT/seat. -# -# Exit only after a successful Quickshell session. If QML cannot start, -# leave this compositor alive instead of making greetd restart it in a loop. -exec-once = quickshell -p /etc/greetd/quickshell/greeter && hyprctl dispatch exit - -# The greeter session is short-lived, so its /run/user log disappears. -# Mirror its complete Hyprland log to this boot's system journal. -debug { - disable_logs = false - enable_stdout_logs = true - # colored_stdout_logs = false -} diff --git a/dotfiles/hypr/greeter.lua b/dotfiles/hypr/greeter.lua new file mode 100644 index 0000000..8452ceb --- /dev/null +++ b/dotfiles/hypr/greeter.lua @@ -0,0 +1,28 @@ +-- Minimal compositor used only to host the login greeter. Once the +-- greeter authenticates and hands off to the real session (see +-- quickshell/greeter/shell.qml), this Hyprland instance has to exit so +-- greetd can start the user's session on the same VT/seat. + +-- ASUS TUF VG34VQL1B: 3440x1440, matching the user session (see hyprland.lua). +hl.monitor({ + output = "", + mode = "3440x1440@165", + position = "auto", + scale = "auto", +}) + +-- Exit only after a successful Quickshell session. If QML cannot start, +-- leave this compositor alive instead of making greetd restart it in a loop. +hl.on("hyprland.start", function() + hl.exec_cmd("quickshell -p /etc/greetd/quickshell/greeter && hyprctl dispatch exit") +end) + +-- The greeter session is short-lived, so its /run/user log disappears. +-- Mirror its complete Hyprland log to this boot's system journal. +hl.config({ + debug = { + disable_logs = false, + enable_stdout_logs = true, + -- colored_stdout_logs = false, + }, +}) diff --git a/dotfiles/hypr/hyprland.conf b/dotfiles/hypr/hyprland.conf deleted file mode 100644 index 44e6421..0000000 --- a/dotfiles/hypr/hyprland.conf +++ /dev/null @@ -1,80 +0,0 @@ -$mainMod = SUPER -$terminal = ghostty -$fileManager = dolphin - -# Keyboard layout. Change kb_layout if this isn't a US physical layout. -input { - kb_layout = us -} - -exec-once = quickshell -exec-once = hyprpolkitagent - -bind = $mainMod, Return, exec, $terminal -bind = $mainMod, Q, exec, $terminal -bind = $mainMod, C, killactive, -bind = $mainMod, M, exit, -bind = $mainMod, E, exec, $fileManager -bind = $mainMod, V, togglefloating, -bind = $mainMod, R, exec, systemctl --user start quickshell-launcher.service -bind = $mainMod, P, pseudo, -bind = $mainMod, J, togglesplit, -bind = $mainMod, L, exec, systemctl --user start quickshell-lock.service - -# Move focus with mainMod + arrow keys -bind = $mainMod, left, movefocus, l -bind = $mainMod, right, movefocus, r -bind = $mainMod, up, movefocus, u -bind = $mainMod, down, movefocus, d - -# Switch workspace with mainMod + [0-9] -bind = $mainMod, 1, workspace, 1 -bind = $mainMod, 2, workspace, 2 -bind = $mainMod, 3, workspace, 3 -bind = $mainMod, 4, workspace, 4 -bind = $mainMod, 5, workspace, 5 -bind = $mainMod, 6, workspace, 6 -bind = $mainMod, 7, workspace, 7 -bind = $mainMod, 8, workspace, 8 -bind = $mainMod, 9, workspace, 9 -bind = $mainMod, 0, workspace, 10 - -# Move the focused window to a workspace with mainMod + SHIFT + [0-9] -bind = $mainMod SHIFT, 1, movetoworkspace, 1 -bind = $mainMod SHIFT, 2, movetoworkspace, 2 -bind = $mainMod SHIFT, 3, movetoworkspace, 3 -bind = $mainMod SHIFT, 4, movetoworkspace, 4 -bind = $mainMod SHIFT, 5, movetoworkspace, 5 -bind = $mainMod SHIFT, 6, movetoworkspace, 6 -bind = $mainMod SHIFT, 7, movetoworkspace, 7 -bind = $mainMod SHIFT, 8, movetoworkspace, 8 -bind = $mainMod SHIFT, 9, movetoworkspace, 9 -bind = $mainMod SHIFT, 0, movetoworkspace, 10 - -# Scratchpad-style special workspace -bind = $mainMod, S, togglespecialworkspace, magic -bind = $mainMod SHIFT, S, movetoworkspace, special:magic - -# Scroll through workspaces with mainMod + scroll wheel -bind = $mainMod, mouse_down, workspace, e+1 -bind = $mainMod, mouse_up, workspace, e-1 - -# Move/resize windows with mainMod + drag -bindm = $mainMod, mouse:272, movewindow -bindm = $mainMod, mouse:273, resizewindow - -# Volume and mic mute -bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ -bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- -bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle -bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle - -# Screen brightness; laptop panels only, likely a no-op on desktop monitors -bindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+ -bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%- - -# Media playback -bindl = ,XF86AudioNext, exec, playerctl next -bindl = ,XF86AudioPause, exec, playerctl play-pause -bindl = ,XF86AudioPlay, exec, playerctl play-pause -bindl = ,XF86AudioPrev, exec, playerctl previous diff --git a/dotfiles/hypr/hyprland.lua b/dotfiles/hypr/hyprland.lua new file mode 100644 index 0000000..a60f3f2 --- /dev/null +++ b/dotfiles/hypr/hyprland.lua @@ -0,0 +1,92 @@ +local mainMod = "SUPER" +local terminal = "ghostty" +local fileManager = "dolphin" + +------------ +-- MONITOR -- +------------ + +-- ASUS TUF VG34VQL1B: 3440x1440, FreeSync Premium up to 165Hz over DisplayPort. +hl.monitor({ + output = "", + mode = "3440x1440@165", + position = "auto", + scale = "auto", + vrr = 1, +}) + +----------- +-- INPUT -- +----------- + +hl.config({ + input = { + kb_layout = "us", -- Change this if the keyboard isn't a US layout. + }, +}) + +--------------- +-- AUTOSTART -- +--------------- + +hl.on("hyprland.start", function() + hl.exec_cmd("quickshell") + hl.exec_cmd("hyprpolkitagent") +end) + +---------------- +-- KEYBINDING -- +---------------- + +hl.bind(mainMod .. " + Return", hl.dsp.exec_cmd(terminal)) +hl.bind(mainMod .. " + Q", hl.dsp.exec_cmd(terminal)) +hl.bind(mainMod .. " + C", hl.dsp.window.close()) +hl.bind(mainMod .. " + M", hl.dsp.exit()) +hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(fileManager)) +hl.bind(mainMod .. " + V", hl.dsp.window.float({ action = "toggle" })) +hl.bind(mainMod .. " + R", hl.dsp.exec_cmd("systemctl --user start quickshell-launcher.service")) +hl.bind(mainMod .. " + P", hl.dsp.window.pseudo()) +hl.bind(mainMod .. " + J", hl.dsp.layout("togglesplit")) -- dwindle only +hl.bind(mainMod .. " + L", hl.dsp.exec_cmd("systemctl --user start quickshell-lock.service")) + +-- Move focus with mainMod + arrow keys +hl.bind(mainMod .. " + left", hl.dsp.focus({ direction = "left" })) +hl.bind(mainMod .. " + right", hl.dsp.focus({ direction = "right" })) +hl.bind(mainMod .. " + up", hl.dsp.focus({ direction = "up" })) +hl.bind(mainMod .. " + down", hl.dsp.focus({ direction = "down" })) + +-- Switch workspaces with mainMod + [0-9] +-- Move the focused window to a workspace with mainMod + SHIFT + [0-9] +for i = 1, 10 do + local key = i % 10 -- 10 maps to key 0 + hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i })) + hl.bind(mainMod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i })) +end + +-- Scratchpad-style special workspace +hl.bind(mainMod .. " + S", hl.dsp.workspace.toggle_special("magic")) +hl.bind(mainMod .. " + SHIFT + S", hl.dsp.window.move({ workspace = "special:magic" })) + +-- Scroll through workspaces with mainMod + scroll wheel +hl.bind(mainMod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" })) +hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" })) + +-- Move/resize windows with mainMod + drag +hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true }) +hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) + +-- Volume and mic mute +hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true }) +hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true }) +hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true, repeating = true }) +hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true, repeating = true }) + +-- Screen brightness; laptop panels only, likely a no-op on desktop monitors +hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%+"), { locked = true, repeating = true }) +hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%-"), { locked = true, repeating = true }) + +-- Media playback +hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true }) +hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) +hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) +hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true }) diff --git a/home.nix b/home.nix index 3c959e3..1f74440 100644 --- a/home.nix +++ b/home.nix @@ -24,10 +24,10 @@ homeDirectory = "/home/igor"; stateVersion = "26.05"; - # hyprpolkitagent: GUI polkit agent, started from hyprland.conf. dolphin: - # file manager bound in hyprland.conf, with ffmpegthumbs for its video + # hyprpolkitagent: GUI polkit agent, started from hyprland.lua. dolphin: + # file manager bound in hyprland.lua, with ffmpegthumbs for its video # thumbnails. playerctl and wireplumber (wpctl): back the media-key binds - # in hyprland.conf. ffmpeg: the system H.264/AAC decoder Firefox loads at + # in hyprland.lua. ffmpeg: the system H.264/AAC decoder Firefox loads at # run time (it can't bundle those codecs itself; see the firefox profile # below). libva-utils: run `vainfo` to confirm GPU video decode is active. packages = [ @@ -60,7 +60,7 @@ # environment.etc in configuration.nix instead of home-manager, since # it runs as a separate system user with no home directory here. file = { - ".config/hypr/hyprland.conf".source = ./dotfiles/hypr/hyprland.conf; + ".config/hypr/hyprland.lua".source = ./dotfiles/hypr/hyprland.lua; ".config/quickshell/shell.qml".source = ./dotfiles/quickshell/bar/shell.qml; ".config/quickshell/lockscreen".source = ./dotfiles/quickshell/lockscreen; ".config/quickshell/launcher".source = ./dotfiles/quickshell/launcher; diff --git a/modules/boot/splash.nix b/modules/boot/splash.nix index 000a864..00396c0 100644 --- a/modules/boot/splash.nix +++ b/modules/boot/splash.nix @@ -7,11 +7,6 @@ _: theme = "bgrt"; }; - # Load the GPU driver during the initrd, before Plymouth starts, so the - # splash renders at its final resolution instead of flickering when the - # driver takes over later in boot. - initrd.kernelModules = [ "amdgpu" ]; - # Keep routine boot messages behind the splash. Press Escape during boot # to switch between Plymouth and the text details. consoleLogLevel = 3; @@ -22,4 +17,9 @@ _: "rd.systemd.show_status=auto" ]; }; + + # Loads the amdgpu driver during the initrd, before Plymouth starts, so the + # splash renders at its final resolution instead of flickering when the + # driver takes over later in boot. + hardware.amdgpu.initrd.enable = true; } diff --git a/modules/storage/luks-btrfs.nix b/modules/storage/luks-btrfs.nix index c3346e8..d57381d 100644 --- a/modules/storage/luks-btrfs.nix +++ b/modules/storage/luks-btrfs.nix @@ -19,7 +19,10 @@ in ESP = { priority = 1; name = "ESP"; - size = "1G"; + # Lanzaboote keeps a full signed UKI (kernel + initrd, amdgpu firmware + # included) per generation, times configurationLimit generations kept + # in modules/boot/secure-boot.nix; 1G leaves too little headroom. + size = "2G"; type = "EF00"; content = { type = "filesystem";