rework the new screen DB stuff:
[lilass.git] / screen.py
index f9bef55740804effac631a19b7d00b2b3f290eaa..a95d45dc8d19cd4f8a39715baa2332e323899786 100644 (file)
--- a/screen.py
+++ b/screen.py
@@ -44,8 +44,10 @@ class RelativeScreenPosition(Enum):
     def __init__(self, text):
         # auto numbering
         cls = self.__class__
     def __init__(self, text):
         # auto numbering
         cls = self.__class__
-        self._value_ = len(cls.__members__)
+        self._value_ = len(cls.__members__) + 1
         self.text = text
         self.text = text
+    def __str__(self):
+        return self.text
 
 class Resolution:
     '''Represents a resolution of a screen'''
 
 class Resolution:
     '''Represents a resolution of a screen'''
@@ -53,6 +55,24 @@ class Resolution:
         self.width = width
         self.height = height
     
         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
     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 pixelCount(self):
         return self.width * self.height
-    
-    def forXrandr(self):
-        return str(self.width)+'x'+str(self.height)
 
 
 class ScreenSetup:
 
 
 class ScreenSetup:
@@ -123,13 +140,22 @@ class ScreenSetup:
                 RelativeScreenPosition.MIRROR: '--same-as',
             }[self.relPosition], intName]
         return args
                 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
 
 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)
     
     def __str__(self):
         return str(self.name)
@@ -138,7 +164,7 @@ class Connector:
         return """<Connector "%s" EDID="%s" resolutions="%s">""" % (str(self.name), str(self.edid), ", ".join(str(r) for r in self.getResolutionList()))
     
     def isConnected(self):
         return """<Connector "%s" EDID="%s" resolutions="%s">""" % (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):
         return self.edid is not None
     
     def addResolution(self, resolution):
@@ -151,13 +177,23 @@ class Connector:
         else:
             self.edid += s
     
         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):
     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
 
 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):
     
     '''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()
            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:
         # 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)
             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)
                 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)
                 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)
                 continue
             # EDID?
             m = re.search(r'^\s*EDID:\s*$', line)
@@ -222,8 +257,7 @@ class ScreenSituation:
                 readingEdid = True
                 continue
             # unknown 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 the first available connector from those listed in <tryConnectorNames>, skipping disabled connectors
     def _findAvailableConnector(self, tryConnectorNames):
@@ -231,21 +265,11 @@ class ScreenSituation:
             return c
         return None
     
             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):
     # 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
         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
 
             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)