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
 |