#!/usr/bin/env python3 import subprocess from typing import Dict, List, Optional, Union import click import pyudev # type: ignore class KnownDevice(): """Base class for implementing any device""" # a map of udev-provided attributes and their values this device should match to UDEV_PROPS: Dict[str, str] = {} @staticmethod def check_available() -> bool: """Check if that device is connected. This method will be run on startup to check if we need to handle the device. """ raise NotImplementedError @staticmethod def handle(device: Optional[pyudev.Device] = None) -> None: """Handle a device being plugged or available This method will be called on finding an device to be bound by udev with the `device` argument. It will also be called on startup without the device argument. """ raise NotImplementedError class UsbHidKeyboardMouse(KnownDevice): """Represent the mechanical thinkpack-like keyboard's trackpoint""" UDEV_PROPS = {'ID_MODEL': 'USB-HID_Keyboard'} @staticmethod def check_available() -> bool: cmd = "xinput | grep pointer | grep 'USB-HID Keyboard Mouse'" cp = subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL) return cp.returncode == 0 @staticmethod def handle(device: Optional[pyudev.Device] = None) -> None: subcmd = "xinput | grep pointer | grep 'USB-HID Keyboard Mouse' | " \ r"sed -r 's/.*[[:space:]]id=([[:digit:]]+)[[:space:]].*/\1/'" cmd: Union[str, List[str]] = f"xinput set-prop $({subcmd}) 'libinput Middle Emulation Enabled' 1" subprocess.run(cmd, shell=True) cmd = ['xset', 'r', 'rate', '195', '25'] subprocess.run(cmd) print("Handled USB-HID_Keyboard") class UsbLogitechG930(KnownDevice): """Represent the Logitech G930 headset's keys""" UDEV_PROPS = {'ID_MODEL': 'Logitech_G930_Headset'} @staticmethod def check_available() -> bool: cmd = "xinput | grep keyboard | grep 'Logitech G930 Headset'" cp = subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL) return cp.returncode == 0 @staticmethod def handle(device: Optional[pyudev.Device] = None) -> None: cmd = ['/home/joker/scripting/remap_headset_keys.sh'] subprocess.run(cmd) print("Handled Logitech G930 Headset") @click.command() @click.option('--debug', is_flag=True, help='Show all device changes instead of only handled.') def main(debug: bool) -> None: # do all actions at startup for devices we might have missed for known_device in KnownDevice.__subclasses__(): if not known_device.check_available(): continue known_device.handle() context = pyudev.Context() monitor = pyudev.Monitor.from_netlink(context) monitor.filter_by(subsystem='usb') monitor.start() device: pyudev.Device for device in iter(monitor.poll, None): if device.action != 'bind': continue for known_device in KnownDevice.__subclasses__(): if not all(device.properties.get(prop) == prop_value for prop, prop_value in known_device.UDEV_PROPS.items()): continue known_device.handle(device=device) break else: if debug: print(dict(device)) if __name__ == '__main__': import sys try: sys.exit(main()) except KeyboardInterrupt: pass