132 lines
4.9 KiB
Python
Executable File
132 lines
4.9 KiB
Python
Executable File
#!/usr/bin/env python2
|
|
import os
|
|
import os.path
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
from collections import namedtuple
|
|
|
|
devicename = 'NTRG0001:01 1B96:1B05'
|
|
freq = 5.0
|
|
|
|
MONITOR_CONNECTED_RE = re.compile(r'\bconnected\b')
|
|
DIGITIZER_RE = re.compile(r'(NTRG0001\:01\s+1B96\:1B05|Microsoft Surface Type Cover Touchpad)')
|
|
PEN_RE = re.compile(r'\bPen (stylus|eraser)\b')
|
|
PROXIMITY_RE = re.compile(r'\bProximity\=(\w+)', re.M)
|
|
|
|
current_orientation = ''
|
|
currently_proximate = False
|
|
xinput_statemap = {True: 'disable', False: 'enable'}
|
|
|
|
|
|
def find_accelerometers():
|
|
spath = '/sys/bus/iio/devices/'
|
|
dpath = [os.path.join(spath, p) for p in os.listdir('/sys/bus/iio/devices/')
|
|
if re.match('iio\:device\d', p) and os.path.exists(os.path.join(spath, p, 'in_accel_scale'))]
|
|
if not dpath:
|
|
sys.stderr.write("Could not find iio device to monitor.")
|
|
sys.exit(1)
|
|
|
|
return (os.path.join(dpath[0], 'in_accel_x_raw'),
|
|
os.path.join(dpath[0], 'in_accel_y_raw'))
|
|
|
|
|
|
x_accel_path, y_accel_path = find_accelerometers()
|
|
|
|
|
|
def refreshtouch():
|
|
os.system('xinput disable "NTRG0001:01 1B96:1B05"')
|
|
os.system('xinput enable "NTRG0001:01 1B96:1B05"')
|
|
|
|
|
|
def countdisplays():
|
|
return int(len([l for l in subprocess.check_output(['xrandr', '--current']).splitlines()
|
|
if MONITOR_CONNECTED_RE.search(l)]))
|
|
|
|
|
|
# Landscape
|
|
|
|
# The 'X' means to read the 'x_accel_path' register, since that's what
|
|
# we need to determine landscape (ls) or portrait (pt) mode. 'N' is
|
|
# 'Normal' and 'I' is 'Inverted.' The magic numbers for the 'X' are
|
|
# around a 65536-based device register; I'm guessing Poel worked with
|
|
# them by just dumping them to the screen while tilting the screen
|
|
# this way and that. They seem to work just fine.
|
|
|
|
def lsx(thex, they): return thex >= 65000 or thex <= 650
|
|
def lsn(thex, they): return they <= 65000 and they >= 64000
|
|
def lsi(thex, they): return they >= 650 and they <= 1100
|
|
|
|
# Portrait: L for "Left" and R for "Right."
|
|
|
|
def ptx(thex, they): return not lsx(thex, they)
|
|
def ptl(thex, they): return thex >= 800 and thex <= 1000
|
|
def ptr(thex, they): return thex >= 64500 and thex <= 64700
|
|
|
|
# It's a little odd that X labels it 'left' when you've just turned
|
|
# the tablet to the right, and vice versa, but that's the convention,
|
|
# I guess.
|
|
|
|
|
|
Transform = namedtuple('Tranform', ['name', 'mode', 'matrix', 'xrule', 'yrule'])
|
|
|
|
|
|
transforms = [
|
|
Transform('normal', 0, '1 0 0 0 1 0 0 0 1', lsx, lsn),
|
|
Transform('inverted', 1, '-1 0 1 0 -1 1 0 0 1', lsx, lsi),
|
|
Transform('left', 2, '0 -1 1 1 0 0 0 0 1', ptx, ptl),
|
|
Transform('right', 3, '0 1 0 -1 0 1 0 0 1', ptx, ptr)
|
|
]
|
|
|
|
|
|
def is_in(pen):
|
|
res = PROXIMITY_RE.search(subprocess.check_output(['xinput', 'query-state', pen]))
|
|
return (res and res.group(1).lower() == 'in')
|
|
|
|
|
|
def manage_orientation_and_palm_rejection(options):
|
|
current_orientation = ''
|
|
currently_proximate = False
|
|
|
|
while True:
|
|
int_displays = countdisplays()
|
|
time.sleep(1.0/freq)
|
|
if int_displays == 1:
|
|
|
|
# Check accelerometers
|
|
# Do we need to check the touch_devices list every time? I
|
|
# think we do; the list will change dynamically if we
|
|
# dynamically load the stylus driver after the system has
|
|
# booted.
|
|
touch_devices = filter(lambda n: DIGITIZER_RE.match(n),
|
|
subprocess.check_output(['xinput', '--list', '--name-only']).splitlines())
|
|
with open(x_accel_path, 'r') as fx:
|
|
with open(y_accel_path, 'r') as fy:
|
|
thex = float(fx.readline())
|
|
they = float(fy.readline())
|
|
for check in transforms:
|
|
if check.xrule(thex, they) and check.yrule(thex, they):
|
|
if current_orientation != check.name:
|
|
print "Switching to orientation %s" % check.name
|
|
os.system('xrandr -o %s' % check.name)
|
|
for device in touch_devices:
|
|
os.system("xinput set-prop '%s' 'Coordinate Transformation Matrix' %s" %
|
|
(device, check.matrix))
|
|
current_orientation = check.name
|
|
refreshtouch()
|
|
break
|
|
|
|
# Palm rejection (sort-of):
|
|
pen_devices = [p for p in touch_devices if PEN_RE.search(p)]
|
|
pen_status = bool([p for p in pen_devices if is_in(p)])
|
|
if pen_status != currently_proximate:
|
|
print "%s palm rejection" % ("Activating" if pen_status else "Deactivating")
|
|
currently_proximate = pen_status
|
|
os.system("xinput %s '%s'" % (xinput_statemap[pen_status], devicename))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
manage_orientation_and_palm_rejection(sys.argv[1:])
|