Compare commits
28 Commits
16d7d81dc1
...
fix/waybar
| Author | SHA1 | Date | |
|---|---|---|---|
| c053846adb | |||
| 2edad6c5df | |||
| 1d5acc09c6 | |||
| ee29e53f7c | |||
| 3c9d0ec886 | |||
| f0c5544b10 | |||
| 4dd0a5a4f5 | |||
| a4a795f7ee | |||
| ecdf877308 | |||
| d1face10ec | |||
| 337d9a2014 | |||
| f059068873 | |||
| 7662c04cda | |||
| 33128fc3c7 | |||
| 68415ad47e | |||
| 6b7201e6fa | |||
| 4360f4863b | |||
| 370cab8f83 | |||
| e74a08b853 | |||
| 250b0b71f9 | |||
| 10299cec19 | |||
| 91654b3068 | |||
| be9c3c07a3 | |||
| a8bd4bf4cf | |||
| a2fc568704 | |||
| 0bf5ebae87 | |||
| fddec14128 | |||
| 85fadc5bfc |
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
## Desktop
|
## Desktop
|
||||||
|
|
||||||
- TODO: Verify this path after the NixOS configuration is migrated.
|
- When adding a graphical application, register its workspace icon in `registry.window-icon`.
|
||||||
When adding a graphical application, add a `window-rewrite` icon mapping in `modules/desktop/session/waybar.nix`.
|
|
||||||
The workspace indicator uses this mapping to show an application icon instead of the generic default glyph.
|
The workspace indicator uses this mapping to show an application icon instead of the generic default glyph.
|
||||||
Match the mapping on the window class.
|
Match the mapping on the window class.
|
||||||
|
Store the icon as the rendered string.
|
||||||
Run `hyprctl clients -j | jq -r '.[].class' | sort -u` to list classes in the running session.
|
Run `hyprctl clients -j | jq -r '.[].class' | sort -u` to list classes in the running session.
|
||||||
- TODO: Verify this path after the NixOS configuration is migrated.
|
- TODO: Verify this path after the NixOS configuration is migrated.
|
||||||
Set Hyprland session environment variables in `modules/desktop/session/hyprland/default.nix` with `env = KEY,VALUE`.
|
Set Hyprland session environment variables in `modules/desktop/session/hyprland/default.nix` with `env = KEY,VALUE`.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
inputs,
|
inputs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# neogaia — Dell XPS 13 9380 laptop.
|
# neogaia — Dell XPS 13 9380 laptop.
|
||||||
@@ -14,8 +15,7 @@
|
|||||||
|
|
||||||
modules.host.cachyos.enable = true;
|
modules.host.cachyos.enable = true;
|
||||||
|
|
||||||
# So wifi can be joined from the console.
|
modules.network.enable = true;
|
||||||
networking.networkmanager.enable = true;
|
|
||||||
|
|
||||||
registry.address.neogaia.self = "10.23.50.146";
|
registry.address.neogaia.self = "10.23.50.146";
|
||||||
|
|
||||||
@@ -40,4 +40,23 @@
|
|||||||
modules.desktop.apps.obsidian.enable = true;
|
modules.desktop.apps.obsidian.enable = true;
|
||||||
modules.desktop.apps.steam.enable = true;
|
modules.desktop.apps.steam.enable = true;
|
||||||
|
|
||||||
|
registry.waybar.neogaia.battery = {
|
||||||
|
order = 70;
|
||||||
|
settings = {
|
||||||
|
format = "{icon} {capacity}%";
|
||||||
|
format-charging = "${util.glyph "f0e7"} {capacity}%";
|
||||||
|
format-icons = [
|
||||||
|
(util.glyph "f244")
|
||||||
|
(util.glyph "f243")
|
||||||
|
(util.glyph "f242")
|
||||||
|
(util.glyph "f241")
|
||||||
|
(util.glyph "f240")
|
||||||
|
];
|
||||||
|
states = {
|
||||||
|
warning = 20;
|
||||||
|
critical = 10;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
enable32Bit = true;
|
enable32Bit = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
networking.networkmanager.enable = true;
|
modules.network.enable = true;
|
||||||
|
|
||||||
services.logind.settings.Login = {
|
services.logind.settings.Login = {
|
||||||
HandlePowerKey = "ignore";
|
HandlePowerKey = "ignore";
|
||||||
@@ -49,8 +49,19 @@
|
|||||||
modules.desktop.apps.vencord.enable = true;
|
modules.desktop.apps.vencord.enable = true;
|
||||||
|
|
||||||
home-manager.users.${config.user.name}.wayland.windowManager.hyprland.settings.monitor = [
|
home-manager.users.${config.user.name}.wayland.windowManager.hyprland.settings.monitor = [
|
||||||
"HDMI-A-1,1920x1080@120,0x0,1,transform,3"
|
{
|
||||||
"DP-3,2560x1440@239.96,1080x0,1"
|
output = "HDMI-A-1";
|
||||||
|
mode = "1920x1080@120";
|
||||||
|
position = "-1080x0";
|
||||||
|
scale = 1;
|
||||||
|
transform = 3;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
output = "DP-3";
|
||||||
|
mode = "2560x1440@239.96";
|
||||||
|
position = "0x0";
|
||||||
|
scale = 1;
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
inputs,
|
inputs,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# Configures Firefox as the desktop browser.
|
# Configures Firefox as the desktop browser.
|
||||||
@@ -26,6 +27,11 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
registry.window-icon.${config.networking.hostName}.firefox = {
|
||||||
|
class = ".*[Ff]irefox.*";
|
||||||
|
icon = util.glyph "f269";
|
||||||
|
};
|
||||||
|
|
||||||
home-manager.users.${config.user.name} = hm: {
|
home-manager.users.${config.user.name} = hm: {
|
||||||
programs.firefox = {
|
programs.firefox = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# Ghostty as the desktop terminal.
|
# Ghostty as the desktop terminal.
|
||||||
@@ -12,6 +13,19 @@ in
|
|||||||
options.modules.desktop.apps.ghostty.enable = lib.mkEnableOption "Ghostty as the desktop terminal";
|
options.modules.desktop.apps.ghostty.enable = lib.mkEnableOption "Ghostty as the desktop terminal";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.apps.ghostty.enable {
|
config = lib.mkIf config.modules.desktop.apps.ghostty.enable {
|
||||||
|
registry.keybind.${config.networking.hostName}.ghostty = {
|
||||||
|
keys = "SUPER + Return";
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = lib.getExe pkgs.ghostty;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.window-icon.${config.networking.hostName}.ghostty = {
|
||||||
|
class = "com.mitchellh.ghostty";
|
||||||
|
icon = util.glyph "f120";
|
||||||
|
};
|
||||||
|
|
||||||
home-manager.users.${user}.home.packages = [ pkgs.ghostty ];
|
home-manager.users.${user}.home.packages = [ pkgs.ghostty ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# Obsidian as the desktop note-taking vault.
|
# Obsidian as the desktop note-taking vault.
|
||||||
@@ -14,5 +15,10 @@ in
|
|||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.apps.obsidian.enable {
|
config = lib.mkIf config.modules.desktop.apps.obsidian.enable {
|
||||||
home-manager.users.${user}.home.packages = [ pkgs.obsidian ];
|
home-manager.users.${user}.home.packages = [ pkgs.obsidian ];
|
||||||
|
|
||||||
|
registry.window-icon.${config.networking.hostName}.obsidian = {
|
||||||
|
class = "obsidian";
|
||||||
|
icon = util.glyph "f02d";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# Signal desktop messenger.
|
# Signal desktop messenger.
|
||||||
@@ -13,5 +14,10 @@ in
|
|||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.apps.signal.enable {
|
config = lib.mkIf config.modules.desktop.apps.signal.enable {
|
||||||
home-manager.users.${user}.home.packages = [ pkgs.signal-desktop ];
|
home-manager.users.${user}.home.packages = [ pkgs.signal-desktop ];
|
||||||
|
|
||||||
|
registry.window-icon.${config.networking.hostName}.signal = {
|
||||||
|
class = "[Ss]ignal";
|
||||||
|
icon = util.glyph "f4ad";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# Steam game launcher and runtime integration.
|
# Steam game launcher and runtime integration.
|
||||||
@@ -23,5 +24,10 @@
|
|||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
pkgs.gamescope
|
pkgs.gamescope
|
||||||
];
|
];
|
||||||
|
|
||||||
|
registry.window-icon.${config.networking.hostName}.steam = {
|
||||||
|
class = "[Ss]team";
|
||||||
|
icon = util.glyph "f1b6";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# Vesktop, a Discord client with Vencord built in.
|
# Vesktop, a Discord client with Vencord built in.
|
||||||
@@ -14,5 +15,10 @@ in
|
|||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.apps.vencord.enable {
|
config = lib.mkIf config.modules.desktop.apps.vencord.enable {
|
||||||
home-manager.users.${user}.home.packages = [ pkgs.vesktop ];
|
home-manager.users.${user}.home.packages = [ pkgs.vesktop ];
|
||||||
|
|
||||||
|
registry.window-icon.${config.networking.hostName}.vesktop = {
|
||||||
|
class = "[Vv]esktop";
|
||||||
|
icon = util.glyph "f1ff";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,5 +23,6 @@
|
|||||||
modules.desktop.utilities.screenshot.enable = lib.mkDefault true;
|
modules.desktop.utilities.screenshot.enable = lib.mkDefault true;
|
||||||
modules.desktop.utilities.theming.enable = lib.mkDefault true;
|
modules.desktop.utilities.theming.enable = lib.mkDefault true;
|
||||||
modules.desktop.utilities.userdirs.enable = lib.mkDefault true;
|
modules.desktop.utilities.userdirs.enable = lib.mkDefault true;
|
||||||
|
registry.env.${config.networking.hostName}.NIXOS_OZONE_WL = "1";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,15 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# The Hyprland compositor.
|
# The Hyprland compositor.
|
||||||
let
|
|
||||||
cfg = config.modules.desktop.session.hyprland; # NOLINT(let_order)
|
|
||||||
cursor = config.stylix.cursor;
|
|
||||||
|
|
||||||
# Numbered-workspace switch and move for 1..9, the operator's i3 muscle memory.
|
|
||||||
workspaceBinds = lib.concatMap (n: [
|
|
||||||
"$mod, ${toString n}, workspace, ${toString n}"
|
|
||||||
"$mod SHIFT, ${toString n}, movetoworkspace, ${toString n}"
|
|
||||||
]) (lib.range 1 9);
|
|
||||||
|
|
||||||
user = config.user.name;
|
|
||||||
|
|
||||||
# Flip the tiling strategy between the two built-in layouts, since neither a
|
|
||||||
# dispatcher nor a keyword toggles it on its own.
|
|
||||||
toggleLayout = pkgs.writeShellScript "hypr-toggle-layout" ''
|
|
||||||
if [ "$(hyprctl getoption -j general:layout | ${pkgs.jq}/bin/jq -r .str)" = dwindle ]; then
|
|
||||||
hyprctl keyword general:layout master
|
|
||||||
else
|
|
||||||
hyprctl keyword general:layout dwindle
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
options.modules.desktop.session.hyprland = {
|
options.modules.desktop.session.hyprland.enable = lib.mkEnableOption "the Hyprland compositor";
|
||||||
enable = lib.mkEnableOption "the Hyprland compositor";
|
|
||||||
|
|
||||||
blur = lib.mkEnableOption ''
|
config = lib.mkIf config.modules.desktop.session.hyprland.enable {
|
||||||
window blur. Off by default as the single biggest battery cost on a
|
|
||||||
laptop, left on for a host with the headroom to spend it'';
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
# This program integration owns the session, portals, and polkit, launched
|
# This program integration owns the session, portals, and polkit, launched
|
||||||
# through the universal Wayland session manager.
|
# through the universal Wayland session manager.
|
||||||
programs.hyprland = {
|
programs.hyprland = {
|
||||||
@@ -44,7 +17,7 @@ in
|
|||||||
withUWSM = true;
|
withUWSM = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
home-manager.users.${user}.wayland.windowManager.hyprland = {
|
home-manager.users.${config.user.name}.wayland.windowManager.hyprland = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
# One package drives the whole session, so there is never a version split.
|
# One package drives the whole session, so there is never a version split.
|
||||||
@@ -56,135 +29,216 @@ in
|
|||||||
# uwsm owns the systemd graphical-session targets.
|
# uwsm owns the systemd graphical-session targets.
|
||||||
systemd.enable = false;
|
systemd.enable = false;
|
||||||
|
|
||||||
# Write the native hyprlang hyprland.conf, whose variable and bind syntax
|
# Write the native Lua Hyprland config. Home Manager renders the Nix
|
||||||
# the settings below are expressed in.
|
# settings below as hl.* calls.
|
||||||
configType = "hyprlang";
|
configType = "lua";
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
"$mod" = "SUPER";
|
config = {
|
||||||
"$terminal" = "ghostty";
|
input = {
|
||||||
|
kb_layout = config.services.xserver.xkb.layout;
|
||||||
# Session variables handed to the compositor directly.
|
repeat_delay = 250;
|
||||||
# UWSM launches the session without the shell profile that would carry
|
repeat_rate = 45;
|
||||||
# them, so a variable the compositor or its children must see is set
|
accel_profile = "flat";
|
||||||
# here rather than through home-manager's sessionVariables.
|
touchpad = {
|
||||||
env = [
|
natural_scroll = true;
|
||||||
# Chromium and Electron apps read this to select native Wayland;
|
tap_to_click = true;
|
||||||
# nixpkgs wrappers (Obsidian's included) gate their Wayland flags on
|
disable_while_typing = true;
|
||||||
# it, so without it they run under XWayland and blur at this DPI.
|
};
|
||||||
"NIXOS_OZONE_WL,1"
|
|
||||||
]
|
|
||||||
# Bibata ships XCursor only.
|
|
||||||
# The hyprcursor variables name the same theme, which Hyprland
|
|
||||||
# resolves through its XCursor fallback.
|
|
||||||
++ lib.optionals (cursor != null) [
|
|
||||||
"XCURSOR_THEME,${cursor.name}"
|
|
||||||
"XCURSOR_SIZE,${toString cursor.size}"
|
|
||||||
"HYPRCURSOR_THEME,${cursor.name}"
|
|
||||||
"HYPRCURSOR_SIZE,${toString cursor.size}"
|
|
||||||
];
|
|
||||||
|
|
||||||
input = {
|
|
||||||
kb_layout = "us";
|
|
||||||
# Caps is a second Escape.
|
|
||||||
# Shift+Caps still toggles a real CapsLock.
|
|
||||||
kb_options = "caps:escape_shifted_capslock";
|
|
||||||
# Snappy key repeat.
|
|
||||||
repeat_delay = 250;
|
|
||||||
repeat_rate = 45;
|
|
||||||
accel_profile = "flat";
|
|
||||||
touchpad = {
|
|
||||||
natural_scroll = true;
|
|
||||||
tap-to-click = true;
|
|
||||||
disable_while_typing = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
general = {
|
||||||
|
gaps_in = 4;
|
||||||
|
gaps_out = 8;
|
||||||
|
border_size = 2;
|
||||||
|
layout = "dwindle";
|
||||||
|
};
|
||||||
|
|
||||||
|
decoration = {
|
||||||
|
rounding = 6;
|
||||||
|
blur.enabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
xwayland.force_zero_scaling = true;
|
||||||
|
|
||||||
|
dwindle.preserve_split = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
general = {
|
env = lib.mapAttrsToList (name: value: {
|
||||||
gaps_in = 4;
|
_args = [
|
||||||
gaps_out = 8;
|
name
|
||||||
border_size = 2;
|
value
|
||||||
layout = "dwindle";
|
|
||||||
};
|
|
||||||
|
|
||||||
decoration = {
|
|
||||||
rounding = 6;
|
|
||||||
blur.enabled = cfg.blur;
|
|
||||||
};
|
|
||||||
|
|
||||||
# XWayland clients render at the panel's native resolution instead of
|
|
||||||
# being raster-scaled by the compositor at the fractional monitor scale.
|
|
||||||
xwayland.force_zero_scaling = true;
|
|
||||||
|
|
||||||
animations = {
|
|
||||||
enabled = true;
|
|
||||||
bezier = [ "ease, 0.25, 0.1, 0.25, 1.0" ];
|
|
||||||
# Durations are in centiseconds.
|
|
||||||
# Short values keep the motion subtle.
|
|
||||||
animation = [
|
|
||||||
"windows, 1, 3, ease"
|
|
||||||
"fade, 1, 3, ease"
|
|
||||||
# Layer surfaces like the launcher fade in a touch quicker than windows.
|
|
||||||
"layersIn, 1, 2, ease"
|
|
||||||
"fadeLayersIn, 1, 2, ease"
|
|
||||||
"workspaces, 1, 3, ease"
|
|
||||||
"border, 1, 3, ease"
|
|
||||||
];
|
];
|
||||||
|
}) (lib.attrByPath [ config.networking.hostName ] { } config.registry.env);
|
||||||
|
|
||||||
|
bind = lib.mapAttrsToList (_name: keybind: {
|
||||||
|
_args = [
|
||||||
|
keybind.keys
|
||||||
|
(lib.generators.mkLuaInline (
|
||||||
|
{
|
||||||
|
exec = "hl.dsp.exec_cmd(${lib.generators.toLua { } keybind.action.command})";
|
||||||
|
focus = "hl.dsp.focus(${
|
||||||
|
lib.generators.toLua { } { inherit (keybind.action) direction workspace; }
|
||||||
|
})";
|
||||||
|
"move-window" = "hl.dsp.window.move(${
|
||||||
|
lib.generators.toLua { } { inherit (keybind.action) direction workspace; }
|
||||||
|
})";
|
||||||
|
"resize-window" = "hl.dsp.window.resize(${
|
||||||
|
lib.generators.toLua { } (
|
||||||
|
{
|
||||||
|
down = {
|
||||||
|
x = 0;
|
||||||
|
y = 10;
|
||||||
|
relative = true;
|
||||||
|
};
|
||||||
|
left = {
|
||||||
|
x = -10;
|
||||||
|
y = 0;
|
||||||
|
relative = true;
|
||||||
|
};
|
||||||
|
right = {
|
||||||
|
x = 10;
|
||||||
|
y = 0;
|
||||||
|
relative = true;
|
||||||
|
};
|
||||||
|
up = {
|
||||||
|
x = 0;
|
||||||
|
y = -10;
|
||||||
|
relative = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
.${keybind.action.direction}
|
||||||
|
)
|
||||||
|
})";
|
||||||
|
"toggle-floating" = ''hl.dsp.window.float({ action = "toggle" })'';
|
||||||
|
"toggle-fullscreen" = "hl.dsp.window.fullscreen()";
|
||||||
|
"close-window" = "hl.dsp.window.close()";
|
||||||
|
}
|
||||||
|
.${keybind.action.kind}
|
||||||
|
))
|
||||||
|
]
|
||||||
|
++ lib.optional keybind.repeating {
|
||||||
|
repeating = true;
|
||||||
|
}
|
||||||
|
++ lib.optional keybind.locked {
|
||||||
|
locked = true;
|
||||||
|
};
|
||||||
|
}) (lib.attrByPath [ config.networking.hostName ] { } config.registry.keybind);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
registry.keybind.${config.networking.hostName} = lib.mkMerge [
|
||||||
|
# Generate numeric workspace keybinds.
|
||||||
|
(lib.listToAttrs (
|
||||||
|
lib.concatMap (workspace: [
|
||||||
|
{
|
||||||
|
name = "hyprland-workspace-${toString workspace}";
|
||||||
|
value = {
|
||||||
|
keys = "SUPER + ${toString workspace}";
|
||||||
|
action = {
|
||||||
|
kind = "focus";
|
||||||
|
inherit workspace;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "hyprland-move-to-workspace-${toString workspace}";
|
||||||
|
value = {
|
||||||
|
keys = "SUPER + SHIFT + ${toString workspace}";
|
||||||
|
action = {
|
||||||
|
kind = "move-window";
|
||||||
|
inherit workspace;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]) (lib.range 1 9)
|
||||||
|
))
|
||||||
|
|
||||||
|
# Generate directional keybinds.
|
||||||
|
(lib.listToAttrs (
|
||||||
|
lib.concatMap
|
||||||
|
(
|
||||||
|
{ direction, key }:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name = "hyprland-focus-${direction}";
|
||||||
|
value = {
|
||||||
|
keys = "SUPER + ${key}";
|
||||||
|
action = {
|
||||||
|
kind = "focus";
|
||||||
|
inherit direction;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "hyprland-move-${direction}";
|
||||||
|
value = {
|
||||||
|
keys = "SUPER + SHIFT + ${key}";
|
||||||
|
action = {
|
||||||
|
kind = "move-window";
|
||||||
|
inherit direction;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "hyprland-resize-${direction}";
|
||||||
|
value = {
|
||||||
|
keys = "SUPER + ALT + ${key}";
|
||||||
|
repeating = true;
|
||||||
|
action = {
|
||||||
|
kind = "resize-window";
|
||||||
|
inherit direction;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key = "H";
|
||||||
|
direction = "left";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
key = "J";
|
||||||
|
direction = "down";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
key = "K";
|
||||||
|
direction = "up";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
key = "L";
|
||||||
|
direction = "right";
|
||||||
|
}
|
||||||
|
]
|
||||||
|
))
|
||||||
|
{
|
||||||
|
hyprland-toggle-floating = {
|
||||||
|
keys = "SUPER + Space";
|
||||||
|
action.kind = "toggle-floating";
|
||||||
};
|
};
|
||||||
|
|
||||||
dwindle = {
|
hyprland-toggle-fullscreen = {
|
||||||
preserve_split = true;
|
keys = "SUPER + F";
|
||||||
|
action.kind = "toggle-fullscreen";
|
||||||
};
|
};
|
||||||
|
|
||||||
bind = [
|
hyprland-close-window = {
|
||||||
"$mod, Return, exec, $terminal"
|
keys = "SUPER + SHIFT + Q";
|
||||||
|
action.kind = "close-window";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
# Move focus.
|
registry.waybar.${config.networking.hostName}."hyprland/workspaces" = {
|
||||||
"$mod, H, movefocus, l"
|
position = "left";
|
||||||
"$mod, J, movefocus, d"
|
order = 10;
|
||||||
"$mod, K, movefocus, u"
|
settings = {
|
||||||
"$mod, L, movefocus, r"
|
format = "{id} {windows}";
|
||||||
|
format-window-separator = " ";
|
||||||
# Move the window within the layout.
|
window-rewrite-default = util.glyph "f2d0";
|
||||||
"$mod SHIFT, H, movewindow, l"
|
window-rewrite = lib.mapAttrs' (
|
||||||
"$mod SHIFT, J, movewindow, d"
|
_name: windowIcon: lib.nameValuePair "class<${windowIcon.class}>" windowIcon.icon
|
||||||
"$mod SHIFT, K, movewindow, u"
|
) (lib.attrByPath [ config.networking.hostName ] { } config.registry.window-icon);
|
||||||
"$mod SHIFT, L, movewindow, r"
|
|
||||||
|
|
||||||
# Resize the active window.
|
|
||||||
"$mod ALT, H, resizeactive, -40 0"
|
|
||||||
"$mod ALT, J, resizeactive, 0 40"
|
|
||||||
"$mod ALT, K, resizeactive, 0 -40"
|
|
||||||
"$mod ALT, L, resizeactive, 40 0"
|
|
||||||
|
|
||||||
"$mod, Space, togglefloating,"
|
|
||||||
"$mod, F, fullscreen,"
|
|
||||||
# togglesplit is a dwindle layout message, reached through layoutmsg.
|
|
||||||
"$mod, T, layoutmsg, togglesplit"
|
|
||||||
"$mod SHIFT, T, exec, ${toggleLayout}"
|
|
||||||
"$mod SHIFT, Q, killactive,"
|
|
||||||
"$mod CTRL, Q, forcekillactive,"
|
|
||||||
]
|
|
||||||
++ workspaceBinds;
|
|
||||||
|
|
||||||
# Volume and brightness keys repeat while held, each raising a popup
|
|
||||||
# through the OSD client.
|
|
||||||
# Volume is capped at 100 percent.
|
|
||||||
# The client floors brightness so a full hold cannot black the screen out.
|
|
||||||
binde = [
|
|
||||||
", XF86AudioRaiseVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100"
|
|
||||||
", XF86AudioLowerVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume lower"
|
|
||||||
", XF86MonBrightnessUp, exec, ${pkgs.swayosd}/bin/swayosd-client --brightness raise"
|
|
||||||
", XF86MonBrightnessDown, exec, ${pkgs.swayosd}/bin/swayosd-client --brightness lower"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Mute and media transport still fire while the session is locked.
|
|
||||||
bindl = [
|
|
||||||
", XF86AudioMute, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume mute-toggle"
|
|
||||||
", XF86AudioPlay, exec, ${pkgs.playerctl}/bin/playerctl play-pause"
|
|
||||||
", XF86AudioNext, exec, ${pkgs.playerctl}/bin/playerctl next"
|
|
||||||
", XF86AudioPrev, exec, ${pkgs.playerctl}/bin/playerctl previous"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,50 +5,32 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# hypridle: idle-triggered locking and display power management.
|
# hypridle: idle-triggered locking and display power management.
|
||||||
let
|
|
||||||
cfg = config.modules.desktop.session.hyprland.hypridle; # NOLINT(let_order)
|
|
||||||
|
|
||||||
# The guard drops the call when a locker is already up, so no idle trigger
|
|
||||||
# stacks a second hyprlock over the first.
|
|
||||||
lockCmd = "${pkgs.procps}/bin/pidof hyprlock || ${hyprlock}";
|
|
||||||
user = config.user.name;
|
|
||||||
|
|
||||||
hyprctl = "${config.programs.hyprland.package}/bin/hyprctl";
|
|
||||||
hyprlock = "${config.home-manager.users.${user}.programs.hyprlock.package}/bin/hyprlock";
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
options.modules.desktop.session.hyprland.hypridle = {
|
options.modules.desktop.session.hyprland.hypridle.enable =
|
||||||
enable = lib.mkEnableOption "hypridle idle management";
|
lib.mkEnableOption "hypridle idle management";
|
||||||
|
|
||||||
lockTimeout = lib.mkOption {
|
config = lib.mkIf config.modules.desktop.session.hyprland.hypridle.enable {
|
||||||
type = lib.types.ints.positive;
|
|
||||||
default = 300;
|
|
||||||
description = "Seconds of inactivity before the screen locks.";
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
# Closing the lid suspends, and every suspend locks first through the
|
# Closing the lid suspends, and every suspend locks first through the
|
||||||
# before_sleep_cmd below, so the lid always lands at a locked screen.
|
# before_sleep_cmd below, so the lid always lands at a locked screen.
|
||||||
services.logind.settings.Login.HandleLidSwitch = "suspend";
|
services.logind.settings.Login.HandleLidSwitch = "suspend";
|
||||||
|
|
||||||
home-manager.users.${user}.services.hypridle = {
|
home-manager.users.${config.user.name}.services.hypridle = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
general = {
|
general = {
|
||||||
lock_cmd = lockCmd;
|
# The guard drops the call when a locker is already up, so no idle
|
||||||
|
# trigger stacks a second hyprlock over the first.
|
||||||
|
lock_cmd = "${lib.getExe' pkgs.procps "pidof"} hyprlock || ${lib.getExe pkgs.hyprlock}";
|
||||||
before_sleep_cmd = "loginctl lock-session";
|
before_sleep_cmd = "loginctl lock-session";
|
||||||
# Waking pokes the compositor in case firmware leaves displays dark.
|
# Waking pokes the compositor in case firmware leaves displays dark.
|
||||||
after_sleep_cmd = "${hyprctl} dispatch dpms on";
|
after_sleep_cmd = "${lib.getExe' config.programs.hyprland.package "hyprctl"} dispatch 'hl.dsp.dpms({ action = \"enable\" })'";
|
||||||
};
|
};
|
||||||
|
|
||||||
listener = [
|
listener = [
|
||||||
# Lock on idle.
|
# Lock on idle.
|
||||||
{
|
{
|
||||||
timeout = cfg.lockTimeout;
|
timeout = 300;
|
||||||
on-timeout = "loginctl lock-session";
|
on-timeout = "loginctl lock-session";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,44 +2,54 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# The lock screen: a hyprlock session-lock surface the compositor owns.
|
# The lock screen: a hyprlock session-lock surface the compositor owns.
|
||||||
let
|
let
|
||||||
user = config.user.name;
|
sleepIcon = util.glyph [
|
||||||
|
"db81"
|
||||||
# The hyprlock this module installs, used by the lock keybind below.
|
"dcb2"
|
||||||
hyprlock = "${config.home-manager.users.${user}.programs.hyprlock.package}/bin/hyprlock";
|
];
|
||||||
|
sleepOffIcon = util.glyph [
|
||||||
sleepIcon = builtins.fromJSON ''"\udb81\udcb2"'';
|
"db81"
|
||||||
sleepOffIcon = builtins.fromJSON ''"\udb81\udcb3"'';
|
"dcb3"
|
||||||
|
];
|
||||||
|
|
||||||
idleLockToggle = pkgs.writeShellScript "lockscreen-idle-inhibit-toggle" ''
|
idleLockToggle = pkgs.writeShellScript "lockscreen-idle-inhibit-toggle" ''
|
||||||
unit=lockscreen-idle-inhibit.service
|
unit=lockscreen-idle-inhibit.service
|
||||||
|
|
||||||
refresh_waybar() {
|
refresh_waybar() {
|
||||||
${pkgs.procps}/bin/pkill -RTMIN+9 waybar >/dev/null 2>&1 || true
|
${lib.getExe' pkgs.procps "pkill"} -RTMIN+9 waybar >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ${pkgs.systemd}/bin/systemctl --user is-active --quiet "$unit"; then
|
if ${lib.getExe' pkgs.systemd "systemctl"} --user is-active --quiet "$unit"; then
|
||||||
${pkgs.systemd}/bin/systemctl --user stop "$unit"
|
${lib.getExe' pkgs.systemd "systemctl"} --user stop "$unit"
|
||||||
refresh_waybar
|
refresh_waybar
|
||||||
${pkgs.swayosd}/bin/swayosd-client --custom-message "${sleepIcon}"
|
${lib.getExe' pkgs.swayosd "swayosd-client"} --custom-message "${sleepIcon}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ${pkgs.systemd}/bin/systemd-run \
|
if ${lib.getExe' pkgs.systemd "systemd-run"} \
|
||||||
--user \
|
--user \
|
||||||
--collect \
|
--collect \
|
||||||
--unit=lockscreen-idle-inhibit \
|
--unit=lockscreen-idle-inhibit \
|
||||||
--description="Disable idle-triggered screen locking" \
|
--description="Disable idle-triggered screen locking" \
|
||||||
${pkgs.systemd}/bin/systemd-inhibit \
|
${lib.getExe' pkgs.systemd "systemd-inhibit"} \
|
||||||
--what=idle \
|
--what=idle \
|
||||||
--who=lockscreen-hotkey \
|
--who=lockscreen-hotkey \
|
||||||
--why="lock screen disabled by hotkey" \
|
--why="lock screen disabled by hotkey" \
|
||||||
${pkgs.coreutils}/bin/sleep infinity >/dev/null; then
|
${lib.getExe' pkgs.coreutils "sleep"} infinity >/dev/null; then
|
||||||
refresh_waybar
|
refresh_waybar
|
||||||
${pkgs.swayosd}/bin/swayosd-client --custom-message "${sleepOffIcon}"
|
${lib.getExe' pkgs.swayosd "swayosd-client"} --custom-message "${sleepOffIcon}"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
idleLockStatus = pkgs.writeShellScript "waybar-idle-lock-status" ''
|
||||||
|
if ${lib.getExe' pkgs.systemd "systemctl"} --user is-active --quiet lockscreen-idle-inhibit.service; then
|
||||||
|
printf '{"text":"${sleepOffIcon}","tooltip":"Idle locking inhibited","class":"inhibited"}\n'
|
||||||
|
else
|
||||||
|
printf '{"text":"","class":"idle"}\n'
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
@@ -48,7 +58,7 @@ in
|
|||||||
lib.mkEnableOption "the hyprlock lock screen";
|
lib.mkEnableOption "the hyprlock lock screen";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.session.hyprland.hyprlock.enable {
|
config = lib.mkIf config.modules.desktop.session.hyprland.hyprlock.enable {
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${config.user.name} = {
|
||||||
# Colors and the lock-screen background come from Stylix's hyprlock target,
|
# Colors and the lock-screen background come from Stylix's hyprlock target,
|
||||||
# so only geometry and behaviour are set here.
|
# so only geometry and behaviour are set here.
|
||||||
programs.hyprlock = {
|
programs.hyprlock = {
|
||||||
@@ -81,14 +91,36 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# Lock on Super+X.
|
registry.keybind.${config.networking.hostName} = {
|
||||||
# The guard drops the keypress when a locker is already up, so a second
|
hyprlock-lock = {
|
||||||
# hyprlock never stacks over the first.
|
keys = "SUPER + X";
|
||||||
wayland.windowManager.hyprland.settings.bind = [
|
action = {
|
||||||
"$mod, X, exec, ${pkgs.procps}/bin/pidof hyprlock || ${hyprlock}"
|
kind = "exec";
|
||||||
"$mod CTRL, X, exec, ${idleLockToggle}"
|
# The guard drops the call when a locker is already up, so no manual
|
||||||
];
|
# trigger stacks a second hyprlock over the first.
|
||||||
|
command = "${lib.getExe' pkgs.procps "pidof"} hyprlock || ${lib.getExe pkgs.hyprlock}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
hyprlock-toggle-idle-inhibit = {
|
||||||
|
keys = "SUPER + CTRL + X";
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${idleLockToggle}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.waybar.${config.networking.hostName}."custom/idle-lock" = {
|
||||||
|
order = 20;
|
||||||
|
settings = {
|
||||||
|
return-type = "json";
|
||||||
|
exec = "${idleLockStatus}";
|
||||||
|
interval = "once";
|
||||||
|
signal = 9;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,13 @@
|
|||||||
config = lib.mkIf config.modules.desktop.session.login.enable {
|
config = lib.mkIf config.modules.desktop.session.login.enable {
|
||||||
services.greetd = {
|
services.greetd = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings.default_session.command =
|
settings.default_session.command = lib.escapeShellArgs [
|
||||||
"${lib.getExe pkgs.tuigreet} --time --remember "
|
(lib.getExe pkgs.tuigreet)
|
||||||
+ "--cmd 'uwsm start -e -D Hyprland hyprland.desktop'";
|
"--time"
|
||||||
|
"--remember"
|
||||||
|
"--cmd"
|
||||||
|
"${lib.getExe config.programs.uwsm.package} start -e -D Hyprland hyprland.desktop"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,29 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# mako as the notification daemon: toasts, a do-not-disturb mode, and history recall.
|
# mako as the notification daemon: toasts, a do-not-disturb mode, and history recall.
|
||||||
let
|
let
|
||||||
user = config.user.name;
|
dndStatus = pkgs.writeShellScript "waybar-dnd-status" ''
|
||||||
|
if ${lib.getExe' pkgs.mako "makoctl"} mode 2>/dev/null | ${lib.getExe pkgs.gnugrep} -qx dnd; then
|
||||||
|
printf '{"text":"${util.glyph "f1f6"}","tooltip":"Do not disturb: on","class":"dnd"}\n'
|
||||||
|
else
|
||||||
|
printf '{"text":"${util.glyph "f0f3"}","tooltip":"Notifications on","class":"active"}\n'
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
makoctl = "${pkgs.mako}/bin/makoctl";
|
dndToggle = pkgs.writeShellScript "waybar-dnd-toggle" ''
|
||||||
|
${lib.getExe' pkgs.mako "makoctl"} mode -t dnd >/dev/null 2>&1
|
||||||
|
${lib.getExe' pkgs.procps "pkill"} -RTMIN+8 waybar
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.modules.desktop.session.mako.enable = lib.mkEnableOption "the mako notification daemon";
|
options.modules.desktop.session.mako.enable = lib.mkEnableOption "the mako notification daemon";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.session.mako.enable {
|
config = lib.mkIf config.modules.desktop.session.mako.enable {
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${config.user.name} = {
|
||||||
# Colors and the popup font come from Stylix's mako target, so only
|
# Colors and the popup font come from Stylix's mako target, so only
|
||||||
# behaviour is set here.
|
# behaviour is set here.
|
||||||
services.mako = {
|
services.mako = {
|
||||||
@@ -31,13 +41,25 @@ in
|
|||||||
"mode=dnd".invisible = true;
|
"mode=dnd".invisible = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# Recall the last notification from history, keyboard-only like the rest
|
registry.keybind.${config.networking.hostName}.mako-history = {
|
||||||
# of the session.
|
keys = "SUPER + N";
|
||||||
# $mod is defined by the compositor config these binds share.
|
action = {
|
||||||
wayland.windowManager.hyprland.settings.bind = [
|
kind = "exec";
|
||||||
"$mod, N, exec, ${makoctl} restore"
|
command = "${lib.getExe' pkgs.mako "makoctl"} restore";
|
||||||
];
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.waybar.${config.networking.hostName}."custom/dnd" = {
|
||||||
|
order = 80;
|
||||||
|
settings = {
|
||||||
|
return-type = "json";
|
||||||
|
exec = "${dndStatus}";
|
||||||
|
on-click = "${dndToggle}";
|
||||||
|
interval = "once";
|
||||||
|
signal = 8;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,17 @@
|
|||||||
}:
|
}:
|
||||||
# rofi as the desktop launcher and dmenu-style menu frontend.
|
# rofi as the desktop launcher and dmenu-style menu frontend.
|
||||||
let
|
let
|
||||||
user = config.user.name;
|
|
||||||
|
|
||||||
# A session menu built on the same themed rofi, so the power actions look
|
# A session menu built on the same themed rofi, so the power actions look
|
||||||
# like every other menu the launcher drives.
|
# like every other menu the launcher drives.
|
||||||
powerMenu = pkgs.writeShellScript "rofi-power-menu" ''
|
powerMenu = pkgs.writeShellScript "rofi-power-menu" ''
|
||||||
chosen=$(printf '%s\n' Lock Suspend 'Log out' Reboot 'Shut down' \
|
chosen=$(printf '%s\n' Lock Suspend 'Log out' Reboot 'Shut down' \
|
||||||
| ${pkgs.rofi}/bin/rofi -dmenu -i -p Power)
|
| ${lib.getExe pkgs.rofi} -dmenu -i -p Power)
|
||||||
case "$chosen" in
|
case "$chosen" in
|
||||||
Lock) ${pkgs.systemd}/bin/loginctl lock-session ;;
|
Lock) ${lib.getExe' pkgs.systemd "loginctl"} lock-session ;;
|
||||||
Suspend) ${pkgs.systemd}/bin/systemctl suspend ;;
|
Suspend) ${lib.getExe' pkgs.systemd "systemctl"} suspend ;;
|
||||||
'Log out') ${pkgs.systemd}/bin/loginctl terminate-session "$XDG_SESSION_ID" ;;
|
'Log out') ${lib.getExe' pkgs.systemd "loginctl"} terminate-session "$XDG_SESSION_ID" ;;
|
||||||
Reboot) ${pkgs.systemd}/bin/systemctl reboot ;;
|
Reboot) ${lib.getExe' pkgs.systemd "systemctl"} reboot ;;
|
||||||
'Shut down') ${pkgs.systemd}/bin/systemctl poweroff ;;
|
'Shut down') ${lib.getExe' pkgs.systemd "systemctl"} poweroff ;;
|
||||||
esac
|
esac
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
@@ -26,10 +24,11 @@ in
|
|||||||
options.modules.desktop.session.rofi.enable = lib.mkEnableOption "the rofi launcher";
|
options.modules.desktop.session.rofi.enable = lib.mkEnableOption "the rofi launcher";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.session.rofi.enable {
|
config = lib.mkIf config.modules.desktop.session.rofi.enable {
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${config.user.name} = hm: {
|
||||||
# No theme is set here: Stylix's rofi target supplies the Nord one.
|
# Stylix supplies the Nord theme.
|
||||||
programs.rofi = {
|
programs.rofi = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
font = lib.mkForce "JetBrains Mono 12";
|
||||||
|
|
||||||
# Application launch is the only mode, kept minimal so the prompt opens
|
# Application launch is the only mode, kept minimal so the prompt opens
|
||||||
# as fast as rofi can read the desktop entries.
|
# as fast as rofi can read the desktop entries.
|
||||||
@@ -38,14 +37,31 @@ in
|
|||||||
modi = "drun";
|
modi = "drun";
|
||||||
show-icons = false;
|
show-icons = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
theme.window = {
|
||||||
|
border = hm.config.lib.formats.rasi.mkLiteral "2px";
|
||||||
|
border-color = hm.config.lib.formats.rasi.mkLiteral "#FFFFFF";
|
||||||
|
border-radius = hm.config.lib.formats.rasi.mkLiteral "8px";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.keybind.${config.networking.hostName} = {
|
||||||
|
rofi-launcher = {
|
||||||
|
keys = "SUPER + R";
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${lib.getExe pkgs.rofi} -show drun";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# The launcher and the session menu, kept next to the tool they drive.
|
rofi-power-menu = {
|
||||||
# $mod is defined by the compositor config these binds share.
|
keys = "SUPER + SHIFT + X";
|
||||||
wayland.windowManager.hyprland.settings.bind = [
|
action = {
|
||||||
"$mod, R, exec, ${pkgs.rofi}/bin/rofi -show drun"
|
kind = "exec";
|
||||||
"$mod SHIFT, X, exec, ${powerMenu}"
|
command = "${powerMenu}";
|
||||||
];
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,97 +6,19 @@
|
|||||||
}:
|
}:
|
||||||
# Waybar: a top status bar reading system state at a glance.
|
# Waybar: a top status bar reading system state at a glance.
|
||||||
let
|
let
|
||||||
vpnClient = config.modules.network.vpn-client;
|
moduleNames =
|
||||||
|
position:
|
||||||
|
map (entry: entry.name) (
|
||||||
|
lib.sort (left: right: left.module.order < right.module.order) (
|
||||||
|
lib.filter (entry: entry.module.position == position) moduleEntries
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
# A Nerd Font Private-Use-Area codepoint as a literal character, decoded
|
moduleEntries = lib.mapAttrsToList (name: module: {
|
||||||
# through JSON so the glyph survives as bytes rather than an editor paste.
|
inherit name module;
|
||||||
g = code: builtins.fromJSON ''"\u${code}"'';
|
}) modules;
|
||||||
user = config.user.name;
|
|
||||||
|
|
||||||
sleepOffIcon = builtins.fromJSON ''"\udb81\udcb3"'';
|
modules = lib.attrByPath [ config.networking.hostName ] { } config.registry.waybar;
|
||||||
|
|
||||||
# Renders and toggles mako's do-not-disturb mode.
|
|
||||||
# With no mako daemon answering, the status reads "notifications on", keeping
|
|
||||||
# the widget inert rather than broken.
|
|
||||||
dndStatus = pkgs.writeShellScript "waybar-dnd-status" ''
|
|
||||||
if ${pkgs.mako}/bin/makoctl mode 2>/dev/null | ${pkgs.gnugrep}/bin/grep -qx dnd; then
|
|
||||||
printf '{"text":"${g "f1f6"}","tooltip":"Do not disturb: on","class":"dnd"}\n'
|
|
||||||
else
|
|
||||||
printf '{"text":"${g "f0f3"}","tooltip":"Notifications on","class":"active"}\n'
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
dndToggle = pkgs.writeShellScript "waybar-dnd-toggle" ''
|
|
||||||
${pkgs.mako}/bin/makoctl mode -t dnd >/dev/null 2>&1
|
|
||||||
${pkgs.procps}/bin/pkill -RTMIN+8 waybar
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Lit while a wf-recorder capture is running.
|
|
||||||
# Empty text collapses the widget when idle, so it shows nothing until then.
|
|
||||||
recStatus = pkgs.writeShellScript "waybar-recording-status" ''
|
|
||||||
if ${pkgs.procps}/bin/pgrep -x wf-recorder >/dev/null; then
|
|
||||||
printf '{"text":"${g "f03d"}","tooltip":"Recording","class":"recording"}\n'
|
|
||||||
else
|
|
||||||
printf '{"text":"","class":"idle"}\n'
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
idleLockStatus = pkgs.writeShellScript "waybar-idle-lock-status" ''
|
|
||||||
if ${pkgs.systemd}/bin/systemctl --user is-active --quiet lockscreen-idle-inhibit.service; then
|
|
||||||
printf '{"text":"${sleepOffIcon}","tooltip":"Idle locking inhibited","class":"inhibited"}\n'
|
|
||||||
else
|
|
||||||
printf '{"text":"","class":"idle"}\n'
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
vpnStatus = pkgs.writeShellScript "waybar-vpn-status" ''
|
|
||||||
interface=${lib.escapeShellArg vpnClient.interface}
|
|
||||||
|
|
||||||
if ! ${pkgs.iproute2}/bin/ip link show dev "$interface" >/dev/null 2>&1; then
|
|
||||||
printf '{"text":"","class":"idle"}\n'
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
ipaddr=$(${pkgs.iproute2}/bin/ip -4 -o addr show dev "$interface" | ${pkgs.gawk}/bin/awk '{ print $4; exit }')
|
|
||||||
|
|
||||||
${pkgs.jq}/bin/jq -cn \
|
|
||||||
--arg text "" \
|
|
||||||
--arg tooltip "VPN $interface: ''${ipaddr:-no IPv4}" \
|
|
||||||
--arg class "active" \
|
|
||||||
'{ text: $text, tooltip: $tooltip, class: $class }'
|
|
||||||
'';
|
|
||||||
|
|
||||||
networkStatus = pkgs.writeShellScript "waybar-network-status" ''
|
|
||||||
active=$(
|
|
||||||
${pkgs.networkmanager}/bin/nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status \
|
|
||||||
| ${pkgs.gawk}/bin/awk -F: '$3 == "connected" && $2 != "loopback" { print; exit }'
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ -z "$active" ]; then
|
|
||||||
${pkgs.jq}/bin/jq -cn \
|
|
||||||
--arg text "${g "f127"} off" \
|
|
||||||
--arg tooltip "Network disconnected" \
|
|
||||||
--arg class "disconnected" \
|
|
||||||
'{ text: $text, tooltip: $tooltip, class: $class }'
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
IFS=: read -r device type _state connection <<< "$active"
|
|
||||||
|
|
||||||
case "$type" in
|
|
||||||
wifi) icon="${g "f1eb"}" ;;
|
|
||||||
ethernet) icon="LAN" ;;
|
|
||||||
*) icon="NET" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
ipaddr=$(${pkgs.iproute2}/bin/ip -4 -o addr show dev "$device" | ${pkgs.gawk}/bin/awk '{ print $4; exit }')
|
|
||||||
|
|
||||||
${pkgs.jq}/bin/jq -cn \
|
|
||||||
--arg text "$icon $connection" \
|
|
||||||
--arg tooltip "$device: ''${ipaddr:-no IPv4}" \
|
|
||||||
--arg class "$type" \
|
|
||||||
'{ text: $text, tooltip: $tooltip, class: $class }'
|
|
||||||
'';
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.modules.desktop.session.waybar.enable = lib.mkEnableOption "the Waybar status bar";
|
options.modules.desktop.session.waybar.enable = lib.mkEnableOption "the Waybar status bar";
|
||||||
@@ -106,64 +28,34 @@ in
|
|||||||
# codepoints Stylix's monospace font does not carry.
|
# codepoints Stylix's monospace font does not carry.
|
||||||
fonts.packages = [ pkgs.nerd-fonts.symbols-only ];
|
fonts.packages = [ pkgs.nerd-fonts.symbols-only ];
|
||||||
|
|
||||||
home-manager.users.${user}.programs.waybar = {
|
home-manager.users.${config.user.name}.programs.waybar = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
# A user service bound to graphical-session.target, which uwsm activates,
|
# A user service bound to graphical-session.target, which uwsm activates,
|
||||||
# so the bar comes up with the session and no compositor exec-once.
|
# so the bar comes up with the session and no compositor exec-once.
|
||||||
systemd.enable = true;
|
systemd.enable = true;
|
||||||
|
|
||||||
style = ''
|
settings.mainBar = lib.mkMerge [
|
||||||
.modules-right {
|
{
|
||||||
margin-right: 8px;
|
layer = "top";
|
||||||
|
position = "top";
|
||||||
|
height = 30;
|
||||||
|
spacing = 8;
|
||||||
|
margin-right = 8;
|
||||||
|
|
||||||
|
modules-left = moduleNames "left";
|
||||||
|
modules-center = moduleNames "center";
|
||||||
|
modules-right = moduleNames "right";
|
||||||
}
|
}
|
||||||
|
(lib.mapAttrs (_name: module: module.settings) modules)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
#custom-dnd {
|
registry.waybar.${config.networking.hostName} = {
|
||||||
margin-right: 8px;
|
clock = {
|
||||||
}
|
position = "center";
|
||||||
'';
|
order = 10;
|
||||||
|
settings = {
|
||||||
settings.mainBar = {
|
|
||||||
layer = "top";
|
|
||||||
position = "top";
|
|
||||||
height = 30;
|
|
||||||
spacing = 8;
|
|
||||||
|
|
||||||
modules-left = [ "hyprland/workspaces" ];
|
|
||||||
modules-center = [ "clock" ];
|
|
||||||
modules-right = [
|
|
||||||
"custom/recording"
|
|
||||||
"custom/idle-lock"
|
|
||||||
"mpris"
|
|
||||||
"wireplumber"
|
|
||||||
]
|
|
||||||
++ lib.optional vpnClient.enable "custom/vpn"
|
|
||||||
++ [
|
|
||||||
"custom/network"
|
|
||||||
"battery"
|
|
||||||
"custom/dnd"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Per-application icons for the windows on each workspace, mapped from
|
|
||||||
# window class with a neutral glyph for anything unlisted.
|
|
||||||
"hyprland/workspaces" = {
|
|
||||||
on-click = "activate";
|
|
||||||
format = "{id} {windows}";
|
|
||||||
format-window-separator = " ";
|
|
||||||
window-rewrite-default = g "f2d0";
|
|
||||||
window-rewrite = {
|
|
||||||
"class<com.mitchellh.ghostty>" = g "f120";
|
|
||||||
"class<.*[Ff]irefox.*>" = g "f269";
|
|
||||||
"class<chromium.*>" = g "f268";
|
|
||||||
"class<[Cc]ode>" = g "f121";
|
|
||||||
"class<obsidian>" = g "f02d";
|
|
||||||
"class<[Ss]ignal>" = g "f4ad";
|
|
||||||
"class<[Ss]team>" = g "f1b6";
|
|
||||||
"class<[Vv]esktop>" = g "f1ff";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
clock = {
|
|
||||||
format = "{:%a %d %b %H:%M}";
|
format = "{:%a %d %b %H:%M}";
|
||||||
tooltip-format = "<tt>{calendar}</tt>";
|
tooltip-format = "<tt>{calendar}</tt>";
|
||||||
calendar = {
|
calendar = {
|
||||||
@@ -172,77 +64,6 @@ in
|
|||||||
format.today = "<b><u>{}</u></b>";
|
format.today = "<b><u>{}</u></b>";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
mpris = {
|
|
||||||
format = "${g "f001"} {title}";
|
|
||||||
format-paused = "${g "f04c"} {title}";
|
|
||||||
max-length = 40;
|
|
||||||
on-click = "play-pause";
|
|
||||||
on-scroll-up = "next";
|
|
||||||
on-scroll-down = "previous";
|
|
||||||
};
|
|
||||||
|
|
||||||
wireplumber = {
|
|
||||||
format = "{icon} {volume}%";
|
|
||||||
format-muted = "${g "f026"} muted";
|
|
||||||
format-icons = [
|
|
||||||
(g "f026")
|
|
||||||
(g "f027")
|
|
||||||
(g "f028")
|
|
||||||
];
|
|
||||||
on-click = "${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
|
|
||||||
};
|
|
||||||
|
|
||||||
"custom/vpn" = {
|
|
||||||
return-type = "json";
|
|
||||||
exec = "${vpnStatus}";
|
|
||||||
interval = 5;
|
|
||||||
};
|
|
||||||
|
|
||||||
"custom/network" = {
|
|
||||||
return-type = "json";
|
|
||||||
exec = "${networkStatus}";
|
|
||||||
interval = 5;
|
|
||||||
};
|
|
||||||
|
|
||||||
battery = {
|
|
||||||
format = "{icon} {capacity}%";
|
|
||||||
format-charging = "${g "f0e7"} {capacity}%";
|
|
||||||
format-icons = [
|
|
||||||
(g "f244")
|
|
||||||
(g "f243")
|
|
||||||
(g "f242")
|
|
||||||
(g "f241")
|
|
||||||
(g "f240")
|
|
||||||
];
|
|
||||||
states = {
|
|
||||||
warning = 20;
|
|
||||||
critical = 10;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
"custom/dnd" = {
|
|
||||||
return-type = "json";
|
|
||||||
exec = "${dndStatus}";
|
|
||||||
on-click = "${dndToggle}";
|
|
||||||
interval = "once";
|
|
||||||
signal = 8;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Recording state has no event to hook, so the widget samples the
|
|
||||||
# wf-recorder process once a second.
|
|
||||||
"custom/recording" = {
|
|
||||||
return-type = "json";
|
|
||||||
exec = "${recStatus}";
|
|
||||||
interval = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
"custom/idle-lock" = {
|
|
||||||
return-type = "json";
|
|
||||||
exec = "${idleLockStatus}";
|
|
||||||
interval = "once";
|
|
||||||
signal = 9;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
{ config, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
util,
|
||||||
|
...
|
||||||
|
}:
|
||||||
# PipeWire as the desktop audio server.
|
# PipeWire as the desktop audio server.
|
||||||
|
let
|
||||||
|
mediaKey = keys: command: {
|
||||||
|
inherit keys;
|
||||||
|
locked = true;
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${lib.getExe pkgs.playerctl} ${command}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
options.modules.desktop.utilities.audio.enable = lib.mkEnableOption "the PipeWire audio server";
|
options.modules.desktop.utilities.audio.enable = lib.mkEnableOption "the PipeWire audio server";
|
||||||
|
|
||||||
@@ -12,5 +28,39 @@
|
|||||||
alsa.enable = true;
|
alsa.enable = true;
|
||||||
pulse.enable = true;
|
pulse.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
registry.keybind.${config.networking.hostName} = {
|
||||||
|
media-play = mediaKey "XF86AudioPlay" "play-pause";
|
||||||
|
media-next = mediaKey "XF86AudioNext" "next";
|
||||||
|
media-previous = mediaKey "XF86AudioPrev" "previous";
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.waybar.${config.networking.hostName} = {
|
||||||
|
mpris = {
|
||||||
|
order = 30;
|
||||||
|
settings = {
|
||||||
|
format = "${util.glyph "f001"} {title}";
|
||||||
|
format-paused = "${util.glyph "f04c"} {title}";
|
||||||
|
max-length = 40;
|
||||||
|
on-click = "play-pause";
|
||||||
|
on-scroll-up = "next";
|
||||||
|
on-scroll-down = "previous";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
wireplumber = {
|
||||||
|
order = 40;
|
||||||
|
settings = {
|
||||||
|
format = "{icon} {volume}%";
|
||||||
|
format-muted = "${util.glyph "f026"} muted";
|
||||||
|
format-icons = [
|
||||||
|
(util.glyph "f026")
|
||||||
|
(util.glyph "f027")
|
||||||
|
(util.glyph "f028")
|
||||||
|
];
|
||||||
|
on-click = "${lib.getExe' pkgs.wireplumber "wpctl"} set-mute @DEFAULT_AUDIO_SINK@ toggle";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,21 +6,15 @@
|
|||||||
}:
|
}:
|
||||||
# Clipboard history: cliphist records every copy, picked back through rofi.
|
# Clipboard history: cliphist records every copy, picked back through rofi.
|
||||||
let
|
let
|
||||||
user = config.user.name;
|
|
||||||
|
|
||||||
cliphist = "${pkgs.cliphist}/bin/cliphist";
|
|
||||||
rofi = "${pkgs.rofi}/bin/rofi";
|
|
||||||
wl-copy = "${pkgs.wl-clipboard}/bin/wl-copy";
|
|
||||||
|
|
||||||
# The picker reuses the themed rofi, so history looks like every other menu
|
# The picker reuses the themed rofi, so history looks like every other menu
|
||||||
# the launcher drives.
|
# the launcher drives.
|
||||||
# decode is needed because list emits id-prefixed lines rather than the copied
|
# decode is needed because list emits id-prefixed lines rather than the copied
|
||||||
# bytes, so the chosen id has to be resolved back before it can be re-copied.
|
# bytes, so the chosen id has to be resolved back before it can be re-copied.
|
||||||
picker = pkgs.writeShellScript "clipboard-picker" ''
|
picker = pkgs.writeShellScript "clipboard-picker" ''
|
||||||
${cliphist} list \
|
${lib.getExe pkgs.cliphist} list \
|
||||||
| ${rofi} -dmenu -i -p Clipboard \
|
| ${lib.getExe pkgs.rofi} -dmenu -i -p Clipboard \
|
||||||
| ${cliphist} decode \
|
| ${lib.getExe pkgs.cliphist} decode \
|
||||||
| ${wl-copy}
|
| ${lib.getExe' pkgs.wl-clipboard "wl-copy"}
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -28,7 +22,7 @@ in
|
|||||||
lib.mkEnableOption "cliphist clipboard history";
|
lib.mkEnableOption "cliphist clipboard history";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.utilities.clipboard.enable {
|
config = lib.mkIf config.modules.desktop.utilities.clipboard.enable {
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${config.user.name} = {
|
||||||
# wl-copy and wl-paste on PATH, so the shell can pipe into and out of the
|
# wl-copy and wl-paste on PATH, so the shell can pipe into and out of the
|
||||||
# clipboard.
|
# clipboard.
|
||||||
# The watchers and picker above reach wl-clipboard by store path, so this
|
# The watchers and picker above reach wl-clipboard by store path, so this
|
||||||
@@ -38,11 +32,14 @@ in
|
|||||||
# Two watchers record text and images to history, bound to the graphical
|
# Two watchers record text and images to history, bound to the graphical
|
||||||
# session so uwsm starts and stops them with it.
|
# session so uwsm starts and stops them with it.
|
||||||
services.cliphist.enable = true;
|
services.cliphist.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
# $mod is defined by the compositor config these binds share.
|
registry.keybind.${config.networking.hostName}.clipboard-history = {
|
||||||
wayland.windowManager.hyprland.settings.bind = [
|
keys = "SUPER + SHIFT + V";
|
||||||
"$mod SHIFT, V, exec, ${picker}"
|
action = {
|
||||||
];
|
kind = "exec";
|
||||||
|
command = "${picker}";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,53 @@ in
|
|||||||
lib.mkEnableOption "the SwayOSD volume and brightness popup";
|
lib.mkEnableOption "the SwayOSD volume and brightness popup";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.utilities.osd.enable {
|
config = lib.mkIf config.modules.desktop.utilities.osd.enable {
|
||||||
|
registry.keybind.${config.networking.hostName} = {
|
||||||
|
volume-up = {
|
||||||
|
keys = "XF86AudioRaiseVolume";
|
||||||
|
repeating = true;
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
volume-down = {
|
||||||
|
keys = "XF86AudioLowerVolume";
|
||||||
|
repeating = true;
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${pkgs.swayosd}/bin/swayosd-client --output-volume lower";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
brightness-up = {
|
||||||
|
keys = "XF86MonBrightnessUp";
|
||||||
|
repeating = true;
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${pkgs.swayosd}/bin/swayosd-client --brightness raise";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
brightness-down = {
|
||||||
|
keys = "XF86MonBrightnessDown";
|
||||||
|
repeating = true;
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${pkgs.swayosd}/bin/swayosd-client --brightness lower";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mute = {
|
||||||
|
keys = "XF86AudioMute";
|
||||||
|
locked = true;
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${pkgs.swayosd}/bin/swayosd-client --output-volume mute-toggle";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
environment.systemPackages = [ pkgs.swayosd ];
|
environment.systemPackages = [ pkgs.swayosd ];
|
||||||
|
|
||||||
# The udev rule chgrps each backlight's brightness node to `video` and adds
|
# The udev rule chgrps each backlight's brightness node to `video` and adds
|
||||||
|
|||||||
@@ -2,21 +2,11 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# Keybound screen recording: pick a region, then toggle a video-only capture.
|
# Keybound screen recording: pick a region, then toggle a video-only capture.
|
||||||
let
|
let
|
||||||
user = config.user.name;
|
|
||||||
|
|
||||||
slurp = "${pkgs.slurp}/bin/slurp";
|
|
||||||
wf-recorder = "${pkgs.wf-recorder}/bin/wf-recorder";
|
|
||||||
notify-send = "${pkgs.libnotify}/bin/notify-send";
|
|
||||||
pgrep = "${pkgs.procps}/bin/pgrep";
|
|
||||||
pkill = "${pkgs.procps}/bin/pkill";
|
|
||||||
date = "${pkgs.coreutils}/bin/date";
|
|
||||||
mkdir = "${pkgs.coreutils}/bin/mkdir";
|
|
||||||
xdgUserDir = "${pkgs.xdg-user-dirs}/bin/xdg-user-dir";
|
|
||||||
|
|
||||||
# A single key both starts and stops.
|
# A single key both starts and stops.
|
||||||
# A running wf-recorder is stopped with SIGINT so it finalises the file.
|
# A running wf-recorder is stopped with SIGINT so it finalises the file.
|
||||||
# Otherwise slurp picks a region and wf-recorder runs in the foreground until
|
# Otherwise slurp picks a region and wf-recorder runs in the foreground until
|
||||||
@@ -24,19 +14,29 @@ let
|
|||||||
# reports it saved.
|
# reports it saved.
|
||||||
# No -a means no audio is captured.
|
# No -a means no audio is captured.
|
||||||
toggle = pkgs.writeShellScript "screen-record-toggle" ''
|
toggle = pkgs.writeShellScript "screen-record-toggle" ''
|
||||||
if ${pgrep} -x wf-recorder >/dev/null; then
|
if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then
|
||||||
${pkill} -INT -x wf-recorder
|
${lib.getExe' pkgs.procps "pkill"} -INT -x wf-recorder
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
region=$(${slurp}) || exit 0
|
region=$(${lib.getExe pkgs.slurp}) || exit 0
|
||||||
dir="$(${xdgUserDir} VIDEOS)/Recordings"
|
dir="$(${lib.getExe' pkgs.xdg-user-dirs "xdg-user-dir"} VIDEOS)/Recordings"
|
||||||
${mkdir} -p "$dir"
|
${lib.getExe' pkgs.coreutils "mkdir"} -p "$dir"
|
||||||
file="$dir/recording-$(${date} +%Y%m%d-%H%M%S).mp4"
|
file="$dir/recording-$(${lib.getExe' pkgs.coreutils "date"} +%Y%m%d-%H%M%S).mp4"
|
||||||
|
|
||||||
${notify-send} -a "Screen recording" "Recording started" "Region capture, no audio."
|
${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording started" "Region capture, no audio."
|
||||||
${wf-recorder} -g "$region" -f "$file"
|
${lib.getExe pkgs.wf-recorder} -g "$region" -f "$file"
|
||||||
${notify-send} -a "Screen recording" "Recording saved" "$file"
|
${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording saved" "$file"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Lit while a wf-recorder capture is running.
|
||||||
|
# Empty text collapses the widget when idle, so it shows nothing until then.
|
||||||
|
recordingStatus = pkgs.writeShellScript "waybar-recording-status" ''
|
||||||
|
if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then
|
||||||
|
printf '{"text":"${util.glyph "f03d"}","tooltip":"Recording","class":"recording"}\n'
|
||||||
|
else
|
||||||
|
printf '{"text":"","class":"idle"}\n'
|
||||||
|
fi
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -44,11 +44,21 @@ in
|
|||||||
lib.mkEnableOption "wf-recorder screen recording";
|
lib.mkEnableOption "wf-recorder screen recording";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.utilities.recording.enable {
|
config = lib.mkIf config.modules.desktop.utilities.recording.enable {
|
||||||
home-manager.users.${user} = {
|
registry.keybind.${config.networking.hostName}.recording-toggle = {
|
||||||
# $mod is defined by the compositor config these binds share.
|
keys = "SUPER + SHIFT + R";
|
||||||
wayland.windowManager.hyprland.settings.bind = [
|
action = {
|
||||||
"$mod SHIFT, R, exec, ${toggle}"
|
kind = "exec";
|
||||||
];
|
command = "${toggle}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.waybar.${config.networking.hostName}."custom/recording" = {
|
||||||
|
order = 10;
|
||||||
|
settings = {
|
||||||
|
return-type = "json";
|
||||||
|
exec = "${recordingStatus}";
|
||||||
|
interval = 1;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,23 +37,42 @@ in
|
|||||||
lib.mkEnableOption "screenshots via grimblast and satty";
|
lib.mkEnableOption "screenshots via grimblast and satty";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.utilities.screenshot.enable {
|
config = lib.mkIf config.modules.desktop.utilities.screenshot.enable {
|
||||||
|
registry.keybind.${config.networking.hostName} = {
|
||||||
|
screenshot-area = {
|
||||||
|
keys = "Print";
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${capture "area"}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
screenshot-active = {
|
||||||
|
keys = "SHIFT + Print";
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${capture "active"}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
screenshot-screen = {
|
||||||
|
keys = "CTRL + Print";
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${capture "screen"}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${user} = {
|
||||||
# satty is a one-shot annotation surface, so float it rather than letting
|
# satty is a one-shot annotation surface, so float it rather than letting
|
||||||
# it claim a tile in the layout.
|
# it claim a tile in the layout.
|
||||||
# windowrule v3 syntax: space-separated field/value tokens, comma
|
wayland.windowManager.hyprland.settings.window_rule = [
|
||||||
# separated, with matchers under a match: prefix and effects bare.
|
{
|
||||||
wayland.windowManager.hyprland.settings.windowrule = [
|
match.class = "^(com\\.gabm\\.satty)$";
|
||||||
"float 1, match:class ^(com\\.gabm\\.satty)$"
|
float = true;
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
# Print with plain/Shift/Ctrl for region/window/full.
|
|
||||||
# Super+L is already the hjkl focus and movement bind, so screenshots take
|
|
||||||
# the Print key instead.
|
|
||||||
wayland.windowManager.hyprland.settings.bind = [
|
|
||||||
", Print, exec, ${capture "area"}"
|
|
||||||
"SHIFT, Print, exec, ${capture "active"}"
|
|
||||||
"CTRL, Print, exec, ${capture "screen"}"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
registry.env.${config.networking.hostName} = {
|
||||||
|
HYPRCURSOR_THEME = config.stylix.cursor.name;
|
||||||
|
HYPRCURSOR_SIZE = toString config.stylix.cursor.size;
|
||||||
|
};
|
||||||
|
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${user} = {
|
||||||
# Stylix drives the cursor through home-manager's pointer-cursor config,
|
# Stylix drives the cursor through home-manager's pointer-cursor config,
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ in
|
|||||||
modules.network.ssh.userKey.sopsFile = hostSecrets;
|
modules.network.ssh.userKey.sopsFile = hostSecrets;
|
||||||
|
|
||||||
services.xserver.xkb.layout = "us";
|
services.xserver.xkb.layout = "us";
|
||||||
services.xserver.xkb.options = "caps:escape_shifted_capslock";
|
|
||||||
console.useXkbConfig = true;
|
console.useXkbConfig = true;
|
||||||
|
|
||||||
sops.defaultSopsFile = ../../secrets/shared.yaml;
|
sops.defaultSopsFile = ../../secrets/shared.yaml;
|
||||||
@@ -54,6 +53,7 @@ in
|
|||||||
home.username = config.user.name;
|
home.username = config.user.name;
|
||||||
home.homeDirectory = "/home/${config.user.name}";
|
home.homeDirectory = "/home/${config.user.name}";
|
||||||
home.stateVersion = config.system.stateVersion;
|
home.stateVersion = config.system.stateVersion;
|
||||||
|
home.sessionVariables = lib.attrByPath [ config.networking.hostName ] { } config.registry.env;
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
eza
|
eza
|
||||||
unzip
|
unzip
|
||||||
|
|||||||
57
configs/nixos/modules/network/default.nix
Normal file
57
configs/nixos/modules/network/default.nix
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
util,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
# NetworkManager-managed networking.
|
||||||
|
let
|
||||||
|
networkStatus = pkgs.writeShellScript "waybar-network-status" ''
|
||||||
|
active=$(
|
||||||
|
${lib.getExe' pkgs.networkmanager "nmcli"} -t -f DEVICE,TYPE,STATE,CONNECTION device status \
|
||||||
|
| ${lib.getExe' pkgs.gawk "awk"} -F: '$3 == "connected" && $2 != "loopback" { print; exit }'
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ -z "$active" ]; then
|
||||||
|
${lib.getExe pkgs.jq} -cn \
|
||||||
|
--arg text "${util.glyph "f127"} off" \
|
||||||
|
--arg tooltip "Network disconnected" \
|
||||||
|
--arg class "disconnected" \
|
||||||
|
'{ text: $text, tooltip: $tooltip, class: $class }'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS=: read -r device type _state connection <<< "$active"
|
||||||
|
|
||||||
|
case "$type" in
|
||||||
|
wifi) icon="${util.glyph "f1eb"}" ;;
|
||||||
|
ethernet) icon="LAN" ;;
|
||||||
|
*) icon="NET" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ipaddr=$(${lib.getExe' pkgs.iproute2 "ip"} -4 -o addr show dev "$device" | ${lib.getExe' pkgs.gawk "awk"} '{ print $4; exit }')
|
||||||
|
|
||||||
|
${lib.getExe pkgs.jq} -cn \
|
||||||
|
--arg text "$icon $connection" \
|
||||||
|
--arg tooltip "$device: ''${ipaddr:-no IPv4}" \
|
||||||
|
--arg class "$type" \
|
||||||
|
'{ text: $text, tooltip: $tooltip, class: $class }'
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.modules.network.enable = lib.mkEnableOption "NetworkManager-managed networking";
|
||||||
|
|
||||||
|
config = lib.mkIf config.modules.network.enable {
|
||||||
|
networking.networkmanager.enable = true;
|
||||||
|
|
||||||
|
registry.waybar.${config.networking.hostName}."custom/network" = {
|
||||||
|
order = 60;
|
||||||
|
settings = {
|
||||||
|
return-type = "json";
|
||||||
|
exec = "${networkStatus}";
|
||||||
|
interval = 5;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,10 +1,34 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.modules.network.vpn-client; # NOLINT(let_order)
|
cfg = config.modules.network.vpn-client; # NOLINT(let_order)
|
||||||
|
|
||||||
|
vpnIcon = util.glyph [
|
||||||
|
"db83"
|
||||||
|
"dc9d"
|
||||||
|
];
|
||||||
|
|
||||||
|
vpnStatus = pkgs.writeShellScript "waybar-vpn-status" ''
|
||||||
|
interface=${lib.escapeShellArg cfg.interface}
|
||||||
|
|
||||||
|
if ! ${lib.getExe' pkgs.iproute2 "ip"} link show dev "$interface" >/dev/null 2>&1; then
|
||||||
|
printf '{"text":"","class":"idle"}\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ipaddr=$(${lib.getExe' pkgs.iproute2 "ip"} -4 -o addr show dev "$interface" | ${lib.getExe' pkgs.gawk "awk"} '{ print $4; exit }')
|
||||||
|
|
||||||
|
${lib.getExe pkgs.jq} -cn \
|
||||||
|
--arg text "${vpnIcon}" \
|
||||||
|
--arg tooltip "VPN $interface: ''${ipaddr:-no IPv4}" \
|
||||||
|
--arg class "active" \
|
||||||
|
'{ text: $text, tooltip: $tooltip, class: $class }'
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.modules.network.vpn-client = {
|
options.modules.network.vpn-client = {
|
||||||
@@ -57,6 +81,15 @@ in
|
|||||||
vpn-off = "sudo systemctl stop 'wireguard-${cfg.interface}*.service' wireguard-${cfg.interface}.target";
|
vpn-off = "sudo systemctl stop 'wireguard-${cfg.interface}*.service' wireguard-${cfg.interface}.target";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
registry.waybar.${config.networking.hostName}."custom/vpn" = {
|
||||||
|
order = 50;
|
||||||
|
settings = {
|
||||||
|
return-type = "json";
|
||||||
|
exec = "${vpnStatus}";
|
||||||
|
interval = 5;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
systemd.targets."wireguard-${cfg.interface}".wantedBy = lib.mkForce [ ];
|
systemd.targets."wireguard-${cfg.interface}".wantedBy = lib.mkForce [ ];
|
||||||
|
|
||||||
networking.wireguard.interfaces.${cfg.interface} = {
|
networking.wireguard.interfaces.${cfg.interface} = {
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ in
|
|||||||
lib.mkEnableOption "Python tools for the primary user's shell environment";
|
lib.mkEnableOption "Python tools for the primary user's shell environment";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.shell.python.enable {
|
config = lib.mkIf config.modules.shell.python.enable {
|
||||||
home-manager.users.${user}.home = {
|
registry.env.${config.networking.hostName}.PYTHON_TOOLCHAIN_ROOT = "${pkgs.python3}";
|
||||||
sessionVariables.PYTHON_TOOLCHAIN_ROOT = "${pkgs.python3}";
|
|
||||||
packages = [ pkgs.python3 ];
|
home-manager.users.${user}.home.packages = [ pkgs.python3 ];
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ in
|
|||||||
lib.mkEnableOption "Rust tools for the primary user's shell environment";
|
lib.mkEnableOption "Rust tools for the primary user's shell environment";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.shell.rust.enable {
|
config = lib.mkIf config.modules.shell.rust.enable {
|
||||||
home-manager.users.${user} = {
|
registry.env.${config.networking.hostName}.RUST_TOOLCHAIN_ROOT = "${toolchain}";
|
||||||
home.sessionVariables.RUST_TOOLCHAIN_ROOT = "${toolchain}";
|
|
||||||
|
|
||||||
|
home-manager.users.${user} = {
|
||||||
home.packages = [
|
home.packages = [
|
||||||
toolchain
|
toolchain
|
||||||
pkgs.rust-analyzer
|
pkgs.rust-analyzer
|
||||||
|
|||||||
4
configs/nixos/registry/env.nix
Normal file
4
configs/nixos/registry/env.nix
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
type = lib.types.str;
|
||||||
|
}
|
||||||
112
configs/nixos/registry/keybind.nix
Normal file
112
configs/nixos/registry/keybind.nix
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
{ lib, util, ... }:
|
||||||
|
{
|
||||||
|
checks = [
|
||||||
|
{
|
||||||
|
assertion = util.registry.predicates.uniquePerHost (entry: entry.value.keys);
|
||||||
|
message = "Desktop keybinds must use unique key chords per host.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion =
|
||||||
|
entries:
|
||||||
|
lib.all (
|
||||||
|
entry:
|
||||||
|
(entry.value.action.kind == "exec" && entry.value.action.command != null)
|
||||||
|
|| (
|
||||||
|
builtins.elem entry.value.action.kind [
|
||||||
|
"focus"
|
||||||
|
"move-window"
|
||||||
|
]
|
||||||
|
&& lib.xor (entry.value.action.direction != null) (entry.value.action.workspace != null)
|
||||||
|
)
|
||||||
|
|| (
|
||||||
|
entry.value.action.kind == "resize-window"
|
||||||
|
&& entry.value.action.direction != null
|
||||||
|
&& entry.value.action.workspace == null
|
||||||
|
)
|
||||||
|
|| (
|
||||||
|
builtins.elem entry.value.action.kind [
|
||||||
|
"toggle-floating"
|
||||||
|
"toggle-fullscreen"
|
||||||
|
"close-window"
|
||||||
|
]
|
||||||
|
&& entry.value.action.command == null
|
||||||
|
&& entry.value.action.direction == null
|
||||||
|
&& entry.value.action.workspace == null
|
||||||
|
)
|
||||||
|
) entries;
|
||||||
|
message = "Desktop keybind actions must set the fields their kind requires.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
keys = lib.mkOption {
|
||||||
|
description = "Sets the key chord for this desktop keybind.";
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "SUPER + Return";
|
||||||
|
};
|
||||||
|
|
||||||
|
repeating = lib.mkOption {
|
||||||
|
description = "Repeats the keybind action while the key chord is held.";
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
example = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
locked = lib.mkOption {
|
||||||
|
description = "Allows the keybind action while the session is locked.";
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
example = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
action = lib.mkOption {
|
||||||
|
description = "Declares the action this keybind runs.";
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
kind = lib.mkOption {
|
||||||
|
description = "Selects the keybind action kind.";
|
||||||
|
type = lib.types.enum [
|
||||||
|
"exec"
|
||||||
|
"focus"
|
||||||
|
"move-window"
|
||||||
|
"resize-window"
|
||||||
|
"toggle-floating"
|
||||||
|
"toggle-fullscreen"
|
||||||
|
"close-window"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
command = lib.mkOption {
|
||||||
|
description = "Command executed by an exec keybind.";
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
example = "ghostty";
|
||||||
|
};
|
||||||
|
|
||||||
|
direction = lib.mkOption {
|
||||||
|
description = "Direction targeted by a keybind action.";
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.enum [
|
||||||
|
"down"
|
||||||
|
"left"
|
||||||
|
"right"
|
||||||
|
"up"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = null;
|
||||||
|
example = "left";
|
||||||
|
};
|
||||||
|
|
||||||
|
workspace = lib.mkOption {
|
||||||
|
description = "Workspace targeted by a keybind action.";
|
||||||
|
type = lib.types.nullOr lib.types.int;
|
||||||
|
default = null;
|
||||||
|
example = 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
29
configs/nixos/registry/waybar.nix
Normal file
29
configs/nixos/registry/waybar.nix
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
position = lib.mkOption {
|
||||||
|
type = lib.types.enum [
|
||||||
|
"left"
|
||||||
|
"center"
|
||||||
|
"right"
|
||||||
|
];
|
||||||
|
default = "right";
|
||||||
|
description = "Selects the Waybar section that shows the module.";
|
||||||
|
};
|
||||||
|
|
||||||
|
order = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 100;
|
||||||
|
example = 50;
|
||||||
|
description = "Sorts the module within its Waybar section.";
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.anything;
|
||||||
|
default = { };
|
||||||
|
description = "Sets the Waybar module configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
19
configs/nixos/registry/window-icon.nix
Normal file
19
configs/nixos/registry/window-icon.nix
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
class = lib.mkOption {
|
||||||
|
description = "Matches the window class reported by the compositor.";
|
||||||
|
type = lib.types.str;
|
||||||
|
example = ".*[Ff]irefox.*";
|
||||||
|
};
|
||||||
|
|
||||||
|
icon = lib.mkOption {
|
||||||
|
description = "Sets the icon shown for matching windows.";
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -16,14 +16,31 @@ let
|
|||||||
[ ]
|
[ ]
|
||||||
) (builtins.readDir dir)
|
) (builtins.readDir dir)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
uniqueValue =
|
||||||
|
value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit collectNixFiles;
|
inherit collectNixFiles;
|
||||||
|
|
||||||
removeNixSuffix = path: lib.removeSuffix ".nix" (builtins.baseNameOf path);
|
removeNixSuffix = path: lib.removeSuffix ".nix" (builtins.baseNameOf path);
|
||||||
|
|
||||||
registry.predicates.uniqueValue =
|
glyph =
|
||||||
value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries;
|
code:
|
||||||
|
builtins.fromJSON (
|
||||||
|
"\""
|
||||||
|
+ (if builtins.isList code then lib.concatMapStrings (part: "\\u${part}") code else "\\u${code}")
|
||||||
|
+ "\""
|
||||||
|
);
|
||||||
|
|
||||||
|
registry.predicates.uniqueValue = uniqueValue;
|
||||||
|
|
||||||
|
registry.predicates.uniquePerHost =
|
||||||
|
value:
|
||||||
|
uniqueValue (entry: {
|
||||||
|
inherit (entry) host;
|
||||||
|
value = value entry;
|
||||||
|
});
|
||||||
|
|
||||||
bridgeName = id: "br-vlan${toString id}";
|
bridgeName = id: "br-vlan${toString id}";
|
||||||
|
|
||||||
|
|||||||
@@ -21,3 +21,32 @@ Do not call `lib.removeSuffix ".nix"` outside the utility definition.
|
|||||||
### Rationale
|
### Rationale
|
||||||
|
|
||||||
The utility centralizes Nix file-name normalization.
|
The utility centralizes Nix file-name normalization.
|
||||||
|
|
||||||
|
## Use executable helpers for package commands
|
||||||
|
|
||||||
|
### Rule
|
||||||
|
|
||||||
|
Use `lib.getExe` for a package's main executable.
|
||||||
|
Use `lib.getExe'` for a package's non-main executable.
|
||||||
|
Do not build executable paths with `"${pkg}/bin/name"` when an executable helper names the target.
|
||||||
|
Use `lib.getExe' config.programs.<name>.package "tool"` when a command follows a configured package option.
|
||||||
|
|
||||||
|
### Rationale
|
||||||
|
|
||||||
|
Executable helpers select the package output that contains binaries.
|
||||||
|
They make package ownership explicit.
|
||||||
|
`lib.getExe'` prevents calls to a package's main executable when code needs another tool.
|
||||||
|
|
||||||
|
## Prefer NixOS package ownership for commands
|
||||||
|
|
||||||
|
### Rule
|
||||||
|
|
||||||
|
Use `pkgs.<name>` for Home Manager tools.
|
||||||
|
Use `config.programs.<name>.package` when a NixOS module owns the package selection.
|
||||||
|
Do not read `config.home-manager.users.<user>.programs.<name>.package` for command paths.
|
||||||
|
|
||||||
|
### Rationale
|
||||||
|
|
||||||
|
Home Manager uses the NixOS package set in this repository.
|
||||||
|
`pkgs.<name>` avoids dependency paths through the user tree under Home Manager.
|
||||||
|
Configured NixOS package options preserve package selection when a module owns the tool.
|
||||||
|
|||||||
Reference in New Issue
Block a user