Initial commit
This commit is contained in:
commit
4833016d47
|
@ -0,0 +1,5 @@
|
|||
fynncom.egg-info
|
||||
fynncom.yaml
|
||||
*.swp
|
||||
__pycache__
|
||||
*.pyc
|
|
@ -0,0 +1,61 @@
|
|||
# Fynncomm - Wacom configuration for Fynn's tablet
|
||||
This is a quick and dirty wacon configuration tool - basically a glorified
|
||||
shellscript with a config in python.
|
||||
|
||||
Features:
|
||||
* freely configure ALL the buttons
|
||||
* quickly change between different button profiles
|
||||
* change button mappings based on wheel led status
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
$ pip install git+https://git.someserver.de/seba/fynnwacom/
|
||||
```
|
||||
|
||||
## Configuration
|
||||
An example configuration can be found in the git repository.
|
||||
|
||||
For Fynncom to find the correct device a name and a usb id has to be configured.
|
||||
The name can be found in `xinput list` and is just the part of the device wihout
|
||||
a Pen/Pad suffix, e.g. for `Wacom Intuos5 touch M Pen stylus` the name would be
|
||||
`Wacom Intuos5 touch M`.
|
||||
The usbid can be found with `lsusb | grep -i wacom` and looks something like
|
||||
`056a:0027`.
|
||||
|
||||
Available buttons can be found with `xev`. In the mappings section of the config
|
||||
multiple profiles can be defined. Inside a profile a device that matches any of
|
||||
the existing wacom inputs has to be defined, e.g. `pen`, `pad` or `touch`. Inside
|
||||
this "subinterface" a mapping from button codes to keycodes can be defined. Single
|
||||
characters are automatically prefixed with "key".
|
||||
|
||||
To get the wheel to change functionality (effectively applying the wheel mappings
|
||||
from the configuration file), button 1 should be mapped to a shortcut for your
|
||||
windowmanager to launch `fynncom`.
|
||||
|
||||
## Autoconfig on login
|
||||
The easiest way is to call `fynncom` from the commandline after the tablet has
|
||||
been plugged in.
|
||||
|
||||
Another possibility would be to create a systemd oneshot service that is triggered
|
||||
by a Udev rule.
|
||||
|
||||
Udev rule to trigger the service in `/etc/udev/rules.d/81-fynncom.rules`:
|
||||
```
|
||||
ACTION=="add", SUBSYSTEM=="hid", ATTRS{idVendor}=="056a", ATTRS{idProduct}=="0027", \
|
||||
TAG+="systemd", ENV{SYSTEMD_WANTS}="fynncom.service"
|
||||
```
|
||||
|
||||
Systemd service in `/etc/systemd/system/fynncom.service`:
|
||||
```
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Environment="DISPLAY=:0"
|
||||
ExecStartPre=/bin/sleep 2
|
||||
User=YOUR_USER
|
||||
Group=YOUR_USERS_GROUP
|
||||
ExecStart=path/to/fynncom -c path/to/fynncomm/fynncom.yaml
|
||||
```
|
||||
|
||||
Don't forget to reload systemd with `systemctl daemon-reload`.
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
device:
|
||||
# name as found in `xinput list` (without Pen/Pad/Finger)
|
||||
# Example: Wacom Intuos5 touch M
|
||||
name: Wacom Intuos5 touch M
|
||||
|
||||
# usbid, as found in `lsusb`
|
||||
# Example: 056a:0027
|
||||
usbid: 056a:0027
|
||||
|
||||
default_profile: default
|
||||
profile_file: ~/.config/fynncom/last_profile
|
||||
|
||||
mappings:
|
||||
# profile named default
|
||||
default:
|
||||
# all Wacom devices with "pen" in the name
|
||||
pen:
|
||||
# make pen absolute to this screen (can be found with xrandr)
|
||||
maptooutput: 1920x1200+1920+0
|
||||
|
||||
# all Wacom devices with pad in name (usually just one)
|
||||
pad:
|
||||
# upper four buttons
|
||||
2: '1'
|
||||
3: '2'
|
||||
8: '3'
|
||||
9: '4'
|
||||
|
||||
# wheel + button
|
||||
# to get the wheel to work, this should be a shortcut for your
|
||||
# window manager launching fynncom to refresh the mappings
|
||||
1: 'key ctrl alt shift c' # middle
|
||||
# This setting will be overwritten by the wheel config lower
|
||||
AbsWheelDown: '5'
|
||||
AbsWheelUp: '7'
|
||||
|
||||
# lower four buttons
|
||||
10: '8'
|
||||
11: '9'
|
||||
12: '0'
|
||||
13: 'A'
|
||||
|
||||
# this configuration is applied depending upon the tablet's wheel state
|
||||
wheel:
|
||||
- pad:
|
||||
AbsWheelUp: 'B'
|
||||
AbsWheelDown: 'C'
|
||||
- pad:
|
||||
AbsWheelUp: 'D'
|
||||
AbsWheelDown: 'E'
|
||||
- pad:
|
||||
AbsWheelUp: 'F'
|
||||
AbsWheelDown: 'G'
|
||||
- pad:
|
||||
AbsWheelUp: 'H'
|
||||
AbsWheelDown: 'I'
|
||||
|
||||
# profile named gimp inheriting from default
|
||||
gimp:
|
||||
base_profile: 'default'
|
|
@ -0,0 +1,158 @@
|
|||
#!/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()
|
|
@ -0,0 +1,2 @@
|
|||
click
|
||||
pyyaml
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
setup(name='fynncom',
|
||||
version='0.1.0',
|
||||
description='Wacom tablet button configuration tool',
|
||||
author='Sebastian Lohff',
|
||||
author_email='seba@someserver.de',
|
||||
url='https://git.someserver.de/seba/fynnwacom',
|
||||
packages=['fynncom'],
|
||||
install_requires=['click', 'pyyaml'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'fynncom = fynncom.fynncom:cli'
|
||||
]
|
||||
},
|
||||
)
|
Loading…
Reference in New Issue