cleanup WM setup

This commit is contained in:
Henry Hiles 2026-10-21 00:30:19 -04:00
commit e9898b83cc
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
9 changed files with 447 additions and 398 deletions

View file

@ -1,398 +0,0 @@
{
inputs,
config,
pkgs,
lib,
...
}:
let
filename = "fht-compositor.toml";
session-programs = pkgs.writeShellApplication {
name = "session-programs";
runtimeInputs = with pkgs; [
procps
util-linux
];
text = ''
restart() {
pattern=$1
shift
pkill -f "$pattern" || true
for _ in $(seq 50); do
pgrep -f "$pattern" >/dev/null || break
sleep 0.1
done
setsid "$@" >/dev/null 2>&1 </dev/null &
}
restart '/bin/elephant$' systemd-cat -t elephant ${lib.getExe pkgs.elephant}
restart 'walker.* --gapplication-service' walker --gapplication-service
restart 'gaypanel-wrapped_' systemd-cat -t gaypanel gaypanel
restart 'bin/swaybg ' ${lib.getExe pkgs.swaybg} -i ${../../../background.jpg} -m fill
restart 'bin/swayosd-server ' ${lib.getExe' pkgs.swayosd "swayosd-server"} --style ${pkgs.writeText "swayosd.css" ''
@import url("file://${pkgs.swayosd}/etc/xdg/swayosd/style.css");
window#osd {
background: shade(@theme_bg_color, 1.15);
}
''}
restart 'bin/swaync ' ${lib.getExe' pkgs.swaynotificationcenter "swaync"} --style ${pkgs.writeText "swaync.css" ''
@import url("file://${pkgs.swaynotificationcenter}/etc/xdg/swaync/style.css");
:root {
--noti-bg-focus: transparent;
--noti-border-color: transparent;
--notification-shadow: none;
--border-radius: 16px;
--notification-icon-size: 48px;
--font-size-summary: 14px;
--font-size-body: 14px;
}
.control-center {
background: shade(@theme_bg_color, 1.1);
}
.notification-row .notification-background .notification,
.notification-group.collapsed .notification-row .notification {
background: shade(@theme_bg_color, 1.25);
}
.notification-row .notification-background .notification .notification-default-action:hover,
.control-center .control-center-list .notification .notification-default-action:hover,
.notification-group.collapsed:hover .notification-row:not(:only-child) .notification {
background: shade(@theme_bg_color, 1.4);
}
.control-center .control-center-list-placeholder {
margin-bottom: 24px;
}
.notification-row .notification-background .notification .notification-default-action .notification-content {
padding: 6px;
}
.notification-row .notification-background .notification .notification-default-action .notification-content .text-box .time {
font-size: 12px;
font-weight: normal;
opacity: 0.6;
}
.notification-row .notification-background .notification .notification-default-action .notification-content .text-box .body {
opacity: 0.8;
}
''} --config ${
(pkgs.formats.json { }).generate "swaync.json" (
let
default = lib.importJSON "${pkgs.swaynotificationcenter}/etc/xdg/swaync/config.json";
in
default
// lib.genAttrs (map (side: "control-center-margin-${side}") [
"top"
"bottom"
"right"
]) (_: 8)
// {
notification-window-width = 420;
ignore-gtk-theme = false;
fit-to-screen = false;
control-center-height = -1;
widget-config = default.widget-config // {
notifications = default.widget-config.notifications or { } // {
vexpand = false;
};
};
}
)
}
# Only locks before suspending, with hyprlock
restart 'bin/hypridle ' ${lib.getExe pkgs.hypridle} --config ${pkgs.writeText "hypridle.conf" ''
general {
lock_cmd = pidof hyprlock || ${lib.getExe pkgs.hyprlock} --config ${
let
inherit (config.lib.stylix) colors;
in
pkgs.writeText "hyprlock.conf" ''
background {
path = screenshot
blur_passes = 3
}
label {
text = $TIME
font_size = 96
position = 0, 200
}
label {
text = cmd[update:60000] date +"%A, %B %-d"
font_size = 20
position = 0, 110
}
input-field {
size = 300, 50
outline_thickness = 0
inner_color = rgb(${colors.base01})
font_color = rgb(${colors.base05})
check_color = rgb(${colors.base0D})
fade_on_empty = false
}
''
}
before_sleep_cmd = loginctl lock-session
}
''}
'';
};
in
{
imports = [ inputs.fht-compositor.nixosModules.default ];
# Tapping the power button suspends (which locks the screen, see hypridle above)
services.logind.settings.Login.HandlePowerKey = "suspend";
# Prefer the GNOME portal (e.g. Nautilus as the file picker), falling back to GTK. This replaces
# the portal config fht-compositor ships.
xdg.portal.config.fht-compositor = {
default = [
"gnome"
"gtk"
];
# GNOME's sends them to GNOME Shell, GTK's to any notification daemon
"org.freedesktop.impl.portal.Notification" = [ "gtk" ];
# GNOME's needs GNOME Shell, and GTK doesn't have one
"org.freedesktop.impl.portal.ScreenCast" = [ "fht-compositor" ];
};
environment = {
systemPackages = [ session-programs ];
etc = {
"xdg/walker/config.toml".source = (pkgs.formats.toml { }).generate "walker.toml" {
# Take keyboard focus when opened, so you can type into the search straight away
force_keyboard_focus = true;
theme = "system";
};
# The default theme, minus its hardcoded @define-color palette, so the GTK theme's colors show through
"xdg/walker/themes/system/style.css".source = pkgs.runCommand "walker-system-style.css" { } ''
sed '/^@define-color/d' ${pkgs.walker.src}/resources/themes/default/style.css > $out
'';
# Only load the providers Walker uses
"xdg/elephant/elephant.toml".source = (pkgs.formats.toml { }).generate "elephant.toml" {
terminal_cmd = "ptyxis -x";
ignored_providers = [
"1password"
"aptpackages"
"archlinuxpkgs"
"bitwarden"
"bluetooth"
"bookmarks"
"clipboard"
"dnfpackages"
"files"
"menus"
"niriactions"
"nirisessions"
"playerctl"
"protonpass"
"providerlist"
"runner"
"snippets"
"symbols"
"todo"
"unicode"
"windows"
"wireplumber"
];
};
# Use the same search engines as LibreWolf, with its aliases as prefixes (e.g. "@no nix")
"xdg/elephant/websearch.toml".source =
let
inherit (config.programs.firefox.policies) SearchEngines;
in
(pkgs.formats.toml { }).generate "websearch.toml" {
entries = map (
engine:
{
name = engine.Name;
url = lib.replaceStrings [ "{searchTerms}" ] [ "%TERM%" ] engine.URLTemplate;
default = engine.Name == SearchEngines.Default;
}
// lib.optionalAttrs (engine ? Alias) { prefix = "${engine.Alias} "; }
) SearchEngines.Add;
};
# FHT config
${filename}.source = (pkgs.formats.toml { }).generate "config.toml" {
autostart = [ "session-programs" ];
general = {
focus-follows-mouse = true;
inner-gaps = 10;
};
decorations = {
border = {
thickness = 4;
radius = 12;
};
blur.disable = true;
};
keybinds = {
Alt-Tab = "focus-next-window";
Super-q = "close-focused-window";
Super-f = "fullscreen-focused-window";
Super-Ctrl-q = "quit";
Super-Ctrl-r = {
action = "run-command";
arg = "fht-compositor ipc action reload-config; session-programs";
};
# Runs when Super is tapped on its own (patched in, see lone-modifier.patch)
Super_L = {
action = "run-command";
arg = "walker";
};
Print = {
action = "run-command";
arg = "${lib.getExe pkgs.grim} -g \"$(${lib.getExe pkgs.slurp})\" - | gradia";
};
Super-t = {
action = "run-command";
arg = "ptyxis --new-window";
};
Super-e = {
action = "run-command";
arg = "nautilus";
};
Super-c = {
action = "run-command";
arg = "codium";
};
Super-b = {
action = "run-command";
arg = "librewolf";
};
Super-Down = {
action = "change-window-proportion";
arg = 0.5;
};
Super-Up = {
action = "change-window-proportion";
arg = -0.5;
};
Super-Left = {
action = "change-mwfact";
arg = -0.1;
};
Super-Right = {
action = "change-mwfact";
arg = 0.1;
};
}
// builtins.listToAttrs (
builtins.genList (i: {
name = "Super-${toString (i + 1)}";
value = {
action = "focus-workspace";
arg = i;
};
}) 9
)
# Media keys, with a SwayOSD popup
//
lib.mapAttrs
(_: args: {
action = "run-command";
arg = "${lib.getExe' pkgs.swayosd "swayosd-client"} ${args}";
# Holding raise/lower keeps changing the volume, but holding mute shouldn't keep toggling
repeat = !lib.hasSuffix "mute-toggle" args;
})
{
XF86AudioRaiseVolume = "--output-volume raise";
XF86AudioLowerVolume = "--output-volume lower";
XF86AudioMute = "--output-volume mute-toggle";
XF86AudioMicMute = "--input-volume mute-toggle";
};
mousebinds = {
Super-Left = "swap-tile";
# Works on tiled windows too (patched in, see resize-tiled-windows.patch)
Super-Right = "resize-tile";
Super-wheeldown = "focus-previous-workspace";
Super-wheelup = "focus-next-workspace";
};
input.touchpad = {
natural-scrolling = true;
click-method = "clickfinger";
};
gesturebinds = {
focus-next-workspace = {
fingers = 3;
direction = "left";
min-swipe-distance = 1;
};
focus-previous-workspace = {
fingers = 3;
direction = "right";
min-swipe-distance = 1;
};
};
rules = [
{
match-app-id = [ ".*" ];
floating = false;
}
];
outputs = {
HDMI-A-1.position.x = 0;
DP-3.position.x = 1920;
};
cursor = {
name = "GoogleDot-Blue";
size = 24;
};
};
};
};
programs = {
hyprlock.enable = true;
fht-compositor = {
enable = true;
package =
inputs.fht-compositor.packages.${pkgs.stdenv.hostPlatform.system}.default.overrideAttrs
(old: {
patches = (old.patches or [ ]) ++ [
./resize-tiled-windows.patch
./lone-modifier.patch
];
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ pkgs.makeWrapper ];
postInstall = (old.postInstall or "") + ''
wrapProgram $out/bin/fht-compositor \
--add-flags "-c /etc/${filename}"
'';
});
};
};
}

View file

@ -0,0 +1,215 @@
{
inputs,
config,
pkgs,
lib,
...
}:
let
cfg = config.programs.fht-compositor;
filename = "fht-compositor.toml";
format = pkgs.formats.toml { };
# (Re)starts the programs that run alongside the compositor. Run at login, and on Super+Ctrl+R
# so they pick up rebuilt versions and config. Each runs as a systemd user unit named
# session-<name>, which is stopped (waiting for it to exit, since single-instance apps would
# otherwise hand off to the old one) and started again. Logs: journalctl --user -u session-<name>
session-programs = pkgs.writeShellApplication {
name = "session-programs";
runtimeInputs = [ pkgs.dbus ];
text = ''
# Give the units this session's environment (display, desktop name, etc.)
dbus-update-activation-environment --systemd --all
${lib.concatLines (
lib.mapAttrsToList (name: command: ''
systemctl --user stop session-${name} 2>/dev/null || true
systemd-run --user --unit=session-${name} --collect --quiet -- sh -c ${
lib.escapeShellArg "exec ${command}"
}
'') cfg.sessionPrograms
)}
'';
};
in
{
imports = [ inputs.fht-compositor.nixosModules.default ];
options.programs.fht-compositor = {
settings = lib.mkOption {
inherit (format) type;
default = { };
description = "fht-compositor's config, written to /etc/${filename}.";
};
sessionPrograms = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = "Commands started at login and restarted on Super+Ctrl+R, as systemd user units named session-<name>.";
};
};
config = {
# Prefer the GNOME portal (e.g. Nautilus as the file picker), falling back to GTK. This replaces
# the portal config fht-compositor ships.
xdg.portal.config.fht-compositor = {
default = [
"gnome"
"gtk"
];
# GNOME's sends them to GNOME Shell, GTK's to any notification daemon
"org.freedesktop.impl.portal.Notification" = [ "gtk" ];
# GNOME's needs GNOME Shell, and GTK doesn't have one
"org.freedesktop.impl.portal.ScreenCast" = [ "fht-compositor" ];
};
environment = {
# Called by name (rather than by store path), so Super+Ctrl+R after a rebuild runs the new version
systemPackages = [ session-programs ];
etc.${filename}.source = format.generate "config.toml" cfg.settings;
};
programs.fht-compositor = {
enable = true;
package =
inputs.fht-compositor.packages.${pkgs.stdenv.hostPlatform.system}.default.overrideAttrs
(old: {
patches = (old.patches or [ ]) ++ [
./resize-tiled-windows.patch
./lone-modifier.patch
];
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ pkgs.makeWrapper ];
postInstall = (old.postInstall or "") + ''
wrapProgram $out/bin/fht-compositor \
--add-flags "-c /etc/${filename}"
'';
});
sessionPrograms.wallpaper = "${lib.getExe pkgs.swaybg} -i ${../../../../background.jpg} -m fill";
settings = {
autostart = [ "session-programs" ];
general = {
focus-follows-mouse = true;
inner-gaps = 10;
};
decorations = {
border = {
thickness = 4;
radius = 12;
};
blur.disable = true;
};
keybinds = {
Alt-Tab = "focus-next-window";
Super-q = "close-focused-window";
Super-f = "fullscreen-focused-window";
Super-Ctrl-q = "quit";
Super-Ctrl-r = {
action = "run-command";
arg = "fht-compositor ipc action reload-config; session-programs";
};
Print = {
action = "run-command";
arg = "${lib.getExe pkgs.grim} -g \"$(${lib.getExe pkgs.slurp})\" - | gradia";
};
Super-t = {
action = "run-command";
arg = "ptyxis --new-window";
};
Super-e = {
action = "run-command";
arg = "nautilus";
};
Super-c = {
action = "run-command";
arg = "codium";
};
Super-b = {
action = "run-command";
arg = "librewolf";
};
Super-Down = {
action = "change-window-proportion";
arg = 0.5;
};
Super-Up = {
action = "change-window-proportion";
arg = -0.5;
};
Super-Left = {
action = "change-mwfact";
arg = -0.1;
};
Super-Right = {
action = "change-mwfact";
arg = 0.1;
};
}
// builtins.listToAttrs (
builtins.genList (i: {
name = "Super-${toString (i + 1)}";
value = {
action = "focus-workspace";
arg = i;
};
}) 9
);
mousebinds = {
Super-Left = "swap-tile";
# Works on tiled windows too (patched in, see resize-tiled-windows.patch)
Super-Right = "resize-tile";
Super-wheeldown = "focus-previous-workspace";
Super-wheelup = "focus-next-workspace";
};
input.touchpad = {
natural-scrolling = true;
click-method = "clickfinger";
};
gesturebinds = {
focus-next-workspace = {
fingers = 3;
direction = "left";
min-swipe-distance = 1;
};
focus-previous-workspace = {
fingers = 3;
direction = "right";
min-swipe-distance = 1;
};
};
rules = [
{
match-app-id = [ ".*" ];
floating = false;
}
];
outputs = {
HDMI-A-1.position.x = 0;
DP-3.position.x = 1920;
};
cursor = {
name = "GoogleDot-Blue";
size = 24;
};
};
};
};
}

View file

@ -0,0 +1,74 @@
{
config,
pkgs,
lib,
...
}:
{
environment.etc = {
"xdg/walker/config.toml".source = (pkgs.formats.toml { }).generate "walker.toml" {
force_keyboard_focus = true;
theme = "system";
};
"xdg/walker/themes/system/style.css".source = pkgs.runCommand "walker-system-style.css" { } ''
sed '/^@define-color/d' ${pkgs.walker.src}/resources/themes/default/style.css > $out
'';
"xdg/elephant/elephant.toml".source = (pkgs.formats.toml { }).generate "elephant.toml" {
terminal_cmd = "ptyxis -x";
ignored_providers = [
"1password"
"aptpackages"
"archlinuxpkgs"
"bitwarden"
"bluetooth"
"bookmarks"
"clipboard"
"dnfpackages"
"files"
"menus"
"niriactions"
"nirisessions"
"playerctl"
"protonpass"
"providerlist"
"runner"
"snippets"
"symbols"
"todo"
"unicode"
"windows"
"wireplumber"
];
};
"xdg/elephant/websearch.toml".source =
let
inherit (config.programs.firefox.policies) SearchEngines;
in
(pkgs.formats.toml { }).generate "websearch.toml" {
entries = map (
engine:
{
name = engine.Name;
url = lib.replaceStrings [ "{searchTerms}" ] [ "%TERM%" ] engine.URLTemplate;
default = engine.Name == SearchEngines.Default;
}
// lib.optionalAttrs (engine ? Alias) { prefix = "${engine.Alias} "; }
) SearchEngines.Add;
};
};
programs.fht-compositor = {
sessionPrograms = {
elephant = lib.getExe pkgs.elephant;
walker = "walker --gapplication-service";
};
settings.keybinds.Super_L = {
action = "run-command";
arg = "walker";
};
};
}

View file

@ -0,0 +1,52 @@
{
config,
pkgs,
lib,
...
}:
{
services.logind.settings.Login.HandlePowerKey = "suspend";
programs = {
hyprlock.enable = true;
fht-compositor.sessionPrograms.hypridle =
"${lib.getExe pkgs.hypridle} --config ${pkgs.writeText "hypridle.conf" ''
general {
lock_cmd = pidof hyprlock || ${lib.getExe pkgs.hyprlock} --config ${
let
inherit (config.lib.stylix) colors;
in
pkgs.writeText "hyprlock.conf" ''
background {
path = screenshot
blur_passes = 3
}
label {
text = $TIME
font_size = 96
position = 0, 200
}
label {
text = cmd[update:60000] date +"%A, %B %-d"
font_size = 20
position = 0, 110
}
input-field {
size = 300, 50
outline_thickness = 0
inner_color = rgb(${colors.base01})
font_color = rgb(${colors.base05})
check_color = rgb(${colors.base0D})
fade_on_empty = false
}
''
}
before_sleep_cmd = loginctl lock-session
}
''}";
};
}

View file

@ -0,0 +1,76 @@
{ pkgs, lib, ... }:
let
swaync = pkgs.swaynotificationcenter;
in
{
programs.fht-compositor.sessionPrograms.swaync =
"${lib.getExe' swaync "swaync"} --style ${pkgs.writeText "swaync.css" ''
@import url("file://${swaync}/etc/xdg/swaync/style.css");
:root {
--noti-bg-focus: transparent;
--noti-border-color: transparent;
--notification-shadow: none;
--border-radius: 16px;
--notification-icon-size: 48px;
--font-size-summary: 14px;
--font-size-body: 14px;
}
.control-center {
background: shade(@theme_bg_color, 1.1);
}
.notification-row .notification-background .notification,
.notification-group.collapsed .notification-row .notification {
background: shade(@theme_bg_color, 1.25);
}
.notification-row .notification-background .notification .notification-default-action:hover,
.control-center .control-center-list .notification .notification-default-action:hover,
.notification-group.collapsed:hover .notification-row:not(:only-child) .notification {
background: shade(@theme_bg_color, 1.4);
}
.control-center .control-center-list-placeholder {
margin-bottom: 24px;
}
.notification-row .notification-background .notification .notification-default-action .notification-content {
padding: 6px;
}
.notification-row .notification-background .notification .notification-default-action .notification-content .text-box .time {
font-size: 12px;
font-weight: normal;
opacity: 0.6;
}
.notification-row .notification-background .notification .notification-default-action .notification-content .text-box .body {
opacity: 0.8;
}
''} --config ${
(pkgs.formats.json { }).generate "swaync.json" (
let
default = lib.importJSON "${swaync}/etc/xdg/swaync/config.json";
in
default
// lib.genAttrs (map (side: "control-center-margin-${side}") [
"top"
"bottom"
"right"
]) (_: 8)
// {
notification-window-width = 420;
ignore-gtk-theme = false;
fit-to-screen = false;
control-center-height = -1;
widget-config = default.widget-config // {
notifications = default.widget-config.notifications or { } // {
vexpand = false;
};
};
}
)
}";
}

View file

@ -0,0 +1,26 @@
{ pkgs, lib, ... }:
{
programs.fht-compositor = {
sessionPrograms.swayosd = "${lib.getExe' pkgs.swayosd "swayosd-server"} --style ${pkgs.writeText "swayosd.css" ''
@import url("file://${pkgs.swayosd}/etc/xdg/swayosd/style.css");
window#osd {
background: shade(@theme_bg_color, 1.15);
}
''}";
settings.keybinds =
lib.mapAttrs
(_: args: {
action = "run-command";
arg = "${lib.getExe' pkgs.swayosd "swayosd-client"} ${args}";
repeat = true;
})
{
XF86AudioRaiseVolume = "--output-volume raise";
XF86AudioLowerVolume = "--output-volume lower";
XF86AudioMute = "--output-volume mute-toggle";
XF86AudioMicMute = "--input-volume mute-toggle";
};
};
}

View file

@ -0,0 +1,4 @@
{
# gaypanel itself is configured in wrappers/desktop/gaypanel
programs.fht-compositor.sessionPrograms.gaypanel = "gaypanel";
}