Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

keyd: add keyd service and test #221321

Merged
merged 1 commit into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2305.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ In addition to numerous new and upgraded packages, this release has the followin

- [QDMR](https://dm3mat.darc.de/qdmr/), a GUI application and command line tool for programming DMR radios [programs.qdmr](#opt-programs.qdmr.enable)

- [keyd](https://github.com/rvaiya/keyd), a key remapping daemon for linux. Available as [services.keyd](#opt-services.keyd.enable).

- [v2rayA](https://v2raya.org), a Linux web GUI client of Project V which supports V2Ray, Xray, SS, SSR, Trojan and Pingtunnel. Available as [services.v2raya](options.html#opt-services.v2raya.enable).

- [ulogd](https://www.netfilter.org/projects/ulogd/index.html), a userspace logging daemon for netfilter/iptables related logging. Available as [services.ulogd](options.html#opt-services.ulogd.enable).
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@
./services/hardware/usbmuxd.nix
./services/hardware/usbrelayd.nix
./services/hardware/vdr.nix
./services/hardware/keyd.nix
./services/home-automation/evcc.nix
./services/home-automation/home-assistant.nix
./services/home-automation/zigbee2mqtt.nix
Expand Down
112 changes: 112 additions & 0 deletions nixos/modules/services/hardware/keyd.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.keyd;
settingsFormat = pkgs.formats.ini { };
in
{
options = {
services.keyd = {
enable = mkEnableOption (lib.mdDoc "keyd, a key remapping daemon");

ids = mkOption {
type = types.listOf types.string;
default = [ "*" ];
example = [ "*" "-0123:0456" ];
description = lib.mdDoc ''
Device identifiers, as shown by {manpage}`keyd(1)`.
'';
};

settings = mkOption {
type = settingsFormat.type;
default = { };
example = {
main = {
capslock = "overload(control, esc)";
rightalt = "layer(rightalt)";
};

rightalt = {
j = "down";
k = "up";
h = "left";
l = "right";
};
};
description = lib.mdDoc ''
Configuration, except `ids` section, that is written to {file}`/etc/keyd/default.conf`.
See <https://github.com/rvaiya/keyd> how to configure.
'';
};
};
};

config = mkIf cfg.enable {
environment.etc."keyd/default.conf".source = pkgs.runCommand "default.conf"
{
ids = ''
[ids]
${concatStringsSep "\n" cfg.ids}
'';
passAsFile = [ "ids" ];
} ''
cat $idsPath <(echo) ${settingsFormat.generate "keyd-main.conf" cfg.settings} >$out
'';

hardware.uinput.enable = lib.mkDefault true;

systemd.services.keyd = {
description = "Keyd remapping daemon";
documentation = [ "man:keyd(1)" ];

wantedBy = [ "multi-user.target" ];

restartTriggers = [
config.environment.etc."keyd/default.conf".source
];

# this is configurable in 2.4.2, later versions seem to remove this option.
# post-2.4.2 may need to set makeFlags in the derivation:
#
# makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ];
environment.KEYD_SOCKET = "/run/keyd/keyd.sock";

serviceConfig = {
ExecStart = "${pkgs.keyd}/bin/keyd";
Restart = "always";

DynamicUser = true;
SupplementaryGroups = [
config.users.groups.input.name
config.users.groups.uinput.name
];

RuntimeDirectory = "keyd";

# Hardening
CapabilityBoundingSet = "";
woojiq marked this conversation as resolved.
Show resolved Hide resolved
DeviceAllow = [
"char-input rw"
"/dev/uinput rw"
];
ProtectClock = true;
PrivateNetwork = true;
ProtectHome = true;
ProtectHostname = true;
PrivateUsers = true;
PrivateMounts = true;
RestrictNamespaces = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
MemoryDenyWriteExecute = true;
RestrictRealtime = true;
LockPersonality = true;
ProtectProc = "noaccess";
UMask = "0077";
};
};
};
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ in {
keter = handleTest ./keter.nix {};
kexec = handleTest ./kexec.nix {};
keycloak = discoverTests (import ./keycloak.nix);
keyd = handleTest ./keyd.nix {};
keymap = handleTest ./keymap.nix {};
knot = handleTest ./knot.nix {};
komga = handleTest ./komga.nix {};
Expand Down
82 changes: 82 additions & 0 deletions nixos/tests/keyd.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# The test template is taken from the `./keymap.nix`
{ system ? builtins.currentSystem
, config ? { }
, pkgs ? import ../.. { inherit system config; }
}:

with import ../lib/testing-python.nix { inherit system pkgs; };

let
readyFile = "/tmp/readerReady";
resultFile = "/tmp/readerResult";

testReader = pkgs.writeScript "test-input-reader" ''
rm -f ${resultFile} ${resultFile}.tmp
logger "testReader: START: Waiting for $1 characters, expecting '$2'."
touch ${readyFile}
read -r -N $1 chars
rm -f ${readyFile}
if [ "$chars" == "$2" ]; then
logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp
else
logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp
fi
# rename after the file is written to prevent a race condition
mv ${resultFile}.tmp ${resultFile}
'';


mkKeyboardTest = name: { settings, test }: with pkgs.lib; makeTest {
inherit name;

nodes.machine = {
services.keyd = {
enable = true;
inherit settings;
};
};

testScript = ''
import shlex
machine.wait_for_unit("keyd.service")
def run_test_case(cmd, test_case_name, inputs, expected):
with subtest(test_case_name):
assert len(inputs) == len(expected)
machine.execute("rm -f ${readyFile} ${resultFile}")
# set up process that expects all the keys to be entered
machine.succeed(
"{} {} {} {} >&2 &".format(
cmd,
"${testReader}",
len(inputs),
shlex.quote("".join(expected)),
)
)
# wait for reader to be ready
machine.wait_for_file("${readyFile}")
# send all keys
for key in inputs:
machine.send_key(key)
# wait for result and check
machine.wait_for_file("${resultFile}")
machine.succeed("grep -q 'PASS:' ${resultFile}")
test = ${builtins.toJSON test}
run_test_case("openvt -sw --", "${name}", test["press"], test["expect"])
'';
};

in
pkgs.lib.mapAttrs mkKeyboardTest {
swap-ab_and_ctrl-as-shift = {
test.press = [ "a" "ctrl-b" "c" ];
test.expect = [ "b" "A" "c" ];

settings.main = {
"a" = "b";
"b" = "a";
"control" = "oneshot(shift)";
};
};
}
6 changes: 6 additions & 0 deletions pkgs/tools/inputmethods/keyd/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
, systemd
, runtimeShell
, python3
, nixosTests
}:

let
Expand Down Expand Up @@ -59,11 +60,16 @@ stdenv.mkDerivation rec {

enableParallelBuilding = true;

# post-2.4.2 may need this to unbreak the test
# makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ];

postInstall = ''
ln -sf ${lib.getExe appMap} $out/bin/${appMap.pname}
rm -rf $out/etc
'';

passthru.tests.keyd = nixosTests.keyd;

meta = with lib; {
description = "A key remapping daemon for linux.";
license = licenses.mit;
Expand Down