From 8d8ac3226f3669f8524eef50161ae7241a808d7e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 25 Jan 2015 17:51:28 +0100 Subject: [PATCH] add a mirror mode --- dsl.py | 31 ++++++++++++++------ gui.py | 8 +++--- qt_dialogue.py | 78 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 81 insertions(+), 36 deletions(-) diff --git a/dsl.py b/dsl.py index e0e71d1..606809c 100755 --- a/dsl.py +++ b/dsl.py @@ -28,6 +28,14 @@ class RelativeScreenPosition: 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: @@ -50,10 +58,15 @@ class ScreenSetup: 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] + 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) @@ -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="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', @@ -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: + # 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: - # 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: - 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 - 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() diff --git a/gui.py b/gui.py index 7db44b8..50cb3a3 100644 --- a/gui.py +++ b/gui.py @@ -42,9 +42,9 @@ class QtFrontend: 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 - return PositionSelection(internalResolutions, externalResolutions).run() + return PositionSelection(internalResolutions, externalResolutions, commonRes).run() @staticmethod def isAvailable(): @@ -61,7 +61,7 @@ class ZenityFrontend: '''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) @@ -80,7 +80,7 @@ class CLIFrontend: 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 diff --git a/qt_dialogue.py b/qt_dialogue.py index 879a09a..c82b8f9 100644 --- a/qt_dialogue.py +++ b/qt_dialogue.py @@ -26,47 +26,59 @@ def makeLayout(layout, members): 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') - # 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.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) - 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) - 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 - 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.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) @@ -74,20 +86,40 @@ class PositionSelection(QtGui.QDialog): 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 - 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 - else: + elif self.extOnly.isChecked(): return RelativeScreenPosition.EXTERNAL_ONLY + else: + raise Exception("Nothing is checked?") -- 2.30.2