+# for auto-config: common names of internal connectors
+commonInternalConnectorNames = ['LVDS', 'LVDS0', 'LVDS1', 'LVDS-0', 'LVDS-1']
+
+# this is as close as one can get to an enum in Python
+class RelativeScreenPosition:
+ LEFT = 0
+ RIGHT = 1
+ EXTERNAL_ONLY = 2
+
+# storing what's necessary for screen setup
+class ScreenSetup:
+ def __init__(self, relPosition, intResolution, extResolution, extIsPrimary = False):
+ '''relPosition must be one of the RelativeScreenPosition members, the resolutions must be (width, height) pairs'''
+ self.relPosition = relPosition
+ self.intResolution = intResolution # value doesn't matter if the internal screen is disabled
+ self.extResolution = extResolution
+ self.extIsPrimary = extIsPrimary or self.relPosition == RelativeScreenPosition.EXTERNAL_ONLY # external is always primary if it is the only one
+
+ def getInternalArgs(self):
+ if self.relPosition == RelativeScreenPosition.EXTERNAL_ONLY:
+ return ["--off"]
+ args = ["--mode", res2xrandr(self.intResolution)] # set internal screen to desired resolution
+ if not self.extIsPrimary:
+ args.append('--primary')
+ return args
+
+ def getExternalArgs(self, intName):
+ args = ["--mode", res2xrandr(self.extResolution)] # set external screen to desired resolution
+ if self.extIsPrimary:
+ args.append('--primary')
+ if self.relPosition == RelativeScreenPosition.LEFT:
+ args += ['--left-of', intName]
+ elif self.relPosition == RelativeScreenPosition.RIGHT:
+ args += ['--right-of', intName]
+ return args
+
+# Load a section-less config file: maps parameter names to space-separated lists of strings (with shell quotation)
+def loadConfigFile(filename):
+ import shlex
+ result = {}
+ if not os.path.exists(filename):
+ return result # no config file
+ # read config file
+ linenr = 0
+ with open(filename) as f:
+ for line in f:
+ linenr += 1
+ line = line.strip()
+ if not len(line) or line.startswith("#"): continue # skip empty and comment lines
+ try:
+ # parse line
+ pos = line.index("=") # will raise exception when substring is not found
+ curKey = line[:pos].strip()
+ result[curKey] = shlex.split(line[pos+1:]) # shlex.split also strips
+ except Exception:
+ raise Exception("Invalid config, line %d: Error parsing line (may be a quoting issue)." % linenr)
+ # add some convencience get functions
+ return result
+
+# helper function: execute a process, return output as iterator, throw exception if there was an error
+# you *must* iterate to the end if you use this!
+def processOutputGen(*args):
+ with subprocess.Popen(args, stdout=subprocess.PIPE) as p:
+ for line in p.stdout:
+ yield line.decode("utf-8")
+ if p.returncode != 0:
+ raise Exception("Error executing "+str(args))
+def processOutputIt(*args):
+ return list(processOutputGen(*args)) # list() iterates over the generator
+
+# Run xrandr and return a dict of output names mapped to lists of available resolutions, each being a (width, height) pair.
+# An empty list indicates that the connector is disabled.