diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index a8c735aefecc8..65e1838f52bef 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -113,6 +113,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m - [Mealie](https://nightly.mealie.io/), a self-hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in NuxtJS for a pleasant user experience for the whole family. Available as [services.mealie](#opt-services.mealie.enable) +- [logiops](https://github.com/PixlOne/logiops), an unofficial userspace driver for HID++ Logitech devices. Available as [services.logiops](#opt-services.logiops.enable). + ## Backward Incompatibilities {#sec-release-24.05-incompatibilities} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index cfe2350d5762e..ffdb63767cce0 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -551,6 +551,7 @@ ./services/hardware/lirc.nix ./services/hardware/nvidia-container-toolkit-cdi-generator ./services/hardware/monado.nix + ./services/hardware/logiops.nix ./services/hardware/nvidia-optimus.nix ./services/hardware/openrgb.nix ./services/hardware/pcscd.nix diff --git a/nixos/modules/services/hardware/logiops.nix b/nixos/modules/services/hardware/logiops.nix new file mode 100644 index 0000000000000..057f78473bfe4 --- /dev/null +++ b/nixos/modules/services/hardware/logiops.nix @@ -0,0 +1,113 @@ +{ config, lib, pkgs, ... }: + +let + inherit (lib) mkPackageOption mkEnableOption mkIf mkOption types; + cfg = config.services.logiops; + format = pkgs.formats.libconfig { }; +in { + options.services.logiops = { + enable = mkEnableOption "logiops, an unofficial userspace driver for HID++ Logitech devices"; + package = mkPackageOption pkgs "logiops" { + example = "logiops_0_2_3"; + }; + + settings = mkOption { + type = types.submodule { freeformType = format.type; }; + description = '' + Configuration for logiops. + + See for more details. + Also see for an example config. + ''; + default = { }; + example = { + devices = [{ + name = "Wireless Mouse MX Master"; + dpi = 1000; + buttons = [ + { + cid = "0xc3"; + action = { + type = "Gestures"; + gestures = [ + { + direction = "Up"; + mode = "OnRelease"; + action = { + type = "Keypress"; + keys = [ "KEY_UP" ]; + }; + } + { + direction = "None"; + mode = "NoPress"; + } + ]; + }; + } + { + cid = "0xc4"; + action = { + type = "Keypress"; + keys = [ "KEY_A" ]; + }; + } + ]; + }]; + }; + }; + }; + + config = mkIf cfg.enable { + environment.etc."logid.cfg".source = format.generate "logid.cfg" cfg.settings; + systemd.services.logiops = { + description = "Logitech Configuration Daemon"; + documentation = [ "https://github.com/PixlOne/logiops" ]; + + wantedBy = [ "multi-user.target" ]; + + startLimitIntervalSec = 0; + after = [ "multi-user.target" ]; + wants = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/logid"; + Restart = "always"; + User = "root"; + + RuntimeDirectory = "logiops"; + + CapabilityBoundingSet = [ "CAP_SYS_NICE" ]; + DeviceAllow = [ "/dev/uinput rw" "char-hidraw rw" ]; + ProtectClock = true; + PrivateNetwork = true; + ProtectHome = true; + ProtectHostname = true; + PrivateUsers = true; + PrivateMounts = true; + PrivateTmp = true; + RestrictNamespaces = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + LockPersonality = true; + ProtectProc = "invisible"; + SystemCallFilter = [ "nice" "@system-service" "~@privileged" ]; + RestrictAddressFamilies = [ "AF_NETLINK" "AF_UNIX" ]; + RestrictSUIDSGID = true; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProcSubset = "pid"; + UMask = "0077"; + }; + }; + + # Add a `udev` rule to restart `logiops` when the mouse is connected + # https://github.com/PixlOne/logiops/issues/239#issuecomment-1044122412 + services.udev.extraRules = '' + ACTION=="add", SUBSYSTEM=="input", ATTRS{id/vendor}=="046d", RUN{program}="${config.systemd.package}/bin/systemctl --no-block try-restart logiops.service" + ''; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 7376cd40b910a..d00dc3695f16c 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -492,6 +492,7 @@ in { lldap = handleTest ./lldap.nix {}; locate = handleTest ./locate.nix {}; login = handleTest ./login.nix {}; + logiops = handleTest ./logiops.nix {}; logrotate = handleTest ./logrotate.nix {}; loki = handleTest ./loki.nix {}; luks = handleTest ./luks.nix {}; diff --git a/nixos/tests/logiops.nix b/nixos/tests/logiops.nix new file mode 100644 index 0000000000000..cce5e4784551d --- /dev/null +++ b/nixos/tests/logiops.nix @@ -0,0 +1,54 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "logiops"; + meta = with pkgs.lib.maintainers; { maintainers = [ ckie ]; }; + + nodes = { + main = { ... }: { + services.logiops = { + enable = true; + + settings = { + devices = [{ + name = "Wireless Mouse MX Master 3"; + + smartshift = { + on = true; + threshold = 20; + }; + + hiresscroll = { + hires = true; + invert = false; + target = false; + }; + + dpi = 1500; + + buttons = [ + { + cid = "0x53"; + action = { + type = "Keypress"; + keys = [ "KEY_FORWARD" ]; + }; + } + { + cid = "0x56"; + action = { + type = "Keypress"; + keys = [ "KEY_BACK" ]; + }; + } + ]; + }]; + }; + }; + }; + }; + + testScript = '' + start_all() + with subtest("main machine should succeed"): + main.wait_for_unit("logiops.service") + ''; +})