feat(keyboard): per-device layout for external keyboards
Add nomarchy.keyboard.devices ({ "<hyprctl-device-name>" = { layout; variant; }; })
generating Hyprland `device` blocks that override the session
nomarchy.keyboard.layout for a named keyboard -- e.g. an external board
that's physically a different layout than the laptop's built-in one.
Hyprland applies it whenever that device connects, so the external keyboard
"remembers" its layout the declarative way. A Waybar hyprland/language
indicator shows the active layout, placed only when more than one layout is
in play (a comma in the session layout, or any per-device override) so
single-layout bars stay clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -188,6 +188,7 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
|||||||
| `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` |
|
| `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` |
|
||||||
| `nomarchy.keyboard.layout` | `"us"` | XKB layout for the Hyprland session (installer writes the matching `services.xserver.xkb` in system.nix; the console + LUKS prompt follow via the distro default) |
|
| `nomarchy.keyboard.layout` | `"us"` | XKB layout for the Hyprland session (installer writes the matching `services.xserver.xkb` in system.nix; the console + LUKS prompt follow via the distro default) |
|
||||||
| `nomarchy.keyboard.variant` | `""` | XKB variant for the session |
|
| `nomarchy.keyboard.variant` | `""` | XKB variant for the session |
|
||||||
|
| `nomarchy.keyboard.devices` | `{}` | Per-device layout overrides (Hyprland `device` blocks keyed by `hyprctl devices` name) — e.g. an external keyboard with its own layout/variant |
|
||||||
| `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config |
|
| `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config |
|
||||||
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
|
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
|
||||||
| `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher |
|
| `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher |
|
||||||
|
|||||||
@@ -283,11 +283,21 @@ how to override it. Items marked ✓ are shipped.
|
|||||||
- **Night light / blue-light filter:** schedulable colour-temperature shift
|
- **Night light / blue-light filter:** schedulable colour-temperature shift
|
||||||
via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) —
|
via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) —
|
||||||
sunset/sunrise or a fixed schedule, with a menu + Waybar toggle.
|
sunset/sunrise or a fixed schedule, with a menu + Waybar toggle.
|
||||||
- **Runtime keyboard-layout switching:** the session layout is a single
|
- **Keyboard layouts (per-device + switching):**
|
||||||
`nomarchy.keyboard.layout` today. Support a list of layouts with a toggle
|
- ✓ **Per-device declarative layout:** `nomarchy.keyboard.devices`
|
||||||
bind (`hyprctl switchxkblayout` / xkb `grp:` options) and a Waybar
|
(`{ "<hyprctl-device-name>" = { layout; variant; }; }`) generates Hyprland
|
||||||
indicator for the active layout. Natural follow-on to the LUKS-keymap work
|
`device` blocks that override the session `nomarchy.keyboard.layout` for a
|
||||||
— keep the system (console/initrd) and session layouts in sync.
|
named keyboard — e.g. an external board that's physically a different
|
||||||
|
layout than the laptop's. Hyprland applies it whenever that device
|
||||||
|
connects (the "external keyboard remembers its layout" case, done the
|
||||||
|
declarative way). A Waybar `hyprland/language` indicator shows the active
|
||||||
|
layout, gated on more than one layout being in play (comma in the session
|
||||||
|
layout, or any per-device override) so single-layout bars stay clean.
|
||||||
|
- Remaining: a multi-layout cycle bind (`hyprctl switchxkblayout` / xkb
|
||||||
|
`grp:` options) for switching layouts on one keyboard; add the
|
||||||
|
`hyprland/language` module to the summer whole-swap themes for parity
|
||||||
|
(the gating doesn't translate to their static JSON). Keep the system
|
||||||
|
(console/initrd) and session layouts in sync (the LUKS-keymap work).
|
||||||
- ✓ **Do-Not-Disturb:** swaync DND toggle wired into the menu
|
- ✓ **Do-Not-Disturb:** swaync DND toggle wired into the menu
|
||||||
(`nomarchy-menu dnd`, in the picker, SUPER+CTRL+D) and a Waybar bell
|
(`nomarchy-menu dnd`, in the picker, SUPER+CTRL+D) and a Waybar bell
|
||||||
indicator (`custom/notification` via `swaync-client -swb`: shows the
|
indicator (`custom/notification` via `swaync-client -swb`: shows the
|
||||||
|
|||||||
@@ -150,6 +150,14 @@ in
|
|||||||
touchpad.natural_scroll = lib.mkDefault true;
|
touchpad.natural_scroll = lib.mkDefault true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Per-device keyboard layouts (nomarchy.keyboard.devices): each
|
||||||
|
# overrides the session kb_layout for one named keyboard — e.g. an
|
||||||
|
# external board that's physically a different layout than the laptop's
|
||||||
|
# built-in one. Hyprland applies them whenever the device connects.
|
||||||
|
device = lib.mapAttrsToList
|
||||||
|
(name: d: { inherit name; kb_layout = d.layout; kb_variant = d.variant; })
|
||||||
|
config.nomarchy.keyboard.devices;
|
||||||
|
|
||||||
# dwindle:pseudotile was removed in Hyprland 0.55 (the `pseudo`
|
# dwindle:pseudotile was removed in Hyprland 0.55 (the `pseudo`
|
||||||
# dispatcher still exists); setting it aborts config parsing.
|
# dispatcher still exists); setting it aborts config parsing.
|
||||||
dwindle.preserve_split = lib.mkDefault true;
|
dwindle.preserve_split = lib.mkDefault true;
|
||||||
|
|||||||
@@ -96,6 +96,33 @@ in
|
|||||||
description = "XKB variant for the Hyprland session.";
|
description = "XKB variant for the Hyprland session.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
keyboard.devices = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
layout = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "XKB layout for this specific keyboard.";
|
||||||
|
};
|
||||||
|
variant = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "XKB variant for this keyboard.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression ''{ "keychron-keychron-k2" = { layout = "de"; }; }'';
|
||||||
|
description = ''
|
||||||
|
Per-device keyboard layout. The key is the device name from
|
||||||
|
`hyprctl devices` (lower-case, hyphenated). Each entry generates a
|
||||||
|
Hyprland `device` block that overrides the session-wide
|
||||||
|
nomarchy.keyboard.layout for that keyboard only — e.g. an external
|
||||||
|
keyboard that's physically a different layout than the laptop's
|
||||||
|
built-in one. Hyprland applies it automatically whenever that
|
||||||
|
keyboard is connected.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
package = lib.mkOption {
|
package = lib.mkOption {
|
||||||
type = lib.types.package;
|
type = lib.types.package;
|
||||||
default = pkgs.nomarchy-theme-sync;
|
default = pkgs.nomarchy-theme-sync;
|
||||||
|
|||||||
@@ -12,6 +12,12 @@
|
|||||||
let
|
let
|
||||||
t = config.nomarchy.theme;
|
t = config.nomarchy.theme;
|
||||||
|
|
||||||
|
# Show the active-keyboard-layout indicator only when more than one layout
|
||||||
|
# is in play — multiple session layouts (comma-separated) or per-device
|
||||||
|
# overrides (nomarchy.keyboard.devices) — so single-layout bars stay clean.
|
||||||
|
showLanguage = lib.hasInfix "," config.nomarchy.keyboard.layout
|
||||||
|
|| config.nomarchy.keyboard.devices != { };
|
||||||
|
|
||||||
# Power-profile indicator (power-profiles-daemon). Both scripts self-
|
# Power-profile indicator (power-profiles-daemon). Both scripts self-
|
||||||
# gate: they exit silently unless this is a laptop (a battery is
|
# gate: they exit silently unless this is a laptop (a battery is
|
||||||
# present) running PPD, so the module hides itself on desktops and
|
# present) running PPD, so the module hides itself on desktops and
|
||||||
@@ -70,7 +76,9 @@ let
|
|||||||
|
|
||||||
modules-left = [ "hyprland/workspaces" "hyprland/window" ];
|
modules-left = [ "hyprland/workspaces" "hyprland/window" ];
|
||||||
modules-center = [ "clock" ];
|
modules-center = [ "clock" ];
|
||||||
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "custom/powerprofile" "battery" "custom/notification" ];
|
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "custom/powerprofile" ]
|
||||||
|
++ lib.optional showLanguage "hyprland/language"
|
||||||
|
++ [ "battery" "custom/notification" ];
|
||||||
|
|
||||||
"hyprland/workspaces" = {
|
"hyprland/workspaces" = {
|
||||||
format = "{icon}";
|
format = "{icon}";
|
||||||
@@ -88,6 +96,13 @@ let
|
|||||||
tooltip-format = "<tt><small>{calendar}</small></tt>";
|
tooltip-format = "<tt><small>{calendar}</small></tt>";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Active keyboard layout (per focused device) — only placed in
|
||||||
|
# modules-right when showLanguage (see above).
|
||||||
|
"hyprland/language" = {
|
||||||
|
format = " {short}";
|
||||||
|
tooltip = false;
|
||||||
|
};
|
||||||
|
|
||||||
pulseaudio = {
|
pulseaudio = {
|
||||||
format = "{icon} {volume}%";
|
format = "{icon} {volume}%";
|
||||||
format-muted = "";
|
format-muted = "";
|
||||||
@@ -193,7 +208,7 @@ let
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #battery, #custom-notification {
|
#tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #language, #battery, #custom-notification {
|
||||||
color: @subtext;
|
color: @subtext;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user