X-Git-Url: https://git.ralfj.de/lilass.git/blobdiff_plain/09b026a7a1fc1a072c7bc7dc6d491ad71180617f..953d73eb56c0bdf86b9b168a657b8f9fa04d80bf:/screen.py diff --git a/screen.py b/screen.py index f9bef55..a95d45d 100644 --- a/screen.py +++ b/screen.py @@ -44,8 +44,10 @@ class RelativeScreenPosition(Enum): def __init__(self, text): # auto numbering cls = self.__class__ - self._value_ = len(cls.__members__) + self._value_ = len(cls.__members__) + 1 self.text = text + def __str__(self): + return self.text class Resolution: '''Represents a resolution of a screen''' @@ -53,6 +55,24 @@ class Resolution: self.width = width self.height = height + @classmethod + def fromDatabase(cls, dbstr): + if dbstr is None: + return None + parts = dbstr.split("x") + if len(parts) != 2: + raise ValueError(xrandrstr) + return Resolution(*map(int,parts)) + + def forDatabase(self): + return str(self.width)+'x'+str(self.height) + + def forXrandr(self): + return self.forDatabase() + + def toTuple(self): + return (self.width, self.height) + def __eq__(self, other): if not isinstance(other, Resolution): return False @@ -80,9 +100,6 @@ class Resolution: def pixelCount(self): return self.width * self.height - - def forXrandr(self): - return str(self.width)+'x'+str(self.height) class ScreenSetup: @@ -123,13 +140,22 @@ class ScreenSetup: 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._resolutions = set() # set of Resolution objects, empty if disconnected + self._preferredResolution = None + self.previousResolution = None + self.hasLastResolution = False def __str__(self): return str(self.name) @@ -138,7 +164,7 @@ class Connector: return """""" % (str(self.name), str(self.edid), ", ".join(str(r) for r in self.getResolutionList())) 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): @@ -151,13 +177,23 @@ class Connector: 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 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): @@ -165,9 +201,6 @@ class ScreenSituation: just choose any remaining connector.''' # which connectors are there? self._getXrandrInformation() - for c in self.connectors: - print(repr(c)) - print() # figure out which is the internal connector self.internalConnector = self._findAvailableConnector(internalConnectorNames) if self.internalConnector is None: @@ -205,7 +238,9 @@ class ScreenSituation: 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) @@ -213,8 +248,8 @@ class ScreenSituation: 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) @@ -222,8 +257,7 @@ class ScreenSituation: 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 , skipping disabled connectors def _findAvailableConnector(self, tryConnectorNames): @@ -231,21 +265,11 @@ class ScreenSituation: 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 @@ -266,3 +290,17 @@ class ScreenSituation: call += ["--output", name] + connectorArgs[name] return call + def fetchDBInfo(self, db): + if self.externalConnector and self.externalConnector.edid: + self.previousSetup = db.getConfig(self.externalConnector.edid) # may also return None + else: + 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: + return + db.putConfig(self.externalConnector.edid, setup)