Description

This is a guide for running [kanata | github.com/jtroo/kanata] on QubesOS. This might work for other keyboard remappers, but it wasn't tested.

Note: This guide was written for kanata v1.9.0 Note: This guide assumes that you have a separate USB Qube and that it's disposable

Overview

1. Installing kanata

2. Configuring kanata

[details="File ~/kanata/kanata-exclusions.conf"]

[Service]
ExecStart=
ExecStart=/usr/local/bin/launch-input-sender.py qubes.InputKeyboard /dev/input/%i "$TARGET_DOMAIN"
[/details]

[details="File ~/kanata/kanata.service"]

[Unit]
Description=Improve keyboard comfort and usability with advanced customization

[Service]
Type=simple
ExecStart=/usr/local/bin/kanata -c /rw/home/user/kanata/kanata.cfg
Restart=on-failure
[/details]

[details="rc.local"]

# === kanata configuration
# exclusion:
cp /rw/home/user/kanata/launch-input-sender.py /usr/local/bin/
chmod +x /usr/local/bin/launch-input-sender.py
mkdir /etc/systemd/system/qubes-input-sender-keyboard@.service.d/
cp /home/user/kanata/kanata-exclusions.conf /etc/systemd/system/qubes-input-sender-keyboard@.service.d/kanata-exclusions.conf

# kanata:
cp /rw/home/user/kanata/kanata /usr/local/bin/
chmod +x /usr/local/bin/kanata
cp /rw/home/user/kanata/kanata.service /etc/systemd/system/

systemctl daemon-reload
systemctl restart qubes-input-sender-keyboard@*
systemctl enable kanata
systemctl start kanata
# ===
[/details]

[details="File ~/kanata/launch-input-sender.py"]

#!/usr/bin/env python3

# Put your keyboard ids here
KEYBOARDS = [
  "VENDOR:PRODUCT"
]


- `sys-usb-dvm`: Run `chmod +x ~/kanata/kanata`

QUBES_INPUT_SENDER = "/usr/bin/qubes-input-sender"

import os
import subprocess
import sys

_, arg_rpc, arg_input, arg_domain = sys.argv

with open("/proc/bus/input/devices") as f:
    deviceinfo = f.read()

devices = deviceinfo.split("\n\n")
inputs = []
keyboards_not_found = KEYBOARDS.copy()

def get_infoline(infolines, i):
    for line in infolines:
        if line.startswith(i+":"):
            return line

for device in devices:
    infolines = device.split("\n")
    I_line = get_infoline(infolines, "I")
    if not I_line: continue

    vendor_pos = I_line.find("Vendor=")
    product_pos = I_line.find("Product=")
    vendor = I_line[vendor_pos+7:vendor_pos+11]
    product = I_line[product_pos+8:product_pos+12]
    if f"{vendor}:{product}" not in KEYBOARDS: continue

    S_line = get_infoline(infolines, "S")
    sysfs_pos = S_line.find("Sysfs=")
    sysfs = S_line[sysfs_pos+6:]
    sysfs_path = f"/sys{sysfs}"
    sysfs_files = os.listdir(sysfs_path)
    event_files = list(filter(lambda i: i.startswith("event"), sysfs_files))
    inputs += event_files

    if f"{vendor}:{product}" in keyboards_not_found:
        keyboards_not_found.remove(f"{vendor}:{product}")
    print(f"INFO: Found keyboard {vendor}:{product} with {len(event_files)} events: {' '.join(event_files)}")

input_paths = list(map(lambda i: f"/dev/input/{i}", inputs))

if arg_input in input_paths:
    print("INFO: Not launching input-sender")
    while True: pass


print(f"INFO: Launching {QUBES_INPUT_SENDER}")

os.execvp(QUBES_INPUT_SENDER, [QUBES_INPUT_SENDER, arg_rpc, arg_input, arg_domain])
[/details]

3. Changing the template of sys-usb

Troubleshooting