106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
#!/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
|