Skip to content

Instantly share code, notes, and snippets.

@jboecker
Created September 8, 2015 02:45
Show Gist options
  • Save jboecker/deef671f940a244b1403 to your computer and use it in GitHub Desktop.
Save jboecker/deef671f940a244b1403 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
Python3 script to manage an external Lenovo LT1423p monitor
Requirements: xrandr, wmctrl, displaylink service
"""
LAPTOP_WACOM_DEVICES=[
"Wacom Serial Penabled 1FG Touchscreen stylus",
"Wacom Serial Penabled 1FG Touchscreen eraser"
]
LT1423P_WACOM_DEVICES=[
"Wacom HID Pen stylus",
"Wacom HID Pen eraser",
"ELAN Touchscreen"
]
WACOM_DEVICE_MAP={ "laptop": LAPTOP_WACOM_DEVICES,
"lt1423p": LT1423P_WACOM_DEVICES }
LAPTOP_OUTPUT_NAME="LVDS1"
LT1423P_OUTPUT_NAME="DVI-1-0"
MPXPOINTER_WINDOW_CLASSES=["xournal.Xournal"]
DISABLE_TOUCHSCREEN=True
DISABLE_HOVER_CLICK=True
import sys
import os
import subprocess
import re
import time
def teardown_mpx():
if subprocess.call(["xinput", "list", "Wacom pointer"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
os.system('xinput remove-master "Wacom pointer" AttachToMaster "Virtual core pointer" "Virtual core keyboard"')
def setup_mpx(wantMPX):
# first, remove MPX setup in any case, so this becomes an idempotent operation
teardown_mpx()
if wantMPX:
os.system('xinput create-master Wacom')
while 1:
if subprocess.call(["xinput", "list", "Wacom pointer"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
break
for device in LT1423P_WACOM_DEVICES:
os.system('xinput reattach "{0}" "Wacom pointer"'.format(device))
# set the correct client pointer for all xournal windows
clientPointer = "Virtual core pointer"
if wantMPX:
clientPointer = "Wacom pointer"
for line in subprocess.getoutput("wmctrl -lx").split("\n"):
windowId, desktopNumber, windowClass, *rest = line.split()
if windowClass in MPXPOINTER_WINDOW_CLASSES:
os.system('xinput --set-cp "{0}" "{1}"'.format(windowId, clientPointer))
def get_screen_size():
screen0line = subprocess.getoutput('xrandr -q | grep "^Screen 0"')
# e.g.: Screen 0: minimum 320 x 200, current 1024 x 768, maximum 8192 x 8192
width, height = map(int, re.match(r".*current (\d+) x (\d+),", screen0line).groups())
return (width, height)
def get_output_rect(outputname):
line = subprocess.getoutput('xrandr -q | grep "^{0}"'.format(outputname))
# e.g.: LVDS1 connected 1024x768+0+0 (normal left inverted right x axis y axis) 245mm x 184mm
width, height, xoffset, yoffset = map(int, re.match(r".* (\d+)x(\d+)\+(\d+)\+(\d+) ", line).groups())
return (width, height, xoffset, yoffset)
def get_output_rotation(outputname):
line = subprocess.getoutput('xrandr --verbose | grep "^{0}"'.format(outputname))
# e.g.: LVDS1 connected 768x1024+0+0 (0x43) left (normal left inverted right x axis y axis) 245mm x 184mm
rot = line.split(" ")[5]
if rot.startswith("("):
rot = "normal"
return rot
def set_wacom_output(outputname):
set_wacom_rect("laptop", *get_output_rect(outputname))
def set_wacom_rect(device_name, width, height, xoffset, yoffset):
# see http://www.x.org/wiki/XInputCoordinateTransformationMatrixUsage
screenwidth, screenheight = get_screen_size()
SCALE_X=float(width)/screenwidth
SCALE_Y=float(height)/screenheight
TRANSLATE_X=xoffset/screenwidth
TRANSLATE_Y=yoffset/screenheight
matrix = "{0[SCALE_X]} 0 {0[TRANSLATE_X]} 0 {0[SCALE_Y]}f {0[TRANSLATE_Y]} 0 0 1".format(locals())
for device in WACOM_DEVICE_MAP[device_name]:
cmd = 'xinput set-prop "{0}" "Coordinate Transformation Matrix" '.format(device) + matrix
os.system(cmd)
def wait_for_lt1423p_devices():
sys.stdout.write("waiting for LT1423P input devices...")
sys.stdout.flush()
while 1:
devices_found = 0
devices_wanted = len(LT1423P_WACOM_DEVICES)
for device in LT1423P_WACOM_DEVICES:
if (subprocess.call(["xinput", "list", device], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0):
devices_found += 1
if devices_found == devices_wanted:
sys.stdout.write("\n")
return
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(0.1)
if __name__=="__main__":
if len(sys.argv) <= 1:
print("""Usage: lt1423p.py <on|setcp|off>
Warning: DisplayLink and/or multi-pointer X support is not very stable.
To avoid crashing the X server, you should follow this sequence of actions precisely.
(And don't try to rotate the image on your LT1423p, either...)
Connecting:
* plug in the LT1423p and wait for its boot sequence to finish
* run "lt1423p.py on"
* Launch your xournal windows, run "lt1423p.py setcp"
Disconnecting:
* run "lt1423p.py off" and unplug the monitor when it tells you to
""")
else:
if sys.argv[1] == "on":
print('Plug in the LT1423p and wait for the "Power Saving Mode" message, then press return.')
dummy=input()
os.system("xrandr --setprovideroutputsource 1 0")
os.system("xrandr --output {0} --auto --below {1}".format(LT1423P_OUTPUT_NAME, LAPTOP_OUTPUT_NAME))
wait_for_lt1423p_devices()
if DISABLE_HOVER_CLICK:
os.system('xinput --set-prop "Wacom HID Pen stylus" "Wacom Hover Click" 0')
if DISABLE_TOUCHSCREEN:
os.system('xinput disable "ELAN Touchscreen"') # disable the touchscreen on the LT1423p
set_wacom_rect("laptop", *get_output_rect(LAPTOP_OUTPUT_NAME))
set_wacom_rect("lt1423p", *get_output_rect(LT1423P_OUTPUT_NAME))
setup_mpx(True)
elif sys.argv[1] == "setcp":
setup_mpx(True)
elif sys.argv[1] == "off":
setup_mpx(False)
print("Save all your work before you continue!")
print("To reduce the chance of crashing the X server, close all applications that")
print("were using MPX before running this script.")
print("")
print("waiting for monitor to be disconnected...")
while 1:
if "{0} disconnected".format(LT1423P_OUTPUT_NAME) in subprocess.getoutput("xrandr -q"):
break
os.system("xrandr --output {0} --off".format(LT1423P_OUTPUT_NAME))
#print("xrandr --output {0} --off".format(LT1423P_OUTPUT_NAME))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment