Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@timothyb89
Last active October 13, 2020 04:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save timothyb89/addfa3483ca81c581841 to your computer and use it in GitHub Desktop.
Save timothyb89/addfa3483ca81c581841 to your computer and use it in GitHub Desktop.
A small Python utility to easily set wacom tablet settings

A quick and dirty Python utility to set wacom tablet settings.

To use, run with no arguments. Options are configured in init_wacom(). Make sure to pip install sh first.

Button mappings can either be a key or mouse button sequence, or a Python function to be executed.

For function actions, write the logic, run wacom, and open your keybinding application choice - the keyboard shortcut settings in GNOME or KDE work well. Configure a new mapping to run wacom [button], where [button] is the button number you're trying to configure. Pushing the button will output the key sequence for you.

Running wacom monitor will watch for new input devices as they are inserted and removed from the system. If a device matching any of the defined devices (stylus, eraser, touch, pad) is inserted, it will automatically run init_wacom. For best results, add it as a startup script to your session.

#!/usr/bin/env python
import os
import sys
import time
import runpy
import wacom_lib
from pyudev import Context, Monitor, MonitorObserver
from datetime import datetime
from sh import notify_send
MIN_REINIT_SECS = 5
DELAY_SECS = 1
_last_trigger = None
def init_wacom(set_profile=True):
print 'Initializing...'
if 'wacom_config' in sys.modules:
reload(sys.modules['wacom_config'])
else:
__import__('wacom_config')
if set_profile and not wacom_lib._current_profile:
wacom_lib.set_profile(wacom_lib._default_profile)
print 'Finished wacom init.'
def reload_stored_profile():
profile = wacom_lib.get_stored_profile()
if profile:
wacom_lib.set_profile(profile)
print "Using saved profile for button action"
def monitor_event(action, device):
global _last_trigger
if _last_trigger is not None and time.time() - _last_trigger < MIN_REINIT_SECS:
return
name = device.get("NAME")
if not name:
return
name = name.replace('"', '') # :/
print device.action, name
if any([ x.startswith(name) for x in (wacom_lib.all_inputs) ]):
if action == "add":
print "Wacom tablet added:", name
time.sleep(DELAY_SECS)
wacom_lib.get_active_targets(refresh=True)
reload_stored_profile()
init_wacom()
_last_trigger = time.time()
notify_send("Tablet Attached",
"Device '%s' has been initialized." % name,
icon="input-tablet",
expire_time=2000)
elif action == "remove":
_last_trigger = time.time()
print "Wacom tablet removed:", name
notify_send("Tablet Removed",
"Device '%s' has been disconnected." % name,
icon="input-tablet",
expire_time=2000)
def monitor():
if wacom_lib.get_active_targets().intersection(wacom_lib.stylus):
print "Device already connected, initializing ..."
init_wacom()
ctx = Context()
mon = Monitor.from_netlink(ctx)
mon.filter_by("input")
obs = MonitorObserver(mon, event_handler=monitor_event)
#obs.daemon = False
obs.start()
print "Monitoring input devices..."
try:
while obs.isAlive:
obs.join(1)
except KeyboardInterrupt:
sys.exit(0)
print "done"
def main():
if len(sys.argv) > 1:
arg = sys.argv[1]
if arg == "monitor":
monitor()
else:
wacom_lib.SIMULATE = True
init_wacom(set_profile=False)
if sys.argv[1] in wacom_lib._profiles:
wacom_lib.SIMULATE = False
wacom_lib.set_profile(sys.argv[1])
else:
reload_stored_profile()
wacom_lib.SIMULATE = False
wacom_lib._action_map[int(sys.argv[1])]()
else:
init_wacom()
if __name__ == '__main__':
main()
from wacom_lib import *
@profile(default=True)
def touchpad():
set_button(bottom_right, lambda: set_profile(pen))
set(touch + stylus, "rotate", "ccw")
set(touch, 'Touch', 'on')
@profile()
def pen():
set_button(bottom_right, lambda: set_profile(touchpad))
set(touch + stylus, "rotate", "none")
set(touch, 'Touch', 'off')
set(stylus + eraser, 'threshold', 20)
set_button(bottom_left, '1')
set_button(top_left, '3')
set_button(top_right, 'key lsuper')
xinput_set(touch, "Device Accel Constant Deceleration", 2.0)
import os
from collections import Callable
from functools import partial
from sh import xsetwacom, xinput, xrandr, notify_send
SIMULATE = False
_action_map = {}
_profiles = {}
_default_profile = None
_current_profile = None
top_left = 3
top_right = 9
bottom_left = 1
bottom_right = 8
stylus = [
"Wacom Intuos PT S Pen stylus",
"Wacom Intuos PT S (WL) Pen stylus"
]
eraser = [
"Wacom Intuos PT S Pen eraser",
"Wacom Intuos PT S Pen stylus"
]
touch = [
"Wacom Intuos PT S Finger touch",
"Wacom Intuos PT S (WL) Finger touch"
]
pad = [
"Wacom Intuos PT S Pad pad",
"Wacom Intuos PT S (WL) Pad pad"
]
all_inputs = stylus + eraser + touch + pad
_active_targets = None
_displays = { line.split()[0] for line in xrandr() if not line.startswith(' ') }
_displays.remove('Screen')
_dir = os.path.dirname(os.path.abspath(__file__))
_profile_store = os.path.join(_dir, ".wacom_profile")
def set(targets, prop, value):
if SIMULATE:
return
print 'active targets:', get_active_targets().intersection(targets)
for target in get_active_targets().intersection(targets):
print 'xsetwacom: %s=%r on %s' % (prop, value, target)
xsetwacom.set(target, prop, value)
def toggle(targets, prop, **mappings):
if not mappings:
mappings = {
'on': 'off',
'off': 'on',
'0': '1',
'1': '0'
}
for target in get_active_targets().intersection(targets):
old = str(xsetwacom.get(target, prop)).strip()
if old in mappings:
xsetwacom.set(target, prop, mappings[old])
else:
print "WARN: unknown value to toggle:", old
def set_button(button, action):
if isinstance(action, Callable):
_action_map[button] = action
action = 'key +ctrl +super +shift +alt %d -alt -shift -super -ctrl' % button
if SIMULATE:
return
for target in get_active_targets().intersection(pad):
print 'xsetwacom: button %d="%s" on %s' % (button, action, target)
xsetwacom.set(target, 'Button', button, action)
def xinput_set(targets, prop, value, type="float"):
if SIMULATE:
return
for target in get_active_targets().intersection(targets):
print 'xinput: %s=%r (%s) on %s' % (prop, value, type, target)
xinput('set-prop', target, prop, value, type=type)
def get_active_targets(refresh=False):
global _active_targets
if not _active_targets or refresh:
_active_targets = { target.strip() for target in xinput.list(name_only=True) }
return _active_targets
def profile(default=False, name=None):
def wrap(func):
global _default_profile
_name = name
if not _name:
_name = func.func_name
_profiles[_name] = func
if not _default_profile or default:
_default_profile = _name
return func
return wrap
def reverse_lookup_profile(search_func):
for profile_name, profile_func in _profiles.iteritems():
if profile_func == search_func:
return profile_name
return None
def get_stored_profile():
if not os.path.exists(_profile_store):
return None
with open(_profile_store, 'r') as f:
return f.read().strip()
def set_profile(profile):
global _current_profile
print 'Setting current profile to', profile
if isinstance(profile, basestring):
if profile in _profiles:
_current_profile = profile
_profiles[profile]()
else:
print 'WARNING: Profile not found:', profile
return
else:
_current_profile = reverse_lookup_profile(profile)
profile()
with open(_profile_store, 'w') as f:
f.write(_current_profile)
if not SIMULATE:
notify_send("Profile Changed",
"Profile set to '%s'." % _current_profile,
icon="input-tablet",
expire_time=2000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment