import re, subprocess
from enum import Enum
-from fractions import Fraction
## utility functions
cls = self.__class__
self._value_ = len(cls.__members__) + 1
self.text = text
+ def __str__(self):
+ return self.text
class Resolution:
'''Represents a resolution of a screen'''
def __str__(self):
# get ratio
- ratio = Fraction(self.width, self.height) # automatically divides by the gcd
- strRatio = "%d:%d" % (ratio.numerator, ratio.denominator)
+ ratio = int(round(16.0*self.height/self.width))
+ if ratio == 11: # 16:10.66 = 3:2
+ strRatio = "3:2"
+ elif ratio == 12: # 16:12 = 4:3
+ strRatio = '4:3'
+ elif ratio == 13: # 16:12.8 = 5:4
+ strRatio = '5:4'
+ else: # let's just hope this will never be 14 or more...
+ strRatio = '16:%d' % ratio
return '%dx%d (%s)' %(self.width, self.height, strRatio)
def __repr__(self):
RelativeScreenPosition.MIRROR: '--same-as',
}[self.relPosition], intName]
return args
+
+ def __str__(self):
+ if self.intResolution is None:
+ return "External display only, at "+str(self.extResolution)
+ if self.extResolution is None:
+ return "Internal display only, at "+str(self.intResolution)
+ return "External display %s at %s %s internal display %s at %s" % ("(primary)" if self.extIsPrimary else "", str(self.extResolution), str(self.relPosition), "" if self.extIsPrimary else "(primary)", str(self.intResolution))
class Connector:
def __init__(self, name=None):
self.name = name # connector name, e.g. "HDMI1"
self.edid = None # EDID string for the connector, or None if disconnected
- self._resolutions = set() # list of Resolution objects, empty if disconnected
- self.preferredResolution = None
- self.__lastResolution = None
+ self._resolutions = set() # set of Resolution objects, empty if disconnected
+ self._preferredResolution = None
+ self.previousResolution = None
self.hasLastResolution = False
def __str__(self):
def __repr__(self):
return """<Connector "%s" EDID="%s" resolutions="%s">""" % (str(self.name), str(self.edid), ", ".join(str(r) for r in self.getResolutionList()))
- def __setLastRes(self, res):
- # res == None means this display was last switched off
- if res is not None and not res in self._resolutions:
- raise ValueError("Resolution "+res+" not available for "+self.name+".")
- self.__lastResolution = res
- self.hasLastResolution = True
-
- def __getLastRes(self):
- if not self.hasLastResolution:
- raise ValueError("Connector %s has no last known resolution." % self.name)
- return self.__lastResolution
-
- lastResolution = property(__getLastRes, __setLastRes)
-
def isConnected(self):
- assert (self.edid is None) == (len(self._resolutions)==0)
+ assert (self.edid is None) == (len(self._resolutions)==0), "Resolution-EDID mismatch; #resolutions: {}".format(len(self._resolutions))
return self.edid is not None
def addResolution(self, resolution):
else:
self.edid += s
+ def setPreferredResolution(self, resolution):
+ assert isinstance(resolution, Resolution) and resolution in self._resolutions
+ self._preferredResolution = resolution
+
+ def getPreferredResolution(self):
+ if self._preferredResolution is not None:
+ return self._preferredResolution
+ return self.getResolutionList()[0] # prefer the largest resolution
+
def getResolutionList(self):
- return sorted(self._resolutions, key=lambda r: (0 if self.hasLastResolution and r==self.lastResolution else 1, 0 if r==self.preferredResolution else 1, -r.pixelCount()))
+ return sorted(self._resolutions, key=lambda r: -r.pixelCount())
class ScreenSituation:
connectors = [] # contains all the Connector objects
internalConnector = None # the internal Connector object (will be an enabled one)
externalConnector = None # the used external Connector object (an enabled one), or None
+ previousSetup = None # None or the ScreenSetup used the last time this external screen was connected
'''Represents the "screen situation" a machine can be in: Which connectors exist, which resolutions do they have, what are the names for the internal and external screen'''
- def __init__(self, internalConnectorNames, externalConnectorNames = None):
+ def __init__(self, internalConnectorNames, externalConnectorNames = None, xrandrSource = None):
'''Both arguments are lists of connector names. The first one which exists and has a screen attached is chosen for that class. <externalConnectorNames> can be None to
just choose any remaining connector.'''
# which connectors are there?
- self._getXrandrInformation()
- for c in self.connectors:
- print(repr(c))
- print()
+ self._getXrandrInformation(xrandrSource)
# figure out which is the internal connector
self.internalConnector = self._findAvailableConnector(internalConnectorNames)
if self.internalConnector is None:
if self.internalConnector == self.externalConnector:
raise Exception("Internal and external connector are the same. This must not happen. Please fix ~/.dsl.conf.");
print("Detected external connector:",self.externalConnector)
- # self.lastSetup is left uninitialized so you can't access it before trying a lookup in the database
# Run xrandr and fill the dict of connector names mapped to lists of available resolutions.
- def _getXrandrInformation(self):
+ def _getXrandrInformation(self, xrandrSource = None):
connector = None # current connector
readingEdid = False
+ if xrandrSource is None:
+ xrandrSource = processOutputGen("xrandr", "-q", "--verbose")
for line in processOutputGen("xrandr", "-q", "--verbose"):
if readingEdid:
m = re.match(r'^\s*([0-9a-f]+)\s*$', line)
if m is not None:
connector = Connector(m.group(1))
assert not any(c.name == connector.name for c in self.connectors)
- self.connectors.append(connector)
+ if not connector.name.startswith("VIRTUAL"):
+ # skip "VIRTUAL" connectors
+ self.connectors.append(connector)
continue
# new resolution?
m = re.search(r'^\s*([\d]+)x([\d]+)', line)
resolution = Resolution(int(m.group(1)), int(m.group(2)))
assert connector is not None
connector.addResolution(resolution)
- if '+preferred' in line:
- connector.preferredResolution = resolution
+ if re.search(r' [+]preferred\b', line):
+ connector.setPreferredResolution(resolution)
continue
# EDID?
m = re.search(r'^\s*EDID:\s*$', line)
readingEdid = True
continue
# unknown line
- # not fatal, e.g. xrandr shows strange stuff when a display is enabled, but not connected
- #print("Warning: Unknown xrandr line %s" % line)
+ # not fatal, e.g. xrandr shows strange stuff when a display is enabled, but not connected; --verbose adds a whole lot of other weird stuff
# return the first available connector from those listed in <tryConnectorNames>, skipping disabled connectors
def _findAvailableConnector(self, tryConnectorNames):
return c
return None
- # return available internal resolutions
- def internalResolutions(self):
- return self.internalConnector.getResolutionList()
-
- # return available external resolutions (or None, if there is no external screen connected)
- def externalResolutions(self):
- if self.externalConnector is None:
- return None
- return self.externalConnector.getResolutionList()
-
# return resolutions available for both internal and external screen
def commonResolutions(self):
- internalRes = self.internalResolutions()
- externalRes = self.externalResolutions()
- assert externalRes is not None
+ assert self.externalConnector is not None, "Common resolutions may only be queried if there is an external screen connected."
+ internalRes = self.internalConnector.getResolutionList()
+ externalRes = self.externalConnector.getResolutionList()
return sorted(set(externalRes).intersection(internalRes), key=lambda r: -r.pixelCount())
# compute the xrandr call
def fetchDBInfo(self, db):
if self.externalConnector and self.externalConnector.edid:
- self.lastSetup = db.getConfig(self.externalConnector.edid) # may also return None
- else:
- self.lastSetup = None
- if self.lastSetup:
- print("SETUP FOUND", self.lastSetup)
- self.externalConnector.lastResolution = self.lastSetup.extResolution
- self.internalConnector.lastResolution = self.lastSetup.intResolution
+ self.previousSetup = db.getConfig(self.externalConnector.edid) # may also return None
else:
- print("NO SETUP FOUND")
+ self.previousSetup = None
+ if self.previousSetup:
+ print("Known screen, previous setup:", self.previousSetup)
+ self.externalConnector.previousResolution = self.previousSetup.extResolution
+ self.internalConnector.previousResolution = self.previousSetup.intResolution
def putDBInfo(self, db, setup):
if not self.externalConnector or not self.externalConnector.edid: