Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/hyprland/hyprland.conf
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ env = GDK_BACKEND,wayland
# =============================================================================
exec-once = dbus-update-activation-environment --systemd --all
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE XDG_RUNTIME_DIR HYPRLAND_INSTANCE_SIGNATURE XDG_SESSION_DESKTOP
exec-once = sleep 1 && systemctl --user restart xremap
exec-once = sleep 1 && systemctl --user restart xdg-desktop-portal-hyprland && sleep 0.5 && systemctl --user restart xdg-desktop-portal-gtk && sleep 0.5 && systemctl --user restart xdg-desktop-portal

# Clipboard history
Expand Down
7 changes: 7 additions & 0 deletions config/keyd/default.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
[ids]
*
# xremap creates its own virtual keyboard device via uinput. By default, xremap
# uses vendor/product 0x1234/0x5678 for that output device.
#
# When keyd matches all keyboards via `*`, it can end up grabbing xremap's
# virtual device too, which may cause event feedback/loops and effectively kill
# input. Exclude the default xremap device id to prevent that.
-1234:5678

[main]
# Caps Lock = pure Super (for Hyprland hotkeys)
Expand Down
91 changes: 90 additions & 1 deletion flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
nixos-hardware = {
url = "github:NixOS/nixos-hardware/master";
};
xremap = {
url = "github:xremap/nix-flake";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs =
Expand Down
127 changes: 126 additions & 1 deletion named-hosts/matic/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ inputs.nixpkgs.lib.nixosSystem {
# Keyd configuration
../../config/keyd

# Xremap module
inputs.xremap.nixosModules.default

# Base system configuration
(
{ config, lib, ... }:
Expand Down Expand Up @@ -63,6 +66,7 @@ inputs.nixpkgs.lib.nixosSystem {
extraGroups = [
"wheel"
"networkmanager"
"input"
"video"
];
Comment on lines 66 to 71

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the user to the input group grants broad read access to /dev/input/event* on many systems (i.e., keylogging capability). If xremap only needs access to /dev/uinput, consider avoiding input group membership and instead granting access just to the uinput node via a dedicated group or uaccess on that device only, to keep privileges minimal.

Copilot uses AI. Check for mistakes.
home = "/home/${username}";
Expand All @@ -72,6 +76,128 @@ inputs.nixpkgs.lib.nixosSystem {

security.sudo.wheelNeedsPassword = false;

# Input remapping (xremap)
hardware.uinput.enable = true;
services.udev.extraRules = ''
KERNEL=="uinput", GROUP="input", TAG+="uaccess", MODE:="0660", OPTIONS+="static_node=uinput"

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The udev rule sets GROUP="input" for the uinput device, which ties the setup to the broad input group and duplicates the uaccess tagging approach. Consider switching this rule to a dedicated group (e.g., uinput) or relying on TAG+="uaccess" alone for the uinput node, so you don’t need to grant the user full input group privileges.

Suggested change
KERNEL=="uinput", GROUP="input", TAG+="uaccess", MODE:="0660", OPTIONS+="static_node=uinput"
KERNEL=="uinput", TAG+="uaccess", MODE:="0660", OPTIONS+="static_node=uinput"

Copilot uses AI. Check for mistakes.
KERNEL=="event*", ATTRS{name}=="keyd virtual keyboard", GROUP="input", MODE:="0660"
KERNEL=="event*", ATTRS{name}=="keyd virtual pointer", GROUP="input", MODE:="0660"
'';
services.xremap =
let
hyperPrefix = "C-Alt-Shift-Super-";
ctrlPrefix = "C-";
letters = [
"a"
"b"
"c"
"d"
"e"
"f"
"g"
"h"
"i"
"j"
"k"
"l"
"m"
"n"
"o"
"p"
"q"
"r"
"s"
"t"
"u"
"v"
"w"
"x"
"y"
"z"
];
# Numbers 3, 4, 5 are intentionally excluded — they pass through as
# Hyper+3/4/5 for Hyprland screenshot/recording bindings.
# See: config/hyprland/hyprland.conf (screenshot section)
numbers = [
"0"
"1"
"2"
"6"
"7"
"8"
"9"
];
Comment on lines +121 to +129

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The numbers list skips "3", "4", and "5", so Hyper+3/4/5 won’t be remapped (and the omission is easy to miss because other sets are complete). If this is meant to cover all number keys, include the missing digits or generate the list programmatically to avoid accidental gaps.

Copilot uses AI. Check for mistakes.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
symbols = [
"semicolon"
"dot"
"comma"
"slash"
"grave"
"backslash"
"leftbrace"
"rightbrace"
"apostrophe"
];
navigation = [
"tab"
"backspace"
"left"
"right"
"up"
"down"
];
remapKeys = letters ++ numbers ++ symbols ++ navigation;
mkRemap =
keys:
builtins.listToAttrs (
map (key: {
name = "${hyperPrefix}${key}";
value = "${ctrlPrefix}${key}";
}) keys
);
globalRemap = mkRemap remapKeys;
# Override copy/paste to Ctrl+Shift (terminal convention: Ctrl+C = SIGINT).
# All other keys (including z for undo) inherit from globalRemap.
ghosttyRemap = globalRemap // {
"${hyperPrefix}c" = "C-Shift-c";
"${hyperPrefix}v" = "C-Shift-v";
};
in
{
enable = true;
serviceMode = "user";
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
userName = username;
withWlroots = true;
watch = true;
deviceNames = [ "keyd virtual keyboard" ];
config = {
keymap = [
{
name = "Framework Command (Ghostty)";
application.only = [ "com.mitchellh.ghostty" ];
remap = ghosttyRemap;
}
{
name = "Framework Command (Global)";
application.not = [ "com.mitchellh.ghostty" ];
remap = globalRemap;
}
];
};
};

# Ensure xremap starts after Hyprland and auto-restarts on failure
systemd.user.services.xremap = {
after = [ "graphical-session.target" ];
partOf = [ "graphical-session.target" ];
serviceConfig = {
Restart = "on-failure";
RestartSec = 3;
};
# StartLimitIntervalSec is a [Unit] directive, not [Service].
unitConfig.StartLimitIntervalSec = 0;
};

# AMD graphics with hardware acceleration
hardware.graphics.enable = true;
hardware.graphics.extraPackages = with pkgs; [
Expand Down Expand Up @@ -219,7 +345,6 @@ inputs.nixpkgs.lib.nixosSystem {
ipaexfont
ipafont
joypixels
nerdfonts
nerd-fonts.jetbrains-mono
noto-fonts-cjk-sans
noto-fonts-cjk-serif
Expand Down
Loading