#!/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 import datetime import time dry_run = False class record: def open(self, filename): self.of = open(filename, 'a') def out(self, line): data = '%s\n' % line self.of.write(data) sys.stdout.write(data) def fout(self, line): data = '%s\n' % line self.of.write(data) def outTime(self, line): tstamp = datetime.datetime.fromtimestamp(time.time()) data = '%s %s\n' % (tstamp.strftime('%Y-%m-%d %H:%M:%S'), line) self.of.write(data) sys.stdout.write(data) def foutTime(self, line): tstamp = datetime.datetime.fromtimestamp(time.time()) data = '%s %s\n' % (tstamp.strftime('%Y-%m-%d %H:%M:%S'), line) self.of.write(data) def close(self): self.of.close() out = record() 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: out.out(' '.join(command)) subprocess.call(command) else: out.out('(dry-run) %s' % ' '.join(command)) def xrandr_off(output): command = ['xrandr', '--output', output['name'], '--off'] if not dry_run: subprocess.call(command) out.out(' '.join(command)) else: out.out('(dry-run) %s' % ' '.join(command)) def output_by_name(outputs, name): for output in outputs: if output['name'] == name: return output return None 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 main(argv): global dry_run if len(argv) >= 2 and argv[1] == '--dry-run': dry_run = True global out from os.path import expanduser, join path = join(expanduser("~"), 'dispcfg.log') out.open(path) out.outTime('started') outputs = parse() for output in outputs: s = '%s' % output['name'] if output['connected']: s += ' connected' else: s += ' disconnected' if output['enabled']: s += ' enabled' t = '%dx%d' % (output['width'], output['height']) if t != '-1x-1': s += ' size:%s' % t t = '%dx%d' % (output['posx'], output['posy']) if t != '-1x-1': s += ' pos:%s' % t out.out(s) # 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 use = [] for output in reversed(outputs): if output['connected']: use.append(output['name']) output['use'] = True output['posx'] = posx posx += output['width'] use_count += 1 if use_count >= 2: break out.out('use %s' % ' '.join(use)) # 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) out.outTime('completed') out.fout('') main(sys.argv)