Add udev-device-plug-handler
This command runs in the background and listens for udev bind events, comparing the attributes of those events to known implemented devices. If any match, we call their handle() method. The same handle() method is called on startup if the presence of the device is detected via the check_available() method. Currently implemented devices are my Logitech G930 headset and my TEX Shinobi keyboard.
This commit is contained in:
parent
7729a8a9db
commit
2f9f69ec77
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
import setuptools
|
||||
|
||||
|
||||
setuptools.setup()
|
|
@ -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
|
Loading…
Reference in New Issue