--- /dev/null
+#!/usr/bin/python
+"""
+Configure displays by looking at xrandr information
+
+Select the best 1 or 2 xrandr outputs to use as displays, based on xrandr
+information, then reconfigure outputs via xrandr.
+"""
+
+import sys
+import subprocess
+import re
+
+dry_run = False
+
+class OutputInfo():
+ def __init__(self):
+ self.clear()
+
+ def clear(self):
+ self.name = None
+ self.connected = False
+ self.enabled = False
+ self.width = -1
+ self.height = -1
+ self.posx = -1
+ self.posy = -1
+
+ def complete(self):
+ if self.name and (self.connected == False or self.width != -1):
+ return True
+ else:
+ return False
+
+ def asDict(self):
+ if self.complete():
+ return {'name':self.name, 'enabled':self.enabled,
+ 'connected':self.connected, 'width':self.width,
+ 'height':self.height, 'posx':self.posx, 'posy':self.posy,
+ 'use':False, 'done':False}
+ else:
+ raise ValueError
+
+ def parse(self, line):
+ tokens = line.split()
+ if len(tokens):
+ if tokens[1] == 'connected' or tokens[1] == 'disconnected':
+ self.clear()
+ self.name = tokens[0]
+ if tokens[1] == 'connected':
+ self.connected = True
+ token = 2
+ size = []
+ while token < len(tokens) and len(size) != 4:
+ # skip other tokens like 'primary'
+ size = re.split('[x+]', tokens[token])
+ token += 1
+ if len(size) == 4:
+ self.enabled = True
+ #self.width = int(size[0])
+ #self.height = int(size[1])
+ #self.posx = int(size[2])
+ #self.posy = int(size[3])
+ elif self.connected and self.width < 0:
+ size = re.split('x', tokens[0])
+ if len(size) == 2:
+ self.width = int(size[0])
+ self.height = int(size[1])
+
+
+def parse():
+ outputs = []
+ output = OutputInfo()
+ p = subprocess.check_output('xrandr')
+ for line in p.split('\n'):
+ output.parse(line)
+ if output.complete():
+ outputs.append(output.asDict())
+ output.clear()
+ return outputs
+
+
+def xrandr_on(output):
+ command = ['xrandr', '--output', output['name'], '--auto', '--pos',
+ '%dx0' % output['posx']]
+ if output['posx'] == 0:
+ command += ['--primary']
+ if not dry_run:
+ print 'xrandr_on:', command
+ subprocess.call(command)
+ else:
+ print '(dry-run) xrandr_on:', command
+
+
+def xrandr_off(output):
+ command = ['xrandr', '--output', output['name'], '--off']
+ if not dry_run:
+ subprocess.call(command)
+ print 'xrandr_off:', command
+ else:
+ print '(dry-run) xrandr_off:', command
+
+
+def output_by_name(outputs, name):
+ for output in outputs:
+ if output['name'] == name:
+ return output
+ return None
+
+
+def print_outputs(header, outputs):
+ print '%s:' % header
+ for output in outputs:
+ print_output(output)
+ print
+
+
+def all_done(outputs):
+ for output in outputs:
+ if not output['done']:
+ return False
+ return True
+
+
+def disable_an_entry(outputs):
+ for output in outputs:
+ if not output['done'] and not output['use'] and output['enabled']:
+ xrandr_off(output)
+ output['done'] = True
+ return 1
+ return 0
+
+
+def enable_an_entry(outputs, new_enable):
+ enables = 0
+ for output in reversed(outputs):
+ if not output['done'] and output['use']:
+ if new_enable or output['connected']:
+ xrandr_on(output)
+ output['done'] = True
+ return 1
+ return 0
+
+
+if __name__ == '__main__':
+ def print_output(output):
+ print 'name:%s connected:%r enabled:%r width:%d height:%d pos:%dx%d' % (
+ output['name'], output['connected'], output['enabled'],
+ output['width'], output['height'], output['posx'],
+ output['posy'])
+
+ def main(argv):
+ global dry_run
+ if len(argv) >= 2 and argv[1] == '--dry-run':
+ dry_run = True
+
+ outputs = parse()
+ print_outputs('Outputs', outputs)
+
+ # Find the first two connected devices, in reverse list order. On the
+ # Thinkpad X201, the reversed list order is the preferred order of
+ # precedence, highest quality external display first, down to internal
+ # LVDS panel used as a last resort. Also find new posx values.
+ use_count = 0
+ posx = 0
+ for output in reversed(outputs):
+ if output['connected']:
+ print 'use', output['name']
+ output['use'] = True
+ output['posx'] = posx
+ posx += output['width']
+ use_count += 1
+ if use_count >= 2:
+ break
+
+ # How many outputs are currently enabled?
+ enabled_count = 0
+ for output in outputs:
+ if output['enabled']:
+ enabled_count += 1
+
+ # Mark outputs that receive no change as done
+ for output in outputs:
+ if not output['use'] and not output['enabled']:
+ # Nothing to do for this output, mark as done
+ output['done'] = True
+
+ while not all_done(outputs):
+ if enabled_count > 1:
+ enabled_count -= disable_an_entry(outputs)
+ enabled_count += enable_an_entry(outputs, enabled_count < 2)
+
+ # A hack to set Control/Win key mapping
+ subprocess.call('/usr/local/bin/setctrl')
+
+
+ main(sys.argv)