From 176c446b97d0c784170586cc465f912e08d623a8 Mon Sep 17 00:00:00 2001 From: Michael Hoang Date: Wed, 24 Aug 2022 18:45:06 +1000 Subject: [PATCH 1/3] launchd: add extra KeepAlive options --- modules/launchd/launchd.nix | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/launchd/launchd.nix b/modules/launchd/launchd.nix index a3ea918..72d3fbb 100644 --- a/modules/launchd/launchd.nix +++ b/modules/launchd/launchd.nix @@ -194,6 +194,21 @@ with lib; ''; }; + Crashed = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + If true, the the job will be restarted as long as it exited due to a signal which is typically + associated with a crash (SIGILL, SIGSEGV, etc.). If false, the job will be restarted in the + inverse condition. + ''; + }; + + AfterInitialDemand = mkOption { + type = types.nullOr types.bool; + default = null; + }; + }; })); default = null; From afcce995bd14cd7b3f15214c9f2a431e24e6e432 Mon Sep 17 00:00:00 2001 From: Michael Hoang Date: Wed, 24 Aug 2022 23:34:46 +1000 Subject: [PATCH 2/3] karabiner-elements: init module --- modules/module-list.nix | 1 + .../services/karabiner-elements/default.nix | 115 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 modules/services/karabiner-elements/default.nix diff --git a/modules/module-list.nix b/modules/module-list.nix index 57b0bf8..95a270e 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -47,6 +47,7 @@ ./services/dnsmasq.nix ./services/emacs.nix ./services/gitlab-runner.nix + ./services/karabiner-elements ./services/khd ./services/kwm ./services/lorri.nix diff --git a/modules/services/karabiner-elements/default.nix b/modules/services/karabiner-elements/default.nix new file mode 100644 index 0000000..4cdba2d --- /dev/null +++ b/modules/services/karabiner-elements/default.nix @@ -0,0 +1,115 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.karabiner-elements; + + parentAppDir = "/Applications/.Nix-Karabiner"; +in + +{ + options = { + services.karabiner-elements.enable = mkEnableOption "Karabiner-Elements"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.karabiner-elements ]; + + system.activationScripts.extraActivation.text = '' + rm -rf ${parentAppDir} + mkdir -p ${parentAppDir} + # Kernel extensions must reside inside of /Applications, they cannot be symlinks + cp -r ${pkgs.karabiner-elements.driver}/Applications/.Karabiner-VirtualHIDDevice-Manager.app ${parentAppDir} + ''; + + # We need the karabiner_grabber and karabiner_observer daemons to run after the + # Nix Store has been mounted, but we can't use wait4path as they need to be + # executed directly for the Input Monitoring permission. We also want these + # daemons to auto restart but if they start up without the Nix Store they will + # refuse to run again until they've been unloaded and loaded back in so we can + # use a helper daemon to start them. + launchd.daemons.start_karabiner_daemons = { + script = '' + /bin/wait4path ${pkgs.karabiner-elements} + launchctl kickstart system/org.pqrs.karabiner.karabiner_grabber + launchctl kickstart system/org.pqrs.karabiner.karabiner_observer + ''; + # Due to the daemons being loaded in alphabetical order during darwin-rebuild switch + # we need to set the label so that this daemon will be loaded after karabiner_grabber + # and karabiner_observer so that no reboot is required to start these daemons. + serviceConfig.Label = "org.xyz.start_karabiner_daemons"; + serviceConfig.RunAtLoad = true; + }; + + launchd.daemons.karabiner_grabber = { + serviceConfig.ProgramArguments = [ + "${pkgs.karabiner-elements}/Library/Application Support/org.pqrs/Karabiner-Elements/bin/karabiner_grabber" + ]; + serviceConfig.ProcessType = "Interactive"; + serviceConfig.Label = "org.pqrs.karabiner.karabiner_grabber"; + serviceConfig.KeepAlive.SuccessfulExit = true; + serviceConfig.KeepAlive.Crashed = true; + serviceConfig.KeepAlive.AfterInitialDemand = true; + }; + + launchd.daemons.karabiner_observer = { + serviceConfig.ProgramArguments = [ + "${pkgs.karabiner-elements}/Library/Application Support/org.pqrs/Karabiner-Elements/bin/karabiner_observer" + ]; + + serviceConfig.Label = "org.pqrs.karabiner.karabiner_observer"; + serviceConfig.KeepAlive.SuccessfulExit = true; + serviceConfig.KeepAlive.Crashed = true; + serviceConfig.KeepAlive.AfterInitialDemand = true; + }; + + launchd.daemons.Karabiner-DriverKit-VirtualHIDDeviceClient = { + serviceConfig.ProgramArguments = [ + "/bin/sh" "-c" + # For unknown reasons this daemon will fail if VirtualHIDDeviceClient is not exec'd. + "/bin/wait4path ${pkgs.karabiner-elements.driver} && exec \"${pkgs.karabiner-elements.driver}/Library/Application Support/org.pqrs/Karabiner-DriverKit-VirtualHIDDevice/Applications/Karabiner-DriverKit-VirtualHIDDeviceClient.app/Contents/MacOS/Karabiner-DriverKit-VirtualHIDDeviceClient\"" + ]; + serviceConfig.ProcessType = "Interactive"; + serviceConfig.Label = "org.pqrs.Karabiner-DriverKit-VirtualHIDDeviceClient"; + serviceConfig.KeepAlive = true; + }; + + # Normally karabiner_console_user_server calls activate on the manager but + # because we use a custom location we need to call activate manually. + launchd.user.agents.activate_karabiner_system_ext = { + serviceConfig.ProgramArguments = [ + "${parentAppDir}/.Karabiner-VirtualHIDDevice-Manager.app/Contents/MacOS/Karabiner-VirtualHIDDevice-Manager" "activate" + ]; + serviceConfig.RunAtLoad = true; + }; + + # We can't put this inside the extraActivation script as /run gets nuked + # every reboot and the extraActivation script only gets run on darwin-rebuild + # switch. + launchd.daemons.setsuid_karabiner_session_monitor = { + script = '' + set -e + /bin/wait4path ${pkgs.karabiner-elements} + rm -rf /run/wrappers + mkdir -p /run/wrappers/bin + install -m4555 "${pkgs.karabiner-elements}/Library/Application Support/org.pqrs/Karabiner-Elements/bin/karabiner_session_monitor" /run/wrappers/bin + ''; + serviceConfig.RunAtLoad = true; + serviceConfig.KeepAlive.SuccessfulExit = false; + }; + + launchd.user.agents.karabiner_session_monitor = { + serviceConfig.ProgramArguments = [ + "/bin/sh" "-c" + "/bin/wait4path /run/wrappers/bin && /run/wrappers/bin/karabiner_session_monitor" + ]; + serviceConfig.Label = "org.pqrs.karabiner.karabiner_session_monitor"; + serviceConfig.KeepAlive = true; + }; + + environment.userLaunchAgents."org.pqrs.karabiner.agent.karabiner_grabber.plist".source = "${pkgs.karabiner-elements}/Library/LaunchAgents/org.pqrs.karabiner.agent.karabiner_grabber.plist"; + environment.userLaunchAgents."org.pqrs.karabiner.agent.karabiner_observer.plist".source = "${pkgs.karabiner-elements}/Library/LaunchAgents/org.pqrs.karabiner.agent.karabiner_observer.plist"; + environment.userLaunchAgents."org.pqrs.karabiner.karabiner_console_user_server.plist".source = "${pkgs.karabiner-elements}/Library/LaunchAgents/org.pqrs.karabiner.karabiner_console_user_server.plist"; + }; +} From ed4d2d69a03c982f71ffe65b589daf3e6b047e7d Mon Sep 17 00:00:00 2001 From: Michael Hoang Date: Thu, 1 Sep 2022 14:39:26 +1000 Subject: [PATCH 3/3] karabiner-elements: don't use scripts `launchd.daemons.*.script` are stored in `/nix/store` which might not be mounted when the launch daemon attempts to start. --- .../services/karabiner-elements/default.nix | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/modules/services/karabiner-elements/default.nix b/modules/services/karabiner-elements/default.nix index 4cdba2d..395a2f8 100644 --- a/modules/services/karabiner-elements/default.nix +++ b/modules/services/karabiner-elements/default.nix @@ -30,11 +30,13 @@ in # refuse to run again until they've been unloaded and loaded back in so we can # use a helper daemon to start them. launchd.daemons.start_karabiner_daemons = { - script = '' - /bin/wait4path ${pkgs.karabiner-elements} - launchctl kickstart system/org.pqrs.karabiner.karabiner_grabber - launchctl kickstart system/org.pqrs.karabiner.karabiner_observer - ''; + serviceConfig.ProgramArguments = [ + "/bin/sh" "-c" + "/bin/wait4path /nix/store && ${pkgs.writeScript "start_karabiner_daemons" '' + launchctl kickstart system/org.pqrs.karabiner.karabiner_grabber + launchctl kickstart system/org.pqrs.karabiner.karabiner_observer + ''}" + ]; # Due to the daemons being loaded in alphabetical order during darwin-rebuild switch # we need to set the label so that this daemon will be loaded after karabiner_grabber # and karabiner_observer so that no reboot is required to start these daemons. @@ -68,7 +70,7 @@ in serviceConfig.ProgramArguments = [ "/bin/sh" "-c" # For unknown reasons this daemon will fail if VirtualHIDDeviceClient is not exec'd. - "/bin/wait4path ${pkgs.karabiner-elements.driver} && exec \"${pkgs.karabiner-elements.driver}/Library/Application Support/org.pqrs/Karabiner-DriverKit-VirtualHIDDevice/Applications/Karabiner-DriverKit-VirtualHIDDeviceClient.app/Contents/MacOS/Karabiner-DriverKit-VirtualHIDDeviceClient\"" + "/bin/wait4path /nix/store && exec \"${pkgs.karabiner-elements.driver}/Library/Application Support/org.pqrs/Karabiner-DriverKit-VirtualHIDDevice/Applications/Karabiner-DriverKit-VirtualHIDDeviceClient.app/Contents/MacOS/Karabiner-DriverKit-VirtualHIDDeviceClient\"" ]; serviceConfig.ProcessType = "Interactive"; serviceConfig.Label = "org.pqrs.Karabiner-DriverKit-VirtualHIDDeviceClient"; @@ -88,13 +90,14 @@ in # every reboot and the extraActivation script only gets run on darwin-rebuild # switch. launchd.daemons.setsuid_karabiner_session_monitor = { - script = '' - set -e - /bin/wait4path ${pkgs.karabiner-elements} - rm -rf /run/wrappers - mkdir -p /run/wrappers/bin - install -m4555 "${pkgs.karabiner-elements}/Library/Application Support/org.pqrs/Karabiner-Elements/bin/karabiner_session_monitor" /run/wrappers/bin - ''; + serviceConfig.ProgramArguments = [ + "/bin/sh" "-c" + "/bin/wait4path /nix/store && ${pkgs.writeScript "setsuid_karabiner_session_monitor" '' + rm -rf /run/wrappers + mkdir -p /run/wrappers/bin + install -m4555 "${pkgs.karabiner-elements}/Library/Application Support/org.pqrs/Karabiner-Elements/bin/karabiner_session_monitor" /run/wrappers/bin + ''}" + ]; serviceConfig.RunAtLoad = true; serviceConfig.KeepAlive.SuccessfulExit = false; };