From: Ralf Jung Date: Tue, 28 Jul 2015 19:34:18 +0000 (+0200) Subject: Merge pull request #1 from ConnyOnny/master X-Git-Url: https://git.ralfj.de/lilass.git/commitdiff_plain/a6c79626b2d248d0484b5242c0a2051da8ec84d1?hp=21144381bdc37f521916c138522b3cfac3eb3e46 Merge pull request #1 from ConnyOnny/master updated zenity, add ability for questions to cli --- diff --git a/cli_frontend.py b/cli_frontend.py new file mode 100644 index 0000000..5e9d400 --- /dev/null +++ b/cli_frontend.py @@ -0,0 +1,51 @@ +# DSL - easy Display Setup for Laptops +# Copyright (C) 2012-2015 Ralf Jung +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# interactive command line interface, question based + +from question_frontend import QuestionFrontend + +import sys + +class CLIFrontend(QuestionFrontend): + def error(self, message): + print(message, file=sys.stderr) + + def userChoose (self, title, choices, returns, fallback): + while True: + # print question + print(title) + for i in range(len(choices)): + print("%d. %s"%(i,choices[i])) + print("Enter 'c' to cancel.") + # handle input + answer = input("> ") + if answer == "c": + return None + #else + try: + answerint = int(answer) + if answerint >= 0 and answerint < len(choices): + return returns[answerint] + except ValueError: + pass + # if we are here something invalid was entered + print("INVALID ANSWER: '%s'" % answer) + + @staticmethod + def isAvailable(): + return True diff --git a/gui.py b/gui.py index 998783f..62decc6 100644 --- a/gui.py +++ b/gui.py @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # This file abstracts GUI stuff away, so that the actual dsl.py does not have to deal with it -import sys ''' This module implements two functions: @@ -29,7 +28,11 @@ def setup(internalResolutions, externalResolutions): The user should be asked about his display setup preferences. The function returns None if the user cancelled, and an instance of dsl.ScreenSetup otherwise. ''' -import subprocess, collections +import sys +import collections + +from cli_frontend import CLIFrontend +from zenity_frontend import ZenityFrontend # Qt frontend class QtFrontend: @@ -54,39 +57,6 @@ class QtFrontend: except ImportError: return False - -# Zenity frontend -class ZenityFrontend: - def error(message): - '''Displays a fatal error to the user''' - subprocess.check_call(["zenity", "--error", "--text="+message]) - - def setup(self, situation): - from zenity_dialogue import run - return run(situation.internalResolutions(), situation.externalResolutions()) - - @staticmethod - def isAvailable(): - try: - from screen import processOutputIt - processOutputIt("zenity", "--version") - return True - except Exception: - return False - - -# CLI frontend -class CLIFrontend: - def error(self, message): - print(message, file=sys.stderr) - - def setup(self, internalResolutions, externalResolutions, commonRes): - raise Exception("Choosing the setup interactively is not supported with the CLI frontend") - - @staticmethod - def isAvailable(): - return True - # list of available frontends frontends = collections.OrderedDict() frontends["qt"] = QtFrontend @@ -100,8 +70,10 @@ def getFrontend(name = None): if name in frontends: if frontends[name].isAvailable(): return frontends[name]() # call constructor - # frontend not found or not available - raise Exception("Frontend %s not found or not available" % name) + else: + raise Exception("Frontend %s not available" % name) + # frontend not found + raise Exception("Frontend %s not found" % name) # auto-detect for frontend in frontends.values(): if frontend.isAvailable(): diff --git a/qt_dialogue.py b/qt_dialogue.py index 96c04a7..26f6717 100644 --- a/qt_dialogue.py +++ b/qt_dialogue.py @@ -18,15 +18,6 @@ import os from screen import RelativeScreenPosition, ScreenSetup from PyQt4 import QtCore, QtGui, uic -relPosNames = { - RelativeScreenPosition.LEFT: "left of", - RelativeScreenPosition.RIGHT: "right of", - RelativeScreenPosition.ABOVE: "above", - RelativeScreenPosition.BELOW: "below", - RelativeScreenPosition.MIRROR: "same as", -} - - class PositionSelection(QtGui.QDialog): def __init__(self, situation): # set up main window @@ -37,7 +28,7 @@ class PositionSelection(QtGui.QDialog): # fill relative position box for pos in RelativeScreenPosition: - self.relPos.addItem(relPosNames[pos], pos) + self.relPos.addItem(pos.text, pos) # keep resolutions in sync when in mirror mode def syncIfMirror(source, target): diff --git a/question_frontend.py b/question_frontend.py new file mode 100644 index 0000000..a17b7ca --- /dev/null +++ b/question_frontend.py @@ -0,0 +1,83 @@ +# DSL - easy Display Setup for Laptops +# Copyright (C) 2012 Ralf Jung +# Copyright (C) 2012-2015 Constantin Berhard +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from screen import RelativeScreenPosition, ScreenSetup, processOutputIt + +from enum import Enum + +class OperationMode(Enum): + INTERNAL_ONLY = ("Use internal display only") + EXTERNAL_ONLY = ("Use external display only") + USE_BOTH = ("Use both displays") + def __init__(self, text): + # auto numbering + cls = self.__class__ + self._value_ = len(cls.__members__) + # description + self.text = text + +class QuestionFrontend: + def userChoose (self, title, choices, returns, fallback): + raise Exception("The abstract method 'userChoose' has not been implemented by %s"%str(self.__class__)) + + def selectResolution(self, displayname, availablemodes): + modedescs = list(map(str, availablemodes)) + return self.userChoose("Select resolution for %s"%displayname, modedescs, availablemodes, None) + + def setup (self, situation): + operationmodes = list(OperationMode) + operationmodedescs = list(map(lambda x: x.text, operationmodes)) + operationmode = self.userChoose ("Display setup", operationmodedescs, operationmodes, None) + if operationmode is None: + return None + elif operationmode is OperationMode.INTERNAL_ONLY: + intres = self.selectResolution("the internal screen", situation.internalResolutions()) + if intres is None: + return None + else: + return ScreenSetup(intres, None, None, False) + elif operationmode is OperationMode.EXTERNAL_ONLY: + extres = self.selectResolution("the external screen", situation.externalResolutions()) + if extres is None: + return None + else: + return ScreenSetup(None, extres, None, True) + else: + assert operationmode is OperationMode.USE_BOTH + relscrpositions = list(RelativeScreenPosition) + relscrdescs = list(map(lambda x: x.text+" internal screen", relscrpositions)) + relpos = self.userChoose ("Position of external screen", relscrdescs, relscrpositions, None) + if relpos == None: + return None + elif relpos == RelativeScreenPosition.MIRROR: + # for mirroring only ask for common resolutions + commonres = self.selectResolution("both screens", situation.commonResolutions()) + if commonres is None: + return None + return ScreenSetup(commonres,commonres,relpos,False) + # select resolutions independently + intres = self.selectResolution("the internal screen", situation.internalResolutions()) + if intres is None: + return None + extres = self.selectResolution("the external screen", situation.externalResolutions()) + if extres is None: + return None + extprim = self.userChoose("Select primary screen", ["Internal screen is primary","External screen is primary"], [False,True], None) + if extprim is None: + return None + return ScreenSetup(intres,extres,relpos,extprim) diff --git a/screen.py b/screen.py index 78eafa1..d567912 100644 --- a/screen.py +++ b/screen.py @@ -36,11 +36,16 @@ def processOutputIt(*args): class RelativeScreenPosition(Enum): '''Represents the relative position of the external screen to the internal one''' - LEFT = 0 - RIGHT = 1 - ABOVE = 2 - BELOW = 3 - MIRROR = 4 + LEFT = ("left of") + RIGHT = ("right of") + ABOVE = ("above") + BELOW = ("below") + MIRROR = ("same as") + def __init__(self, text): + # auto numbering + cls = self.__class__ + self._value_ = len(cls.__members__) + self.text = text class Resolution: diff --git a/zenity_dialogue.py b/zenity_dialogue.py deleted file mode 100644 index 7dbbc87..0000000 --- a/zenity_dialogue.py +++ /dev/null @@ -1,47 +0,0 @@ -# DSL - easy Display Setup for Laptops -# Copyright (C) 2012 Ralf Jung -# Copyright (C) 2012-2015 Constantin Berhard -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -from screen import RelativeScreenPosition, ScreenSetup, processOutputIt - -def userChoose (title, choices, returns, fallback): - assert len(choices) == len(returns) - args = ["zenity", "--list", "--text="+title, "--column="]+choices - switch = dict (list(zip (choices,returns))) - try: - for line in processOutputIt(*args): - return switch.get(line.strip(), fallback) - except Exception: - # on user cancel, the return code of zenity is nonzero - return fallback - return fallback - -def run (internalResolutions, externalResolutions): - relpos = userChoose ("Position of external screen", ["Left of internal screen", "Right of internal screen"], [RelativeScreenPosition.LEFT, RelativeScreenPosition.RIGHT], None) - if relpos == None: - return None - intres = internalResolutions[0] - extres = externalResolutions[0] - extprim = True - extres = userChoose ("external display resolution", list(map(str,externalResolutions)), externalResolutions, None) - if extres == None: - return None - if extprim == None: - extprim = userChoose ("Which display should be the primary display?", ["internal display", "external display"], [False, True], None) - if extprim == None: - return None - return ScreenSetup(intres, extres, relpos, extIsPrimary = extprim) diff --git a/zenity_frontend.py b/zenity_frontend.py new file mode 100644 index 0000000..7dd4d82 --- /dev/null +++ b/zenity_frontend.py @@ -0,0 +1,50 @@ +# DSL - easy Display Setup for Laptops +# Copyright (C) 2012-2015 Ralf Jung +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# interactive zenity interface, question based + +from question_frontend import QuestionFrontend +from screen import processOutputIt + +import subprocess + +class ZenityFrontend(QuestionFrontend): + def error(self, message): + '''Displays a fatal error to the user''' + subprocess.check_call(["zenity", "--error", "--text="+message]) + + def userChoose (self, title, choices, returns, fallback): + assert len(choices) == len(returns) + args = ["zenity", "--list", "--text="+title, "--column="]+choices + switch = dict (list(zip (choices,returns))) + try: + for line in processOutputIt(*args): + return switch.get(line.strip(), fallback) + except Exception: + # on user cancel, the return code of zenity is nonzero + return fallback + # if the output was empty + return fallback + + def isAvailable(): + try: + processOutputIt("zenity", "--version") + return True + except FileNotFoundError: + return False + except PermissionError: + return False