refactor: change to litterate config
Configuration is now held by the `.org` files. All `.nix` files are tangled from the org-mode files.
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
#+title: Hyprland
|
||||
#+setupfile: ../headers
|
||||
#+property: header-args:conf :noweb yes :tangle no :exports code
|
||||
|
||||
* Hyprland
|
||||
[[https://hyprland.org/][Hyprland]] is my main Wayland compositor, dynamic tiling with nice
|
||||
animations out of the box. I used to keep the NixOS and home-manager
|
||||
halves of its config in separate files, but there was never a good
|
||||
reason for that, so they now both live here: one turns the program on
|
||||
system-wide, the other is where all of my actual config lives. Here’s
|
||||
the skeleton of both.
|
||||
#+begin_src nix :tangle yes
|
||||
{
|
||||
flake.modules = {
|
||||
nixos.hyprland = {
|
||||
<<nixos-config>>
|
||||
};
|
||||
homeManager.hyprland = {
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.home.desktop.hyprland;
|
||||
laptops = ["gampo"];
|
||||
caelestiaEnabled = config.home.desktop.caelestia.enable;
|
||||
in {
|
||||
<<hm-options>>
|
||||
config = {
|
||||
<<desktop-defaults>>
|
||||
<<blueman-applet>>
|
||||
<<hyprland-settings>>
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
#+end_src
|
||||
|
||||
** Turning Hyprland On
|
||||
On the NixOS side, there’s really nothing to it: just turn the program
|
||||
on. =withUWSM= hands session startup over to [[https://github.com/Vladimir-csp/uwsm][UWSM]] instead of
|
||||
Hyprland’s own half-baked systemd integration, which plays nicer with
|
||||
everything else expecting a proper systemd user session.
|
||||
#+name: nixos-config
|
||||
#+begin_src nix
|
||||
programs.hyprland = {
|
||||
enable = true;
|
||||
withUWSM = true;
|
||||
};
|
||||
#+end_src
|
||||
|
||||
** Declaring the Options
|
||||
=emacsPkg= exists so the rest of my config can reach whatever Emacs
|
||||
package I’m actually running, rather than assuming the default one.
|
||||
=host= is the more important one: it tells this module which machine
|
||||
it’s running on, which decides the monitor layout below and whether
|
||||
waybar bothers showing a battery indicator.
|
||||
#+name: hm-options
|
||||
#+begin_src nix
|
||||
options.home.desktop.hyprland = {
|
||||
emacsPkg = mkOption {
|
||||
type = types.package;
|
||||
default = config.home.dev.editors.emacs.package or pkgs.emacs;
|
||||
example = pkgs.emacs;
|
||||
};
|
||||
host = mkOption {
|
||||
type = types.enum ["gampo" "marpa"];
|
||||
description = ''
|
||||
Which host is Hyprland running on.
|
||||
|
||||
This helps determine the monitors layout and enable battery support in waybar.
|
||||
'';
|
||||
};
|
||||
};
|
||||
#+end_src
|
||||
|
||||
** Letting Caelestia Take Over
|
||||
These days I run [[https://github.com/caelestia-dots/shell][caelestia]] on top of Hyprland, and it already covers
|
||||
the wallpaper, a bar, a notification centre, and night-light handling
|
||||
on its own. So whenever caelestia is enabled, I don’t want my own
|
||||
=hyprpaper=, =waybar=, =swaync=, and =wlsunset= fighting it for the same
|
||||
job. They’re all =mkDefault=, though, so a host still running bare
|
||||
Hyprland without caelestia can turn them back on — =rofi= and the
|
||||
battery indicator on waybar are the exceptions, since I always want
|
||||
=rofi= around, and only =gampo= (my laptop) needs a battery in the bar.
|
||||
|
||||
=blueman-applet= is a bit different: caelestia doesn’t have its own
|
||||
Bluetooth applet, so that one is just a straight negation rather than
|
||||
a default, with no way for a host to override it short of changing
|
||||
this module.
|
||||
#+name: desktop-defaults
|
||||
#+begin_src nix
|
||||
home.desktop = {
|
||||
hyprpaper.enable = mkDefault (! caelestiaEnabled);
|
||||
rofi.enable = mkDefault true;
|
||||
swaync.enable = mkDefault (! caelestiaEnabled);
|
||||
waybar = {
|
||||
enable = mkDefault (! caelestiaEnabled);
|
||||
battery = mkDefault (builtins.elem cfg.host laptops);
|
||||
};
|
||||
wlsunset.enable = mkDefault true;
|
||||
};
|
||||
#+end_src
|
||||
#+name: blueman-applet
|
||||
#+begin_src nix
|
||||
services.blueman-applet.enable = ! caelestiaEnabled;
|
||||
#+end_src
|
||||
|
||||
** The Hyprland Config Itself
|
||||
Everything from here on is the actual =wayland.windowManager.hyprland=
|
||||
config.
|
||||
#+name: hyprland-settings
|
||||
#+begin_src nix
|
||||
wayland.windowManager.hyprland = {
|
||||
enable = true;
|
||||
systemd.enable = false;
|
||||
importantPrefixes = ["$left" "$right" "$up" "$down" "$menu"];
|
||||
settings = {
|
||||
<<env>>
|
||||
<<input>>
|
||||
<<device>>
|
||||
<<monitor>>
|
||||
<<general>>
|
||||
<<master>>
|
||||
<<workspace>>
|
||||
<<decoration>>
|
||||
<<animations>>
|
||||
dwindle.preserve_split = true;
|
||||
exec-once =
|
||||
[
|
||||
<<exec-once-apps>>
|
||||
]
|
||||
++ lib.lists.optional (! caelestiaEnabled) "${pkgs.networkmanagerapplet}/bin/nm-applet";
|
||||
};
|
||||
extraConfig = ''
|
||||
<<keybind-device>>
|
||||
|
||||
<<keybind-vars>>
|
||||
|
||||
<<keybind-launch>>
|
||||
|
||||
<<keybind-media>>
|
||||
|
||||
<<keybind-movement>>
|
||||
|
||||
<<keybind-mouse-actions>>
|
||||
|
||||
<<keybind-workspaces>>
|
||||
'';
|
||||
};
|
||||
#+end_src
|
||||
|
||||
*** Environment and Input
|
||||
=XMODIFIERS= points at fcitx so [[file:../i18n/input.org][my input methods]] actually work inside
|
||||
Hyprland, and =XCURSOR_SIZE= just keeps the cursor from being
|
||||
comically tiny on my monitors. My keyboard uses the AFNOR standard for
|
||||
the bépo layout, same as everywhere else, with a US layout kept around
|
||||
as a fallback I can switch to on demand. I also keep numlock on by
|
||||
default and disable touchpad natural scrolling, since I never got used
|
||||
to it.
|
||||
#+name: env
|
||||
#+begin_src nix
|
||||
env = [
|
||||
"XMODIFIERS,@im=fcitx"
|
||||
"XCURSOR_SIZE,12"
|
||||
];
|
||||
#+end_src
|
||||
#+name: input
|
||||
#+begin_src nix
|
||||
input = {
|
||||
kb_layout = "fr,us";
|
||||
kb_variant = "bepo_afnor,";
|
||||
numlock_by_default = true;
|
||||
follow_mouse = 1;
|
||||
touchpad.natural_scroll = false;
|
||||
sensitivity = "0";
|
||||
};
|
||||
#+end_src
|
||||
|
||||
On top of that, my [[file:../hardware/input/opentablet.org][graphics tablet]] gets its own sensitivity tuned down a
|
||||
bit, since the default is far too twitchy for actual drawing.
|
||||
#+name: device
|
||||
#+begin_src nix
|
||||
device = {
|
||||
name = "wacom-usb-bamboo-pad-finger";
|
||||
sensitivity = 0.5;
|
||||
};
|
||||
#+end_src
|
||||
|
||||
** Monitors
|
||||
Hyprland just ignores a monitor it can’t find, but between =marpa= and
|
||||
=gampo= I want genuinely different layouts rather than one config that
|
||||
happens to degrade gracefully, so this picks the right list by host
|
||||
name instead. On =marpa=, =DP-1= is my main ultrawide and =DP-2= sits
|
||||
to its left as a secondary vertical-ish screen; the commented-out line
|
||||
underneath is a lower resolution I switch to by hand when streaming,
|
||||
since the full resolution is more than my upload can comfortably push
|
||||
through. =gampo= is just its own laptop panel, so there’s nothing to
|
||||
list — Hyprland figures that one out on its own.
|
||||
#+name: monitor
|
||||
#+begin_src nix
|
||||
monitor =
|
||||
{
|
||||
"marpa" = [
|
||||
"DP-1, 3440x1440@144, 1080x550, 1"
|
||||
# "DP-1, 2560x1440@144, 1080x550, 1" # streaming
|
||||
"DP-2, 2560x1080@60, 0x0, 1, transform, 1"
|
||||
];
|
||||
"gampo" = [];
|
||||
}."${cfg.host}";
|
||||
#+end_src
|
||||
|
||||
** Look and Feel
|
||||
I like my gaps, and keeping windows relatively tight together but with
|
||||
a generous gap around the whole screen looks nice to me. The border
|
||||
colours come from the Nord palette again, nord9 fading into nord14 for
|
||||
the active window and a plain nord3 for inactive ones.
|
||||
#+name: general
|
||||
#+begin_src nix
|
||||
general = {
|
||||
gaps_in = 5;
|
||||
gaps_out = 20;
|
||||
border_size = 2;
|
||||
"col.active_border" = "rgb(81a1c1) rgb(a3be8c) 45deg";
|
||||
"col.inactive_border" = "rgb(4c566a)";
|
||||
layout = "master";
|
||||
};
|
||||
#+end_src
|
||||
|
||||
I switched over to the =master= layout, centred, with new windows
|
||||
inheriting whatever status the window they split off from had.
|
||||
#+name: master
|
||||
#+begin_src nix
|
||||
master = {
|
||||
orientation = "center";
|
||||
new_status = "inherit";
|
||||
};
|
||||
#+end_src
|
||||
|
||||
Workspaces 1, 9 and 10 get their master area pinned to the bottom
|
||||
instead of the side, which just suits how I actually use them better.
|
||||
#+name: workspace
|
||||
#+begin_src nix
|
||||
workspace = [
|
||||
"1, layoutopt:orientation:bottom"
|
||||
"9, layoutopt:orientation:bottom"
|
||||
"10, layoutopt:orientation:bottom"
|
||||
];
|
||||
#+end_src
|
||||
|
||||
Decoration-wise I like a fairly generous rounding on my windows, no
|
||||
shadow or blur needed.
|
||||
#+name: decoration
|
||||
#+begin_src nix
|
||||
decoration = {
|
||||
rounding = 20;
|
||||
};
|
||||
#+end_src
|
||||
|
||||
Animations are fun, and I’ve mostly kept close to Hyprland’s own
|
||||
example config here, minus the window-open animation, which I never
|
||||
liked as much as everything else.
|
||||
#+name: animations
|
||||
#+begin_src nix
|
||||
animations = {
|
||||
enabled = true;
|
||||
animation = [
|
||||
# "windows, 1, 7, myBezier"
|
||||
"windowsOut, 1, 7, default, popin 80%"
|
||||
"border, 1, 10, default"
|
||||
"borderangle, 1, 8, default"
|
||||
"fade, 1, 7, default"
|
||||
"workspaces, 1, 6, default"
|
||||
];
|
||||
};
|
||||
#+end_src
|
||||
|
||||
** Autolaunch of Programs
|
||||
=module-switch-on-connect= means Pipewire hops to a new audio device
|
||||
the moment it’s plugged in, and I stop =mpc= right away just in case
|
||||
something was already playing. The Polkit agent needs to be running
|
||||
for a lot of GUI programs to be able to ask for elevated privileges at
|
||||
all, and =mozc_server=/=fcitx5= are the input method daemons behind [[file:../i18n/input.org][my
|
||||
input method setup]]. =nm-applet= only joins the party when caelestia
|
||||
isn’t around to show network status itself.
|
||||
#+name: exec-once-apps
|
||||
#+begin_src nix
|
||||
"pactl load-module module-switch-on-connect"
|
||||
"${pkgs.mpc}/bin/mpc stop"
|
||||
"${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1"
|
||||
"${pkgs.mozc}/lib/mozc/mozc_server"
|
||||
"${pkgs.fcitx5}/bin/fcitx5 -d"
|
||||
#+end_src
|
||||
|
||||
** Keybindings
|
||||
The rest of the config is plain Hyprland syntax, so it lives in
|
||||
=extraConfig= rather than the =settings= attrset. I don’t bother with
|
||||
submaps here, unlike my old standalone config — everything’s a direct
|
||||
=SUPER=-something bind.
|
||||
|
||||
First, the tablet gets a second, more specific declaration here with
|
||||
an adaptive acceleration profile, since that bit isn’t exposed through
|
||||
the =settings= attrset the way the rest of =device= is. Then come my
|
||||
usual =$left=/=$right=/=$up=/=$down= variables — remember that =hjkl=
|
||||
becomes =ctsr= on a bépo keyboard — and =$menu= for my combi launcher.
|
||||
#+name: keybind-device
|
||||
#+begin_src conf
|
||||
device {
|
||||
name = wacom-usb-bamboo-pad-finger
|
||||
sensitivity = 0.5
|
||||
accel_profile = adaptive
|
||||
}
|
||||
#+end_src
|
||||
#+name: keybind-vars
|
||||
#+begin_src conf
|
||||
$left = c
|
||||
$right = r
|
||||
$up = s
|
||||
$down = t
|
||||
$menu = rofi -combi-modi drun,calc -show combi
|
||||
#+end_src
|
||||
|
||||
A terminal and my [[file:../home/desktop/wlr-which-key.org][wlr-which-key]] menu (bound to both =SUPER+Space= and
|
||||
=Print=, so I can reach it either way) are the only two things I
|
||||
actually launch by hand.
|
||||
#+name: keybind-launch
|
||||
#+begin_src conf :noweb no
|
||||
bind = SUPER, Return, exec, ${pkgs.kitty}/bin/kitty ${pkgs.tmux}/bin/tmux
|
||||
bind = SUPER, Space, exec, ${pkgs.wlr-which-key}/bin/wlr-which-key
|
||||
bind = , Print, exec, ${pkgs.wlr-which-key}/bin/wlr-which-key -k s
|
||||
#+end_src
|
||||
|
||||
The media keys are the same as always: playback and volume through
|
||||
=playerctl=/=pamixer=, and brightness through =xbacklight= — including
|
||||
the keyboard backlight keys, which just reuse the same command since
|
||||
my keyboard doesn’t expose a separate control for it.
|
||||
#+name: keybind-media
|
||||
#+begin_src conf
|
||||
bindl = , XF86AudioPlay, exec, playerctl play-pause
|
||||
bindl = , XF86AudioPause, exec, playerctl pause
|
||||
bindl = , XF86AudioStop, exec, playerctl stop
|
||||
bindl = , XF86AudioPrev, exec, playerctl previous
|
||||
bindl = , XF86AudioNext, exec, playerctl next
|
||||
bindl = , XF86AudioForward, exec, playerctl position +1
|
||||
bindl = , XF86AudioRewind, exec, playerctl position -1
|
||||
bindl = , XF86AudioRaiseVolume, exec, pamixer -i 2
|
||||
bindl = , XF86AudioLowerVolume, exec, pamixer -d 2
|
||||
bindl = , XF86MonBrightnessUp, exec, xbacklight -perceived -inc 2
|
||||
bindl = , XF86MonBrightnessDown, exec, xbacklight -perceived -dec 2
|
||||
bindl = , XF86KbdBrightnessUp, exec, xbacklight -perceived -inc 2
|
||||
bindl = , XF86KbdBrightnessDown, exec, xbacklight -perceived -dec 2
|
||||
bind = SUPER, a, exec, hyprctl switchxkblayout glove80-keyboard next
|
||||
#+end_src
|
||||
|
||||
Window focus and movement all go through the =$left=/=$right=/=$up=/=$down=
|
||||
variables above, plus the usual =Tab= cycling and mouse-driven move/resize.
|
||||
#+name: keybind-movement
|
||||
#+begin_src conf
|
||||
bind = SUPER, $left, movefocus, l
|
||||
bind = SUPER, $right, movefocus, r
|
||||
bind = SUPER, $up, movefocus, u
|
||||
bind = SUPER, $down, movefocus, d
|
||||
bind = SUPER_SHIFT, $left, movewindow, l
|
||||
bind = SUPER_SHIFT, $right, movewindow, r
|
||||
bind = SUPER_SHIFT, $up, movewindow, u
|
||||
bind = SUPER_SHIFT, $down, movewindow, d
|
||||
bind = SUPER_CTRL_SHIFT, $left, moveworkspacetomonitor, e+0 +1
|
||||
bind = SUPER_CTRL_SHIFT, $right, moveworkspacetomonitor, e+0 -1
|
||||
bind = SUPER, Tab, cyclenext,
|
||||
bind = SUPER_SHIFT, Tab, cyclenext, prev
|
||||
#+end_src
|
||||
#+name: keybind-mouse-actions
|
||||
#+begin_src conf
|
||||
bindm = SUPER, mouse:272, movewindow
|
||||
bindm = SUPER, mouse:273, resizewindow
|
||||
#+end_src
|
||||
|
||||
Workspace switching is where the bépo layout bites back: the number
|
||||
row gives me punctuation, not digits, so the binds below go by key
|
||||
name instead. As a reminder, here’s what each one actually is and
|
||||
which digit sits on it under Shift:
|
||||
| Character name | Character | Number |
|
||||
|----------------+-----------+--------|
|
||||
| quotedbl | ="= | 1 |
|
||||
| guillemotleft | =«= | 2 |
|
||||
| guillemotright | =»= | 3 |
|
||||
| parenleft | =(= | 4 |
|
||||
| parenright | =)= | 5 |
|
||||
| at | =@= | 6 |
|
||||
| plus | =+= | 7 |
|
||||
| minus | =-= | 8 |
|
||||
| slash | =/= | 9 |
|
||||
| asterisk | =*= | 0 |
|
||||
#+name: keybind-workspaces
|
||||
#+begin_src conf
|
||||
bind = SUPER, quotedbl, workspace, 1
|
||||
bind = SUPER, guillemotleft, workspace, 2
|
||||
bind = SUPER, guillemotright, workspace, 3
|
||||
bind = SUPER, parenleft, workspace, 4
|
||||
bind = SUPER, parenright, workspace, 5
|
||||
bind = SUPER, at, workspace, 6
|
||||
bind = SUPER, plus, workspace, 7
|
||||
bind = SUPER, minus, workspace, 8
|
||||
bind = SUPER, slash, workspace, 9
|
||||
bind = SUPER, asterisk, workspace, 10
|
||||
bind = SUPER, mouse_down, workspace, e+1
|
||||
bind = SUPER, mouse_up, workspace, e-1
|
||||
bind = SUPER_SHIFT, quotedbl, movetoworkspace, 1
|
||||
bind = SUPER_SHIFT, guillemotleft, movetoworkspace, 2
|
||||
bind = SUPER_SHIFT, guillemotright, movetoworkspace, 3
|
||||
bind = SUPER_SHIFT, parenleft, movetoworkspace, 4
|
||||
bind = SUPER_SHIFT, parenright, movetoworkspace, 5
|
||||
bind = SUPER_SHIFT, at, movetoworkspace, 6
|
||||
bind = SUPER_SHIFT, plus, movetoworkspace, 7
|
||||
bind = SUPER_SHIFT, minus, movetoworkspace, 8
|
||||
bind = SUPER_SHIFT, slash, movetoworkspace, 9
|
||||
bind = SUPER_SHIFT, asterisk, movetoworkspace, 10
|
||||
#+end_src
|
||||
Reference in New Issue
Block a user