#!/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 if not dry_run: subprocess.call('/usr/local/bin/setctrl') main(sys.argv)