Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
109 changes: 90 additions & 19 deletions configuration.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -34,6 +38,60 @@

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;
};
};

security.rtkit.enable = true; # Realtime scheduling for PipeWire.

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;
};

# 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.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
# 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.lua";
user = "greeter";
};
};

udisks2.enable = true;

btrfs.autoScrub.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.
Expand All @@ -43,15 +101,38 @@
# 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;
};
# 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";
};

# 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" ];
};
};
qt.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.

# GUI privilege prompts (mounting a drive, some NetworkManager actions)
# need an authentication agent; home.nix installs Hyprland's own
# (hyprpolkitagent) and hyprland.lua starts it.
security.polkit.enable = true;

environment = {
systemPackages = [
pkgs.git
Expand All @@ -62,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;
};
Expand All @@ -73,16 +154,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"
Expand Down
16 changes: 0 additions & 16 deletions dotfiles/hypr/greeter.conf

This file was deleted.

28 changes: 28 additions & 0 deletions dotfiles/hypr/greeter.lua
Original file line number Diff line number Diff line change
@@ -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,
},
})
6 changes: 0 additions & 6 deletions dotfiles/hypr/hyprland.conf

This file was deleted.

92 changes: 92 additions & 0 deletions dotfiles/hypr/hyprland.lua
Original file line number Diff line number Diff line change
@@ -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 })
Loading