+#!/usr/bin/python
+"""
+Parse xrandr current information
+
+TODO:
+* Cannot seem to undock notebook then recover displays without causing a logout
+ event. Could it be that i3 is finding there are no displays?
+"""
+
+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}
+ 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 not self.enabled:
+ 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
+
+
+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 print_outputs(header, outputs):
+ print '%s:' % header
+ for output in outputs:
+ print_output(output)
+ print
+
+ def main(argv):
+ global dry_run
+ if len(argv) >= 2 and argv[1] == '--dry-run':
+ dry_run = True
+
+ outputs = parse()
+ #outputs[1]['connected'] = False
+ #outputs[2]['connected'] = False
+ print_outputs('Raw outputs', outputs)
+
+ preferred_order = list(reversed([output['name'] for output in outputs]))
+ print 'preferred order:', preferred_order
+ print
+
+ tmp = outputs
+ outputs = []
+ for name in preferred_order:
+ output = output_by_name(tmp, name)
+ if output:
+ outputs.append(output)
+ print_outputs('Sorted outputs', outputs)
+
+ connected_outputs = [output for output in outputs if output['connected']]
+ print_outputs('Connected outputs', connected_outputs)
+
+ enabled_outputs = [output for output in outputs if output['enabled']]
+ print_outputs('Enabled outputs', enabled_outputs)
+
+ # The outputs to use will be a subset of connected outputs
+ touse_outputs = connected_outputs
+
+ # If there are more then 2 connected outputs, start removing them by
+ # order of desirability. The LCD panel is least desirable under the
+ # assumption that having more than two means the LCD panel shouldn't be
+ # used.
+ while len(touse_outputs) > 2:
+ print 'trim', touse_outputs[-1]['name']
+ touse_outputs = touse_outputs[:-1]
+ print_outputs('To use outputs', touse_outputs)
+
+ if touse_outputs == enabled_outputs:
+ # If the touse_outputs list is the same as the enabled_outputs
+ # list, then presumably there is no change made
+ print 'Identical'
+ else:
+ # Determine which outputs to turn off, and which to turn on
+ off_outputs = [output for output in enabled_outputs if output not in
+ touse_outputs]
+ print_outputs('Turn these off', off_outputs)
+ on_outputs = [output for output in touse_outputs if output not in
+ enabled_outputs]
+ print_outputs('Turn these on', on_outputs)
+
+ if len(enabled_outputs) < 1 or len(enabled_outputs) > 2 or \
+ len(touse_outputs) < 1 or len(touse_outputs) > 2:
+ print 'Something is wrong; doing nothing'
+ return
+
+ # Compute positions for final outputs
+ posx = 0
+ for output in touse_outputs:
+ output['posx'] = posx
+ output['posy'] = 0
+ posx += output['width']
+ print_outputs('touse, with positions', touse_outputs)
+
+ # Remove all enabled outputs save one
+ while len(enabled_outputs) > 1:
+ output = enabled_outputs[0]
+ enabled_outputs = enabled_outputs[1:]
+ xrandr_off(output)
+
+ # Move the remaining output to a safe position
+ last_old_output = enabled_outputs[0]
+ tmp = last_old_output['posx']
+ last_old_output['posx'] = posx # from above
+ xrandr_on(last_old_output)
+ last_old_output['posx'] = tmp
+
+ # Add the first touse output
+ first_new_output = touse_outputs[0]
+ touse_outputs = touse_outputs[1:]
+ xrandr_on(first_new_output)
+
+ # If last old is not to be used, remove it
+ if not output_by_name(touse_outputs, last_old_output['name']):
+ xrandr_off(last_old_output)
+
+ # Now add the remaining touse outputs
+ for output in touse_outputs:
+ xrandr_on(output)
+
+
+ main(sys.argv)