Skip to content

Commit 9bd08b5

Browse files
committed
macos-remap-keys: add
1 parent c630dfa commit 9bd08b5

File tree

7 files changed

+247
-0
lines changed

7 files changed

+247
-0
lines changed

modules/modules.nix

+1
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ let
342342
./services/linux-wallpaperengine.nix
343343
./services/listenbrainz-mpd.nix
344344
./services/lorri.nix
345+
./services/macos-remap-keys
345346
./services/mako.nix
346347
./services/mbsync.nix
347348
./services/megasync.nix
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{ config, lib, pkgs, ... }:
2+
let
3+
cfg = config.services.macos-remap-keys;
4+
keytables = import ./keytables.nix { inherit lib; };
5+
6+
keyToHIDCode = table: key: keytables.${table}.${key};
7+
8+
# Note: hidutil requires HIDKeyboardModifierMapping values to be in hexadecimal
9+
# format rather than decimal JSON. Using hex strings instead of numbers will
10+
# crash macOS.
11+
makeMapping = table: from: to:
12+
''
13+
{ "HIDKeyboardModifierMappingSrc": ${
14+
keyToHIDCode table from
15+
}, "HIDKeyboardModifierMappingDst": ${keyToHIDCode table to} }'';
16+
17+
makeMappingsList = table: mappings:
18+
lib.mapAttrsToList (from: to: makeMapping table from to) mappings;
19+
20+
allMappings = (makeMappingsList "keyboard" (cfg.keyboard or { }))
21+
++ (makeMappingsList "keypad" (cfg.keypad or { }));
22+
23+
allMappingsString = lib.concatStringsSep ", " allMappings;
24+
propertyString = ''{ "UserKeyMapping": [ ${allMappingsString} ] }'';
25+
in {
26+
meta.maintainers = [ lib.maintainers.WeetHet ];
27+
28+
options.services.macos-remap-keys = {
29+
enable = lib.mkEnableOption "macOS key remapping service";
30+
31+
keyboard = lib.mkOption {
32+
type = lib.types.attrsOf lib.types.str;
33+
default = { };
34+
example = {
35+
Capslock = "Escape";
36+
SquareBracketOpen = "SquareBracketClose";
37+
};
38+
description = "Mapping of keyboard keys to remap";
39+
};
40+
41+
keypad = lib.mkOption {
42+
type = lib.types.attrsOf lib.types.str;
43+
default = { };
44+
example = {
45+
Enter = "Equal";
46+
Plus = "Minus";
47+
};
48+
description = "Mapping of keypad keys to remap";
49+
};
50+
};
51+
52+
config = lib.mkIf cfg.enable {
53+
assertions = [{
54+
assertion = (cfg.enable -> pkgs.stdenv.hostPlatform.isDarwin);
55+
message = "Key remapping only supports macOS";
56+
}];
57+
home.activation.macosRemapKeys =
58+
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
59+
$DRY_RUN_CMD /usr/bin/hidutil property --set '${propertyString}' &> /dev/null
60+
'';
61+
launchd.agents.remap-keys = {
62+
enable = true;
63+
config = {
64+
ProgramArguments =
65+
[ "/usr/bin/hidutil" "property" "--set" propertyString ];
66+
KeepAlive.SuccessfulExit = false;
67+
RunAtLoad = true;
68+
};
69+
};
70+
};
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{ lib }:
2+
let
3+
letters = let
4+
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
5+
lettersList = lib.stringToCharacters alphabet;
6+
indices = builtins.genList (i: i + 4) 26;
7+
in lib.listToAttrs (lib.zipListsWith (letter: index: {
8+
name = letter;
9+
value = "0x${lib.toHexString index}";
10+
}) lettersList indices);
11+
12+
numbers = {
13+
One = "0x1E";
14+
Two = "0x1F";
15+
Three = "0x20";
16+
Four = "0x21";
17+
Five = "0x22";
18+
Six = "0x23";
19+
Seven = "0x24";
20+
Eight = "0x25";
21+
Nine = "0x26";
22+
Zero = "0x27";
23+
};
24+
25+
specialKeys = {
26+
Enter = "0x28";
27+
Escape = "0x29";
28+
Backspace = "0x2A";
29+
Tab = "0x2B";
30+
Spacebar = "0x2C";
31+
Minus = "0x2D";
32+
Equal = "0x2E";
33+
SquareBracketOpen = "0x2F";
34+
SquareBracketClose = "0x30";
35+
Backslash = "0x31";
36+
Hash = "0x32";
37+
Semicolon = "0x33";
38+
SingleQuote = "0x34";
39+
GraveAccent = "0x35";
40+
Comma = "0x36";
41+
Dot = "0x37";
42+
Slash = "0x38";
43+
Capslock = "0x39";
44+
};
45+
46+
fKeys1To12 = {
47+
F1 = "0x3A";
48+
F2 = "0x3B";
49+
F3 = "0x3C";
50+
F4 = "0x3D";
51+
F5 = "0x3E";
52+
F6 = "0x3F";
53+
F7 = "0x40";
54+
F8 = "0x41";
55+
F9 = "0x42";
56+
F10 = "0x43";
57+
F11 = "0x44";
58+
F12 = "0x45";
59+
};
60+
61+
fKeys13To24 = {
62+
F13 = "0x68";
63+
F14 = "0x69";
64+
F15 = "0x6A";
65+
F16 = "0x6B";
66+
F17 = "0x6C";
67+
F18 = "0x6D";
68+
F19 = "0x6E";
69+
F20 = "0x6F";
70+
F21 = "0x70";
71+
F22 = "0x71";
72+
F23 = "0x72";
73+
F24 = "0x73";
74+
};
75+
76+
navigationKeys = {
77+
PrintScreen = "0x46";
78+
ScrollLock = "0x47";
79+
Pause = "0x48";
80+
Insert = "0x49";
81+
Home = "0x4A";
82+
PageUp = "0x4B";
83+
ForwardDelete = "0x4C";
84+
End = "0x4D";
85+
PageDown = "0x4E";
86+
RightArrow = "0x4F";
87+
LeftArrow = "0x50";
88+
DownArrow = "0x51";
89+
UpArrow = "0x52";
90+
NumLock = "0x53";
91+
};
92+
93+
modifierKeys = {
94+
Control = "0xE0";
95+
Shift = "0xE1";
96+
Option = "0xE2";
97+
Command = "0xE3";
98+
RightControl = "0xE4";
99+
RightShift = "0xE5";
100+
RightOption = "0xE6";
101+
RightCommand = "0xE7";
102+
};
103+
104+
keypadKeys = {
105+
Slash = "0x54";
106+
Asterisk = "0x55";
107+
Minus = "0x56";
108+
Plus = "0x57";
109+
Enter = "0x58";
110+
One = "0x59";
111+
Two = "0x5A";
112+
Three = "0x5B";
113+
Four = "0x5C";
114+
Five = "0x5D";
115+
Six = "0x5E";
116+
Seven = "0x5F";
117+
Eight = "0x60";
118+
Nine = "0x61";
119+
Zero = "0x62";
120+
Dot = "0x63";
121+
BashSlash = "0x64";
122+
Application = "0x65";
123+
Power = "0x66";
124+
Equal = "0x67";
125+
};
126+
127+
mapToInt = keyPage: attrs:
128+
lib.mapAttrs (name: value:
129+
let keycode = lib.fromHexString (lib.removePrefix "0x" value);
130+
in "0x${lib.toHexString (keyPage + keycode)}") attrs;
131+
132+
page7Keys = mapToInt (lib.fromHexString "700000000") (letters // numbers
133+
// specialKeys // fKeys1To12 // fKeys13To24 // navigationKeys
134+
// modifierKeys);
135+
pageFFKeys = mapToInt (lib.fromHexString "FF00000000") { Fn = "0x3"; };
136+
in {
137+
keyboard = page7Keys // pageFFKeys;
138+
keypad = mapToInt keypadKeys;
139+
}

tests/default.nix

+1
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ in import nmtSrc {
325325
./modules/services/git-sync-darwin
326326
./modules/services/imapnotify-darwin
327327
./modules/services/nix-gc-darwin
328+
./modules/services/macos-remap-keys
328329
./modules/services/ollama/darwin
329330
./modules/services/yubikey-agent-darwin
330331
./modules/targets-darwin
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>KeepAlive</key>
6+
<dict>
7+
<key>SuccessfulExit</key>
8+
<false/>
9+
</dict>
10+
<key>Label</key>
11+
<string>org.nix-community.home.remap-keys</string>
12+
<key>ProgramArguments</key>
13+
<array>
14+
<string>/usr/bin/hidutil</string>
15+
<string>property</string>
16+
<string>--set</string>
17+
<string>{ "UserKeyMapping": [ { "HIDKeyboardModifierMappingSrc": 0x700000039, "HIDKeyboardModifierMappingDst": 0x70000002A } ] }</string>
18+
</array>
19+
<key>RunAtLoad</key>
20+
<true/>
21+
</dict>
22+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
services.macos-remap-keys = {
3+
enable = true;
4+
keyboard = { Capslock = "Backspace"; };
5+
};
6+
7+
nmt.script = ''
8+
launchAgent=LaunchAgents/org.nix-community.home.remap-keys.plist
9+
assertFileExists "$launchAgent"
10+
assertFileContent "$launchAgent" ${./basic-agent.plist}
11+
'';
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ macos-remap-keys-basic-configuration = ./basic-configuration.nix; }

0 commit comments

Comments
 (0)