Last active
August 29, 2015 14:04
-
-
Save nocnokneo/3722097d2fc29b0d676a to your computer and use it in GitHub Desktop.
multidispay_touchscreen_autoconfig.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copy this file to /etc/xdg/autostart/ | |
# multidisplay_touchscreen_autoconfig.py must be executable and in your system PATH | |
[Desktop Entry] | |
Exec=multidisplay_touchscreen_autoconfig.py | |
Name=Autoconfigure touchscreens for use on a multi-monitor system | |
Type=Application | |
NoDisplay=true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import subprocess | |
import re | |
import binascii | |
import logging | |
# A dictionary that maps (vendorId,productId) => touchscreenDevice | |
# TODO: Make configurable | |
touchscreen_db = { ('ACR', 0x9703) : 'Advanced Silicon S.A CoolTouch(TM) System' | |
('FIM', 0x6145) : 'USB Touchscreen 0dfc:0001' } | |
# Copied from edid.py in Chromium OS | |
def parse_edid(blob): | |
"""EDID Parser (light-weight parse-edid replacement). | |
Simple parsing of EDID. The full-feature parser (parse-edid) has | |
many more dependencies, and so is too heavy-weight for use here. | |
TODO(hungte) Use parse-edid if it becomes practical/available. | |
Specifically once we know that it will be available on all of our | |
systems. | |
Args: | |
blob: a binary blob with encoded EDID. | |
Returns: | |
A dict of extracted keys to extracted EDID fields. Return None if | |
the blob is not a valid EDID record, and also log warning messages | |
indicating the reason for parsing failure. | |
""" | |
def read_short(offset): | |
return ((ord(blob[offset]) << 8) | ord(blob[offset + 1])) | |
# Constants lifted from EDID documentation. | |
VERSION = 0x01 | |
MAGIC = '\x00\xff\xff\xff\xff\xff\xff\x00' | |
MAGIC_OFFSET = 0 | |
MANUFACTURER_ID_OFFSET = 8 | |
PRODUCT_ID_OFFSET = 10 | |
VERSION_OFFSET = 18 | |
REVISION_OFFSET = 19 | |
PIXEL_CLOCK_OFFSET = 54 | |
HORIZONTAL_OFFSET = 56 | |
HORIZONTAL_HIGH_OFFSET = 58 | |
VERTICAL_OFFSET = 59 | |
VERTICAL_HIGH_OFFSET = 61 | |
CHECKSUM_OFFSET = 127 | |
MINIMAL_SIZE = 128 | |
MANUFACTURER_ID_BITS = 5 | |
# Check size, magic, and version | |
if len(blob) < MINIMAL_SIZE: | |
logging.warning("EDID parsing error: length too small.") | |
return None | |
if (blob[MAGIC_OFFSET:(MAGIC_OFFSET + len(MAGIC))] != MAGIC): | |
logging.warning("EDID parse error: incorrect header.") | |
return None | |
if ord(blob[VERSION_OFFSET]) != VERSION: | |
logging.warning("EDID parse error: unsupported EDID version.") | |
return None | |
# Verify checksum | |
if sum([ord(char) for char in blob[:CHECKSUM_OFFSET+1]]) % 0x100 != 0: | |
logging.warning("EDID parse error: checksum error.") | |
return None | |
# Currently we don't support EDID not using pixel clock | |
pixel_clock = read_short(PIXEL_CLOCK_OFFSET) | |
if not pixel_clock: | |
logging.warning("EDID parse error: " | |
"non-pixel clock format is not supported yet.") | |
return None | |
# Extract manufactuer | |
vendor_name = '' | |
vendor_code = read_short(MANUFACTURER_ID_OFFSET) | |
# vendor_code: [0 | char1 | char2 | char3] | |
for i in range(2, -1, -1): | |
vendor_char = (vendor_code >> (i * MANUFACTURER_ID_BITS)) & 0x1F | |
vendor_char = chr(vendor_char + ord('@')) | |
vendor_name += vendor_char | |
product_id = read_short(PRODUCT_ID_OFFSET) | |
width = (ord(blob[HORIZONTAL_OFFSET]) | | |
((ord(blob[HORIZONTAL_HIGH_OFFSET]) >> 4) << 8)) | |
height = (ord(blob[VERTICAL_OFFSET]) | | |
((ord(blob[VERTICAL_HIGH_OFFSET]) >> 4) << 8)) | |
return { 'vendor': vendor_name, | |
'product_id': product_id, | |
'width': width, | |
'height': height } | |
def connected_display_hardware(): | |
"""Returns a list of tuples that map outputs to the corresponding connected display hardware | |
hardware. For example: | |
>>> connected_display_hardware() | |
[('DP-0', ('SAM', 44545)), ('DP-3', ('ACR', 38659))] | |
""" | |
xrandr_prop = subprocess.check_output(['xrandr', '--prop']) | |
# Regex that matches two groups: | |
# (1) The display device name | |
# (2) The EDID ascii hex string (with spaces) | |
display_edid_regex = re.compile(r"^(\S+) connected .*\n^\s*EDID: *((?:\n\t\t[\da-fA-F]{32})*)", re.MULTILINE) | |
display_edid_iter = display_edid_regex.finditer(xrandr_prop) | |
supported_touchscreen_found = False | |
connections = [] | |
for match in display_edid_iter: | |
output, edid_ascii = match.groups() | |
edid_blob = binascii.unhexlify(re.sub(r"\s*", "", edid_ascii)) | |
edid = parse_edid(edid_blob) | |
connections.append( (output, (edid['vendor'], edid['product_id'])) ) | |
return connections | |
def main(): | |
print "Establishing correspondance between display and touch input devices ..." | |
for output, display in connected_display_hardware(): | |
if touchscreen_db.has_key(display): | |
touch_input_device = touchscreen_db[display] | |
try: | |
touch_input_device_id = subprocess.check_output(['xinput', '--list', '--id-only', touch_input_device]).strip() | |
except subprocess.CalledProcessError: | |
logging.warning("Could not find touch input device '%s' for display %s. Is it connected?" % (touch_input_device, display)) | |
continue | |
print "\t%s => %s" % (display, touch_input_device) | |
subprocess.check_call(['xinput', '--map-to-output', touch_input_device_id, output]) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment