My setup is currently this: NixOS.
I use following for xcape
, however it only works when manually running systemctl --user start xcape
instead of starting automatically:
# xcape service
systemd.user.services.xcape = {
restartIfChanged = true;
description = "Combine Ctrl+Escape";
wantedBy = ["graphical-session.target"];
partOf = ["graphical-session.target"];
serviceConfig = {
Type = "forking";
Restart = "always";
ExecStart = ''${pkgs.xcape}/bin/xcape -e "Control_L=Escape"'';
};
};
What’s the output of $ systemctl --user status graphical-session.target
?
If it’s inactive/hasn’t been reached, you need to configure your window manager/desktop environment to start and stop the target at the appropriate time. Here’s how I do it in my .xinitrc
:
systemctl --user -q start graphical-session.target
herbstluftwm --locked
systemctl --user -q stop graphical-session.target
~ ❯ systemctl --user status graphical-session.target
â—Ź graphical-session.target - Current graphical user session
Loaded: loaded (/etc/systemd/user/graphical-session.target; static)
Drop-In: /nix/store/h9f84vyplfs1hdhdwphz7jwq6b8rnvy6-user-units/graphical-session.target.d
└─overrides.conf
Active: active since Fri 2022-07-22 22:39:14 CEST; 54min ago
Until: Fri 2022-07-22 22:39:14 CEST; 54min ago
Docs: man:systemd.special(7)
Jul 22 22:39:14 odd systemd[1176]: Reached target Current graphical user session.
Here’s xcape
as well:
~ ❯ systemctl --user status xcape
â—Ź xcape.service - Combine Ctrl+Escape
Loaded: loaded (/etc/systemd/user/xcape.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2022-07-22 22:39:14 CEST; 54min ago
Main PID: 1208 (xcape)
Tasks: 2 (limit: 19089)
Memory: 564.0K
CPU: 259ms
CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/xcape.service
└─1208 /nix/store/2dxqy8lh3r03dxgqkg0zkr2ffja3wig9-xcape-unstable-2018-03-01/bin/xcape -e Control_L Escape
Jul 22 22:39:14 odd systemd[1176]: Starting Combine Ctrl+Escape...
Jul 22 22:39:14 odd systemd[1176]: Started Combine Ctrl+Escape.
Is the status output for xcape after you’ve manually started it? Because that certainly looks like it’s working.
It’s from when the system is booting. However the change doesn’t persist, for instance after sudo nixos-rebuild switch
. I don’t think it works after booting either, will try.
Hmm, for some reason xcape
starts before graphical-session.target
…
~ ❯ systemctl --user status graphical-session.target
â—Ź graphical-session.target - Current graphical user session
Loaded: loaded (/etc/systemd/user/graphical-session.target; static)
Drop-In: /nix/store/h9f84vyplfs1hdhdwphz7jwq6b8rnvy6-user-units/graphical-session.target.d
└─overrides.conf
Active: active since Fri 2022-07-22 23:53:37 CEST; 14s ago
Until: Fri 2022-07-22 23:53:37 CEST; 14s ago
Docs: man:systemd.special(7)
Jul 22 23:53:37 odd systemd[1171]: Reached target Current graphical user session.
~ ❯ systemctl --user status xcape
â—Ź xcape.service - Combine Ctrl+Escape
Loaded: loaded (/etc/systemd/user/xcape.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2022-07-22 23:53:37 CEST; 18s ago
Process: 1202 ExecStart=/nix/store/2dxqy8lh3r03dxgqkg0zkr2ffja3wig9-xcape-unstable-2018-03-01/bin/xcape -e Control_L=Escape (code=exited,>
Main PID: 1203 (xcape)
Tasks: 2 (limit: 19089)
Memory: 564.0K
CPU: 6ms
CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/xcape.service
└─1203 /nix/store/2dxqy8lh3r03dxgqkg0zkr2ffja3wig9-xcape-unstable-2018-03-01/bin/xcape -e Control_L Escape
Jul 22 23:53:37 odd systemd[1171]: Starting Combine Ctrl+Escape...
Jul 22 23:53:37 odd systemd[1171]: Started Combine Ctrl+Escape.
Process seems to start but it has no effect. Restarting makes it work again. Then after some time it stops working. Don’t know why.
Yeah, when I was using xcape
I found it was fairly finicky in general, the workaround I used at the time was having my window manager restart it when reloading it’s configuration.
If you don’t mind side-stepping the issue, I would recommend switching to interception-tools
and the dual-function-keys
plugin. It wraps evdev, so will work anywhere, be it X11, wayland, or the linux TTY.
Do you have an example of using either of those in NixOS? As long as I have control + escape it doesn’t matter 
I was just writing that up actually:
services.interception-tools =
let
dfkConfig = pkgs.writeText "dual-function-keys.yaml" ''
MAPPINGS:
- KEY: KEY_CAPSLOCK
TAP: KEY_ESC
HOLD: KEY_LEFTCTRL
'';
in
{
enable = true;
plugins = lib.mkForce [
pkgs.interception-tools-plugins.dual-function-keys
];
udevmonConfig = ''
- JOB: "${pkgs.interception-tools}/bin/intercept -g $DEVNODE | ${pkgs.interception-tools-plugins.dual-function-keys}/bin/dual-function-keys -c ${dfkConfig} | ${pkgs.interception-tools}/bin/uinput -d $DEVNODE"
DEVICE:
NAME: "USB Keyboard"
EVENTS:
EV_KEY: [[KEY_CAPSLOCK, KEY_ESC, KEY_LEFTCTRL]]
'';
};
This puts left control and escape on capslock.
To get the device name you can use sudo uinput -p -d /dev/input/by-id/foo
. Or just drop it, as it’s technically not required, it just limits interception tools from wrapping any compatible device (which can include things you wouldn’t really expect).
1 Like
Added this configuration here: configuration.nix, and control seems to work but not escape.
Hmm wait, services.xserver.xkbOptions might be interferring.
Now my caps lock is acting as backspace. Don’t know why this is the case.
EDIT:
Finally got it working here, thanks for the help!
I used xinput
to find the proper NAME:
1 Like
That’s the default colemak behaviour.
Is $ systemctl status interception-tools
showing you something like:
CGroup: /system.slice/interception-tools.service
├─3021901 /nix/store/ch2272gvqhzwrjxzcf6ldg39b61nrlfw-interception-tools-0.6.8/bin/udevmon -c /nix/store/v16yzikjkz6w3a8qgjfirgks39x63hdl-udevmon.yaml
├─3021967 sh -c "/nix/store/ch2272gvqhzwrjxzcf6ldg39b61nrlfw-interception-tools-0.6.8/bin/intercept -g \$DEVNODE | /nix/store/5vyziafj0x0jk8y89mlkaxnwi3wlf6c0-dual-function
├─3021970 /nix/store/ch2272gvqhzwrjxzcf6ldg39b61nrlfw-interception-tools-0.6.8/bin/intercept -g /dev/input/event1
├─3021971 /nix/store/5vyziafj0x0jk8y89mlkaxnwi3wlf6c0-dual-function-keys-1.4.0/bin/dual-function-keys -c /nix/store/bnlgww1gn5gqfk448f5964dx6m8bfv4q-dual-function-keys.yaml
└─3021972 /nix/store/ch2272gvqhzwrjxzcf6ldg39b61nrlfw-interception-tools-0.6.8/bin/uinput -d /dev/input/event1
CGroup: /system.slice/interception-tools.service
└─5889 /nix/store/ch2272gvqhzwrjxzcf6ldg39b61nrlfw-interception-tools-0.6.8/bin/udevmon -c /nix/store/fx4g2aqvn9fb4bl7khvjhrfpfa>
Then it hasen’t found a device with a matching device name and EV_KEYs.
The system control
in the device name is a bit suspicious, try dropping the name and see what happens.