159 satır
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Çalıştırılabilir Dosya
		
	
	
			
		
		
	
	
			159 satır
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Çalıştırılabilir Dosya
		
	
	
#!/usr/bin/env python3
 | 
						|
from collections import defaultdict
 | 
						|
import os
 | 
						|
import subprocess
 | 
						|
 | 
						|
import click
 | 
						|
import yaml
 | 
						|
 | 
						|
 | 
						|
class FynnWacom:
 | 
						|
    def __init__(self, config, verbose=False):
 | 
						|
        self.config = config
 | 
						|
        self.verbose = verbose
 | 
						|
        self.validate_config()
 | 
						|
 | 
						|
    def validate_config(self):
 | 
						|
        try:
 | 
						|
            self.usbid = self.config['device']['usbid'].lower()
 | 
						|
            self.device_name = self.config['device']['name']
 | 
						|
            self.profile_file = os.path.expanduser(self.config['device']['profile_file'])
 | 
						|
 | 
						|
            # check default profile
 | 
						|
            default_profile = self.config['device']['default_profile']
 | 
						|
            if default_profile not in self.config['mappings']:
 | 
						|
                raise click.ClickException("Profile {} missing from mappigns".format(default_profile))
 | 
						|
 | 
						|
            # check mappings
 | 
						|
            for name, mapping in self.config['mappings'].items():
 | 
						|
                # does the baseprofile exist?
 | 
						|
                if 'base_profile' in mapping and mapping['base_profile'] not in self.config['mappings']:
 | 
						|
                    raise click.ClickException("Base profile {} not found for profile {}"
 | 
						|
                                               "".format(mapping['base_profile'], name))
 | 
						|
 | 
						|
                for key in mapping:
 | 
						|
                    if key in ('base_profile',):
 | 
						|
                        continue
 | 
						|
 | 
						|
                    pass
 | 
						|
 | 
						|
                # check recursion
 | 
						|
                pass
 | 
						|
        except KeyError as e:
 | 
						|
            raise click.ClickException("Key missing in config: {}".format(e))
 | 
						|
 | 
						|
    def get_current_profile(self):
 | 
						|
        try:
 | 
						|
            with open(self.profile_file) as pf:
 | 
						|
                return pf.read().strip()
 | 
						|
        except IOError:
 | 
						|
            return self.config['device']['default_profile']
 | 
						|
 | 
						|
    @property
 | 
						|
    def wheel_state(self):
 | 
						|
        """Read the wacom tablet wheel status from /sys, int in [0, 3]"""
 | 
						|
        basepath = '/sys/bus/hid/drivers/wacom/'
 | 
						|
        wacom_dirs = [d for d in os.listdir(basepath) if self.usbid in d.lower()]
 | 
						|
        if not wacom_dirs:
 | 
						|
            raise click.ClickException("Could not find usbid {} in {}".format(self.usbid, basepath))
 | 
						|
 | 
						|
        ledpath = os.path.join(basepath, sorted(wacom_dirs)[0], 'wacom_led/status_led0_select')
 | 
						|
 | 
						|
        with open(ledpath) as ledfile:
 | 
						|
            return int(ledfile.read())
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _call(cmd):
 | 
						|
        # print(" >> Calling: ", " ".join(cmd))
 | 
						|
        return subprocess.check_output(cmd, encoding='utf-8')
 | 
						|
 | 
						|
    def guess_devices(self, subname):
 | 
						|
        found = False
 | 
						|
        for device in self._call(["xinput", "list", "--name-only"]).split("\n"):
 | 
						|
            if not device.startswith(self.device_name):
 | 
						|
                continue
 | 
						|
            rest = device.replace(self.device_name, "")
 | 
						|
            if subname.lower() in rest.lower():
 | 
						|
                yield device
 | 
						|
                found = True
 | 
						|
 | 
						|
        if not found:
 | 
						|
            raise click.ClickException("Subdevice {} for {} not found".format(subname, self.device_name))
 | 
						|
 | 
						|
    def build_mapping(self, profile, wheel_state):
 | 
						|
        conf = self.config['mappings'][profile]
 | 
						|
        mapping = defaultdict(dict)
 | 
						|
        if 'base_profile' in conf:
 | 
						|
            mapping = self.build_mapping(conf['base_profile'], wheel_state)
 | 
						|
 | 
						|
        # copy mappings into mapping dict
 | 
						|
        for key, keymap in conf.items():
 | 
						|
            if key in ('base_profile', 'wheel'):
 | 
						|
                continue
 | 
						|
            mapping[key].update(keymap)
 | 
						|
 | 
						|
        # handle wheel, if present and in wheel_state range
 | 
						|
        if 'wheel' in conf and len(conf['wheel']) > wheel_state and conf['wheel'][wheel_state]:
 | 
						|
            for device, key_mapping in conf['wheel'][wheel_state].items():
 | 
						|
                mapping[device].update(key_mapping)
 | 
						|
 | 
						|
        return mapping
 | 
						|
 | 
						|
    def configure(self, profile):
 | 
						|
        if profile not in self.config['mappings']:
 | 
						|
            raise click.ClickException("Profile {} does not exist".format(profile))
 | 
						|
 | 
						|
        mapping = self.build_mapping(profile, self.wheel_state)
 | 
						|
 | 
						|
        if self.verbose:
 | 
						|
            print("Current mapping for profile {}: {}".format(profile, mapping))
 | 
						|
 | 
						|
        for subdevice, keymap in mapping.items():
 | 
						|
            for button, keysym in keymap.items():
 | 
						|
                if len(keysym) == 1:
 | 
						|
                    keysym = "key {}".format(keysym)
 | 
						|
                try:
 | 
						|
                    btn = ["Button", str(int(button))]
 | 
						|
                except ValueError:
 | 
						|
                    btn = [button]
 | 
						|
 | 
						|
                for device in self.guess_devices(subdevice):
 | 
						|
                    cmd = ["xsetwacom", "set", device] + btn + [keysym]
 | 
						|
                    if self.verbose:
 | 
						|
                        print(" >> xsetwacom set '{}' '{}' '{}'".format(device, btn, keysym))
 | 
						|
                    self._call(cmd)
 | 
						|
 | 
						|
        with open(self.profile_file, 'w') as f:
 | 
						|
            f.write(profile + "\n")
 | 
						|
 | 
						|
 | 
						|
@click.command()
 | 
						|
@click.option('-c', '--config', default='~/.config/fynncom/fynncom.yaml',
 | 
						|
              help='Path to config file')
 | 
						|
@click.option('-p', '--profile', default=None,
 | 
						|
              help='Profile to switch to')
 | 
						|
@click.option('-v', '--verbose', is_flag=True, default=False,
 | 
						|
              help='Be more verbose')
 | 
						|
def cli(config, profile, verbose):
 | 
						|
    # check for xinput / xsetwacom
 | 
						|
    for cmd in ('xinput', 'xsetwacom'):
 | 
						|
        if subprocess.call(["which", cmd], stdout=subprocess.DEVNULL) != 0:
 | 
						|
            raise click.ClickException("Could not find `{}`, please install it".format(cmd))
 | 
						|
 | 
						|
    # load config
 | 
						|
    try:
 | 
						|
        with open(os.path.expanduser(config)) as f:
 | 
						|
            config = yaml.safe_load(f)
 | 
						|
    except IOError as e:
 | 
						|
        raise click.ClickException("Could not load config: {}".format(e))
 | 
						|
 | 
						|
    fynncom = FynnWacom(config, verbose)
 | 
						|
    profile = profile or fynncom.get_current_profile()
 | 
						|
    fynncom.configure(profile)
 | 
						|
 | 
						|
    print("Switched to profile {} with weel-state {}".format(profile, fynncom.wheel_state))
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    cli()
 |