diff --git a/udev-device-plug-handler/setup.cfg b/udev-device-plug-handler/setup.cfg new file mode 100644 index 0000000..17824c8 --- /dev/null +++ b/udev-device-plug-handler/setup.cfg @@ -0,0 +1,15 @@ +[metadata] +name = udev-device-plug-handler +version = 0.1 +description = Supposed to be background application waiting for devices to change and running scripts for them + +[options] +scripts = udev-device-plug-handler +install_requires = + click + pyudev + +[flake8] +max-line-length = 120 +exclude = .git,__pycache__,*.egg-info,*lib/python* +ignore = E241,E741,W503,W504 diff --git a/udev-device-plug-handler/setup.py b/udev-device-plug-handler/setup.py new file mode 100644 index 0000000..056ba45 --- /dev/null +++ b/udev-device-plug-handler/setup.py @@ -0,0 +1,4 @@ +import setuptools + + +setuptools.setup() diff --git a/udev-device-plug-handler/udev-device-plug-handler b/udev-device-plug-handler/udev-device-plug-handler new file mode 100644 index 0000000..f1a5860 --- /dev/null +++ b/udev-device-plug-handler/udev-device-plug-handler @@ -0,0 +1,105 @@ +#!/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