add a mirror mode
authorRalf Jung <post@ralfj.de>
Sun, 25 Jan 2015 16:51:28 +0000 (17:51 +0100)
committerRalf Jung <post@ralfj.de>
Sun, 25 Jan 2015 16:51:28 +0000 (17:51 +0100)
dsl.py
gui.py
qt_dialogue.py

diff --git a/dsl.py b/dsl.py
index e0e71d11ac09b9d51453e15f3f124b762c44ba42..606809cd9d7e80b99776152d5d7070d72d58cbfa 100755 (executable)
--- a/dsl.py
+++ b/dsl.py
@@ -28,6 +28,14 @@ class RelativeScreenPosition:
     LEFT          = 0
     RIGHT         = 1
     EXTERNAL_ONLY = 2
     LEFT          = 0
     RIGHT         = 1
     EXTERNAL_ONLY = 2
+    MIRROR        = 3
+    
+    __names__ = {
+        'left': LEFT,
+        'right': RIGHT,
+        'external-only': EXTERNAL_ONLY,
+        'mirror': MIRROR
+    }
 
 # storing what's necessary for screen setup
 class ScreenSetup:
 
 # storing what's necessary for screen setup
 class ScreenSetup:
@@ -50,10 +58,15 @@ class ScreenSetup:
         args = ["--mode", res2xrandr(self.extResolution)] # set external screen to desired resolution
         if self.extIsPrimary:
             args.append('--primary')
         args = ["--mode", res2xrandr(self.extResolution)] # set external screen to desired resolution
         if self.extIsPrimary:
             args.append('--primary')
+        # set position
         if self.relPosition == RelativeScreenPosition.LEFT:
             args += ['--left-of', intName]
         elif self.relPosition == RelativeScreenPosition.RIGHT:
             args += ['--right-of', intName]
         if self.relPosition == RelativeScreenPosition.LEFT:
             args += ['--left-of', intName]
         elif self.relPosition == RelativeScreenPosition.RIGHT:
             args += ['--right-of', intName]
+        elif self.relPosition == RelativeScreenPosition.MIRROR:
+            args += ['--same-as', intName]
+        else:
+            assert self.relPosition == RelativeScreenPosition.EXTERNAL_ONLY
         return args
 
 # Load a section-less config file: maps parameter names to space-separated lists of strings (with shell quotation)
         return args
 
 # Load a section-less config file: maps parameter names to space-separated lists of strings (with shell quotation)
@@ -186,7 +199,7 @@ if __name__ == "__main__":
                             dest="frontend",
                             help="The frontend to be used for user interaction")
         parser.add_argument("-r", "--relative-position",
                             dest="frontend",
                             help="The frontend to be used for user interaction")
         parser.add_argument("-r", "--relative-position",
-                            dest="rel_position", choices=('left', 'right', 'external-only'),
+                            dest="rel_position", choices=RelativeScreenPosition.__names__.keys(),
                             help="Position of external screen relative to internal one")
         parser.add_argument("-i", "--internal-only",
                             dest="internal_only", action='store_true',
                             help="Position of external screen relative to internal one")
         parser.add_argument("-i", "--internal-only",
                             dest="internal_only", action='store_true',
@@ -210,19 +223,19 @@ if __name__ == "__main__":
         usedExternalConnector = findAvailableConnector(externalConnectors, connectors) # *the* external connector which is actually used
         hasExternal = not cmdArgs.internal_only and usedExternalConnector is not None
         if hasExternal:
         usedExternalConnector = findAvailableConnector(externalConnectors, connectors) # *the* external connector which is actually used
         hasExternal = not cmdArgs.internal_only and usedExternalConnector is not None
         if hasExternal:
+            # compute the list of resolutons available on both
+            commonRes = [res for res in connectors[usedExternalConnector] if res in connectors[internalConnector]]
             # there's an external screen connected, we need to get a setup
             if cmdArgs.rel_position is not None:
             # there's an external screen connected, we need to get a setup
             if cmdArgs.rel_position is not None:
-                # use command-line arguments (can we do this relPosition stuff more elegant?)
-                if cmdArgs.rel_position == 'left':
-                    relPosition = RelativeScreenPosition.LEFT
-                elif cmdArgs.rel_position == 'right':
-                    relPosition = RelativeScreenPosition.RIGHT
+                # use command-line arguments
+                relPosition = RelativeScreenPosition.__names__[cmdArgs.rel_position]
+                if relPosition == RelativeScreenPosition.MIRROR:
+                    setup = ScreenSetup(relPosition, commonRes[0], commonRes[0]) # use default resolutions
                 else:
                 else:
-                    relPosition = RelativeScreenPosition.EXTERNAL_ONLY
-                setup = ScreenSetup(relPosition, connectors[internalConnector][0], connectors[usedExternalConnector][0]) # use default resolutions
+                    setup = ScreenSetup(relPosition, connectors[internalConnector][0], connectors[usedExternalConnector][0]) # use default resolutions
             else:
                 # use GUI
             else:
                 # use GUI
-                setup = frontend.setup(connectors[internalConnector], connectors[usedExternalConnector])
+                setup = frontend.setup(connectors[internalConnector], connectors[usedExternalConnector], commonRes)
             if setup is None: sys.exit(1) # the user canceled
             # apply it
             connectorArgs[internalConnector] = setup.getInternalArgs()
             if setup is None: sys.exit(1) # the user canceled
             # apply it
             connectorArgs[internalConnector] = setup.getInternalArgs()
diff --git a/gui.py b/gui.py
index 7db44b83e1fe9f9128d1c6554eea6eb968349e26..50cb3a3d6377c7ce4bed77040fe79099ac08aca4 100644 (file)
--- a/gui.py
+++ b/gui.py
@@ -42,9 +42,9 @@ class QtFrontend:
         from PyQt4 import QtGui
         QtGui.QMessageBox.critical(None, 'Fatal error', message)
     
         from PyQt4 import QtGui
         QtGui.QMessageBox.critical(None, 'Fatal error', message)
     
-    def setup(self, internalResolutions, externalResolutions):
+    def setup(self, internalResolutions, externalResolutions, commonRes):
         from qt_dialogue import PositionSelection
         from qt_dialogue import PositionSelection
-        return PositionSelection(internalResolutions, externalResolutions).run()
+        return PositionSelection(internalResolutions, externalResolutions, commonRes).run()
     
     @staticmethod
     def isAvailable():
     
     @staticmethod
     def isAvailable():
@@ -61,7 +61,7 @@ class ZenityFrontend:
         '''Displays a fatal error to the user'''
         subprocess.check_call(["zenity", "--error", "--text="+message])
     
         '''Displays a fatal error to the user'''
         subprocess.check_call(["zenity", "--error", "--text="+message])
     
-    def setup(self, internalResolutions, externalResolutions):
+    def setup(self, internalResolutions, externalResolutions, commonRes):
         from zenity_dialogue import run
         run(internalResolutions, externalResolutions)
     
         from zenity_dialogue import run
         run(internalResolutions, externalResolutions)
     
@@ -80,7 +80,7 @@ class CLIFrontend:
     def error(self, message):
         print(message, file=sys.stderr)
     
     def error(self, message):
         print(message, file=sys.stderr)
     
-    def setup(self, internalResolutions, externalResolutions):
+    def setup(self, internalResolutions, externalResolutions, commonRes):
         raise Exception("Choosing the setup interactively is not supported with the CLI frontend")
     
     @staticmethod
         raise Exception("Choosing the setup interactively is not supported with the CLI frontend")
     
     @staticmethod
index 879a09aaf46b904b1bb7b6b678d4a8e86268552a..c82b8f9ee6f543aaa5c89dbf0776d55bf8203fea 100644 (file)
@@ -26,47 +26,59 @@ def makeLayout(layout, members):
     return layout
 
 class PositionSelection(QtGui.QDialog):
     return layout
 
 class PositionSelection(QtGui.QDialog):
-    def __init__(self, internalResolutions, externalResolutions):
+    def __init__(self, internalResolutions, externalResolutions, commonResolutions):
         # set up main window
         super(PositionSelection, self).__init__()
         self.setWindowTitle('DSL - easy Display Setup for Laptops')
         
         # set up main window
         super(PositionSelection, self).__init__()
         self.setWindowTitle('DSL - easy Display Setup for Laptops')
         
-        # position selection
+        ## position selection
         posBox = QtGui.QGroupBox('Position of external screen', self)
         self.posLeft = QtGui.QRadioButton('Left of internal screen', posBox)
         self.posRight = QtGui.QRadioButton('Right of internal screen', posBox)
         self.posRight.setChecked(True)
         self.posRight.setFocus()
         self.extOnly = QtGui.QRadioButton('Use external screen exclusively', posBox)
         posBox = QtGui.QGroupBox('Position of external screen', self)
         self.posLeft = QtGui.QRadioButton('Left of internal screen', posBox)
         self.posRight = QtGui.QRadioButton('Right of internal screen', posBox)
         self.posRight.setChecked(True)
         self.posRight.setFocus()
         self.extOnly = QtGui.QRadioButton('Use external screen exclusively', posBox)
-        posBox.setLayout(makeLayout(QtGui.QVBoxLayout(), [self.posLeft, self.posRight, self.extOnly]))
+        self.mirror = QtGui.QRadioButton('Mirror internal screen', posBox)
+        positions = [self.posLeft, self.posRight, self.extOnly, self.mirror]
+        posBox.setLayout(makeLayout(QtGui.QVBoxLayout(), positions))
+        for pos in positions:
+            pos.toggled.connect(self.updateForm)
         
         
-        # primary screen
-        primBox = QtGui.QGroupBox('Which should be the primary screen?', self)
-        self.extOnly.toggled.connect(primBox.setDisabled) # disable the box if there's just one screen in use
-        self.primExt = QtGui.QRadioButton('The external screen', primBox)
-        self.primInt = QtGui.QRadioButton('The internal screen', primBox)
+        ## primary screen
+        self.primBox = QtGui.QGroupBox('Which should be the primary screen?', self)
+        self.primExt = QtGui.QRadioButton('The external screen', self.primBox)
+        self.primInt = QtGui.QRadioButton('The internal screen', self.primBox)
         self.primInt.setChecked(True)
         self.primInt.setChecked(True)
-        primBox.setLayout(makeLayout(QtGui.QVBoxLayout(), [self.primExt, self.primInt]))
+        self.primBox.setLayout(makeLayout(QtGui.QVBoxLayout(), [self.primExt, self.primInt]))
         
         
-        # resolution selection
+        ## resolution selection
         resBox = QtGui.QGroupBox('Screen resolutions', self)
         resBox = QtGui.QGroupBox('Screen resolutions', self)
-        extResLabel = QtGui.QLabel('Resolution of external screen:', resBox)
+        # external screen
+        self.extResLabel = QtGui.QLabel('Resolution of external screen:', resBox)
         self.extResolutions = externalResolutions
         self.extResolutionsBox = QtGui.QComboBox(resBox)
         for res in externalResolutions:
             self.extResolutionsBox.addItem(res2user(res))
         self.extResolutionsBox.setCurrentIndex(0) # select first resolution
         self.extResolutions = externalResolutions
         self.extResolutionsBox = QtGui.QComboBox(resBox)
         for res in externalResolutions:
             self.extResolutionsBox.addItem(res2user(res))
         self.extResolutionsBox.setCurrentIndex(0) # select first resolution
-        extRow = makeLayout(QtGui.QHBoxLayout(), [extResLabel, self.extResolutionsBox])
-        intResLabel = QtGui.QLabel('Resolution of internal screen:', resBox)
-        self.extOnly.toggled.connect(intResLabel.setDisabled) # disable the label if there's just one screen in use
+        self.extRow = makeLayout(QtGui.QHBoxLayout(), [self.extResLabel, self.extResolutionsBox])
+        # internal screen
+        self.intResLabel = QtGui.QLabel('Resolution of internal screen:', resBox)
         self.intResolutions = internalResolutions
         self.intResolutionsBox = QtGui.QComboBox(resBox)
         for res in internalResolutions:
             self.intResolutionsBox.addItem(res2user(res))
         self.intResolutionsBox.setCurrentIndex(0) # select first resolution
         self.intResolutions = internalResolutions
         self.intResolutionsBox = QtGui.QComboBox(resBox)
         for res in internalResolutions:
             self.intResolutionsBox.addItem(res2user(res))
         self.intResolutionsBox.setCurrentIndex(0) # select first resolution
-        self.extOnly.toggled.connect(self.intResolutionsBox.setDisabled) # disable the box if there's just one screen in use
-        intRow = makeLayout(QtGui.QHBoxLayout(), [intResLabel, self.intResolutionsBox])
-        resBox.setLayout(makeLayout(QtGui.QVBoxLayout(), [extRow, intRow]))
+        self.intRow = makeLayout(QtGui.QHBoxLayout(), [self.intResLabel, self.intResolutionsBox])
+        # both screens
+        self.mirrorResLabel = QtGui.QLabel('Resolution of both screens:', resBox)
+        self.mirrorResolutions = commonResolutions
+        self.mirrorResolutionsBox = QtGui.QComboBox(resBox)
+        for res in commonResolutions:
+            self.mirrorResolutionsBox.addItem(res2user(res))
+        self.mirrorResolutionsBox.setCurrentIndex(0) # select first resolution
+        self.mirrorRow = makeLayout(QtGui.QHBoxLayout(), [self.mirrorResLabel, self.mirrorResolutionsBox])
+        # show them all
+        resBox.setLayout(makeLayout(QtGui.QVBoxLayout(), [self.extRow, self.intRow, self.mirrorRow]))
         
         # last row: buttons
         buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, QtCore.Qt.Horizontal, self)
         
         # last row: buttons
         buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, QtCore.Qt.Horizontal, self)
@@ -74,20 +86,40 @@ class PositionSelection(QtGui.QDialog):
         buttons.rejected.connect(self.reject)
         
         # add them all to the window
         buttons.rejected.connect(self.reject)
         
         # add them all to the window
-        self.setLayout(makeLayout(QtGui.QVBoxLayout(), [posBox, primBox, resBox, buttons]))
+        self.setLayout(makeLayout(QtGui.QVBoxLayout(), [posBox, self.primBox, resBox, buttons]))
+        
+        # make sure we are consistent
+        self.updateForm()
+    
+    def updateForm(self):
+        self.primBox.setEnabled(self.posLeft.isChecked() or self.posRight.isChecked())
+        self.extResolutionsBox.setEnabled(not self.mirror.isChecked())
+        self.extResLabel.setEnabled(not self.mirror.isChecked())
+        self.intResolutionsBox.setEnabled(self.posLeft.isChecked() or self.posRight.isChecked())
+        self.intResLabel.setEnabled(self.posLeft.isChecked() or self.posRight.isChecked())
+        self.mirrorResolutionsBox.setEnabled(self.mirror.isChecked())
+        self.mirrorResLabel.setEnabled(self.mirror.isChecked())
     
     def run(self):
         self.exec_()
         if not self.result(): return None
     
     def run(self):
         self.exec_()
         if not self.result(): return None
-        return ScreenSetup(self.getRelativeScreenPosition(),
-            self.intResolutions[self.intResolutionsBox.currentIndex()],
-            self.extResolutions[self.extResolutionsBox.currentIndex()],
-            self.primExt.isChecked())
+        if self.mirror.isChecked():
+            return ScreenSetup(RelativeScreenPosition.MIRROR,
+                self.mirrorResolutions[self.mirrorResolutionsBox.currentIndex()],
+                self.mirrorResolutions[self.mirrorResolutionsBox.currentIndex()],
+                extIsPrimary = True)
+        else:
+            return ScreenSetup(self.getRelativeScreenPosition(),
+                self.intResolutions[self.intResolutionsBox.currentIndex()],
+                self.extResolutions[self.extResolutionsBox.currentIndex()],
+                self.primExt.isChecked())
     
     def getRelativeScreenPosition(self):
         if self.posLeft.isChecked():
             return RelativeScreenPosition.LEFT
         elif self.posRight.isChecked():
             return RelativeScreenPosition.RIGHT
     
     def getRelativeScreenPosition(self):
         if self.posLeft.isChecked():
             return RelativeScreenPosition.LEFT
         elif self.posRight.isChecked():
             return RelativeScreenPosition.RIGHT
-        else:
+        elif self.extOnly.isChecked():
             return RelativeScreenPosition.EXTERNAL_ONLY
             return RelativeScreenPosition.EXTERNAL_ONLY
+        else:
+            raise Exception("Nothing is checked?")