diff --git a/lib/python/Components/SystemInfo.py b/lib/python/Components/SystemInfo.py index 908e26299b..0d862dd1b6 100644 --- a/lib/python/Components/SystemInfo.py +++ b/lib/python/Components/SystemInfo.py @@ -112,6 +112,11 @@ def deleteItem(self, item, *args, **kws): BoxInfo = BoxInformation() +DISPLAYMODEL = BoxInfo.getItem("displaymodel") +DISPLAYBRAND = BoxInfo.getItem("displaybrand") + +def getBoxDisplayName(): # This function returns a tuple like ("BRANDNAME", "BOXNAME") + return (DISPLAYBRAND, DISPLAYMODEL) # Parse the boot commandline. # diff --git a/lib/python/Screens/Setup.py b/lib/python/Screens/Setup.py index 70e0e76103..a443a91d9e 100644 --- a/lib/python/Screens/Setup.py +++ b/lib/python/Screens/Setup.py @@ -1,127 +1,43 @@ - from gettext import dgettext -from xml.etree.ElementTree import parse - -from boxbranding import getMachineBrand, getMachineName -from enigma import eEnv -from six import ensure_str +from os.path import getmtime, isfile, join as pathjoin +from xml.etree.ElementTree import fromstring from Components.ActionMap import ActionMap, NumberActionMap -from Components.config import ConfigNothing, ConfigPassword, ConfigText, config +from Components.config import ConfigBoolean, ConfigNothing, ConfigSelection, config, ConfigPassword, ConfigText from Components.ConfigList import ConfigListScreen from Components.Label import Label from Components.Pixmap import Pixmap -from Components.Sources.Boolean import Boolean +from Components.SystemInfo import BoxInfo, getBoxDisplayName from Components.Sources.StaticText import StaticText -from Components.SystemInfo import BoxInfo -from Screens.Screen import Screen -from Tools.Directories import SCOPE_CURRENT_PLUGIN, resolveFilename - - -def setupdom(plugin=None): - # read the setupmenu - if plugin: - # first we search in the current path - setupfile = open(resolveFilename(SCOPE_CURRENT_PLUGIN, plugin + '/setup.xml'), 'r') - else: - # if not found in the current path, we use the global datadir-path - setupfile = open(eEnv.resolve('${datadir}/enigma2/setup.xml'), 'r') - setupfiledom = parse(setupfile) - setupfile.close() - return setupfiledom - - -def getConfigMenuItem(configElement): - for item in setupdom().getroot().findall('./setup/item/.'): - if item.text == configElement: - return _(item.attrib["text"]), eval(configElement) - return "", None - +from Screens.HelpMenu import HelpableScreen +from Screens.Screen import Screen, ScreenSummary +from Tools.Directories import SCOPE_GUISKIN, SCOPE_PLUGINS, SCOPE_SKINS, fileReadXML, resolveFilename +from Tools.LoadPixmap import LoadPixmap -class SetupError(Exception): - def __init__(self, message): - self.msg = message +MODULE_NAME = __name__.split(".")[-1] - def __str__(self): - return self.msg +domSetups = {} +setupModTimes = {} -class SetupSummary(Screen): - def __init__(self, session, parent): - Screen.__init__(self, session, parent=parent) - self["SetupTitle"] = StaticText(_(parent.setup_title)) - self["SetupEntry"] = StaticText("") - self["SetupValue"] = StaticText("") - if hasattr(self.parent, "onChangedEntry"): - self.onShow.append(self.addWatcher) - self.onHide.append(self.removeWatcher) - - def addWatcher(self): - if hasattr(self.parent, "onChangedEntry"): - self.parent.onChangedEntry.append(self.selectionChanged) - self.parent["config"].onSelectionChanged.append(self.selectionChanged) - self.selectionChanged() - - def removeWatcher(self): - if hasattr(self.parent, "onChangedEntry"): - self.parent.onChangedEntry.remove(self.selectionChanged) - self.parent["config"].onSelectionChanged.remove(self.selectionChanged) - - def selectionChanged(self): - self["SetupEntry"].text = self.parent.getCurrentEntry() - self["SetupValue"].text = self.parent.getCurrentValue() - if hasattr(self.parent, "getCurrentDescription") and "description" in self.parent: - self.parent["description"].text = self.parent.getCurrentDescription() - if 'footnote' in self.parent: - if self.parent.getCurrentEntry().endswith('*'): - self.parent['footnote'].text = (_("* = Restart Required")) - else: - self.parent['footnote'].text = (_(" ")) - - -class Setup(ConfigListScreen, Screen): - - ALLOW_SUSPEND = True - - def removeNotifier(self): - self.onNotifiers.remove(self.levelChanged) - - def levelChanged(self, configElement): - list = [] - self.refill(list) - self["config"].setList(list) - - def refill(self, list): - xmldata = setupdom(self.plugin).getroot() - for x in xmldata.findall("setup"): - if x.get("key") != self.setup: - continue - self.addItems(list, x) - self.setup_title = ensure_str(x.get("title", "")) - self.seperation = int(x.get('separation', '0')) - +class Setup(ConfigListScreen, Screen, HelpableScreen): def __init__(self, session, setup, plugin=None, PluginLanguageDomain=None): - Screen.__init__(self, session) - # for the skin: first try a setup_, then Setup - self.skinName = ["setup_" + setup, "Setup"] - - self['footnote'] = Label() - self["HelpWindow"] = Pixmap() - self["HelpWindow"].hide() - self["VKeyIcon"] = Boolean(False) - self["status"] = StaticText() - self.onChangedEntry = [] - self.item = None + Screen.__init__(self, session, mandatoryWidgets=["config", "footnote", "description"]) + HelpableScreen.__init__(self) self.setup = setup self.plugin = plugin - self.PluginLanguageDomain = PluginLanguageDomain - list = [] - self.onNotifiers = [] - self.refill(list) - ConfigListScreen.__init__(self, list, session=session, on_change=self.changedEntry) - self.createSetup() - - #check for list.entries > 0 else self.close + self.pluginLanguageDomain = PluginLanguageDomain + if not isinstance(self.skinName, list): + self.skinName = [self.skinName] + if setup: + self.skinName.append("setup_%s" % setup) + self.skinName.append("Setup%s" % setup) + self.skinName.append("Setup") + self.list = [] + ConfigListScreen.__init__(self, self.list, session=session, on_change=self.changedEntry) + self["footnote"] = Label() + self["footnote"].hide() + self["description"] = Label() self["key_red"] = StaticText(_("Cancel")) self["key_green"] = StaticText(_("OK")) self["description"] = Label("") @@ -132,172 +48,318 @@ def __init__(self, session, setup, plugin=None, PluginLanguageDomain=None): "save": self.keySave, "menu": self.closeRecursive, }, -2) - - self["VirtualKB"] = ActionMap(["VirtualKeyboardActions"], - { - "showVirtualKeyboard": self.KeyText, - }, -2) - self["VirtualKB"].setEnabled(False) - - if not self.handleInputHelpers in self["config"].onSelectionChanged: - self["config"].onSelectionChanged.append(self.handleInputHelpers) - self.changedEntry() + self.createSetup() + self["config"].onSelectionChanged.append(self.selectionChanged) self.onLayoutFinish.append(self.layoutFinished) - self.onClose.append(self.HideHelp) - - def createSetup(self): - list = [] - self.refill(list) - self["config"].setList(list) - if config.usage.sort_settings.value: - self["config"].list.sort() - self.moveToItem(self.item) - - def getIndexFromItem(self, item): - if item is not None: - for x in list(range(len(self["config"].list))): - if self["config"].list[x][0] == item[0]: - return x - return None - def moveToItem(self, item): - newIdx = self.getIndexFromItem(item) - if newIdx is None: - newIdx = 0 - self["config"].setCurrentIndex(newIdx) - - def handleInputHelpers(self): - self["status"].setText(self["config"].getCurrent()[2]) - if self["config"].getCurrent() is not None: - try: - if isinstance(self["config"].getCurrent()[1], ConfigText) or isinstance(self["config"].getCurrent()[1], ConfigPassword): - if "VKeyIcon" in self: - self["VirtualKB"].setEnabled(True) - self["VKeyIcon"].boolean = True - if "HelpWindow" in self: - if self["config"].getCurrent()[1].help_window.instance is not None: - helpwindowpos = self["HelpWindow"].getPosition() - from enigma import ePoint - self["config"].getCurrent()[1].help_window.instance.move(ePoint(helpwindowpos[0], helpwindowpos[1])) + def changedEntry(self): + if isinstance(self["config"].getCurrent()[1], (ConfigBoolean, ConfigSelection)): + self.createSetup() + + def createSetup(self, appendItems=None, prependItems=None): + oldList = self.list + self.showDefaultChanged = False + self.graphicSwitchChanged = False + self.list = prependItems or [] + title = None + xmlData = setupDom(self.setup, self.plugin) + for setup in xmlData.findall("setup"): + if setup.get("key") == self.setup: + self.addItems(setup) + skin = setup.get("skin", None) + if skin and skin != "": + self.skinName.insert(0, skin) + title = setup.get("title", None) + # If this break is executed then there can only be one setup tag with this key. + # This may not be appropriate if conditional setup blocks become available. + break + if appendItems: + self.list = self.list + appendItems + if title: + title = dgettext(self.pluginLanguageDomain, title) if self.pluginLanguageDomain else _(title) + self.setTitle(title if title else _("Setup")) + if not self.list: + self["config"].setList(self.list) + elif self.list != oldList or self.showDefaultChanged or self.graphicSwitchChanged: + currentItem = self["config"].getCurrent() + self["config"].setList(self.list) + if config.usage.sort_settings.value: + self["config"].list.sort() + self.moveToItem(currentItem) + + def addItems(self, parentNode, including=True): + for element in parentNode: + if not element.tag: + continue + if element.tag in ("elif", "else") and including: + break # End of succesful if/elif branch - short-circuit rest of children. + include = self.includeElement(element) + if element.tag == "item": + if including and include: + self.addItem(element) + elif element.tag == "if": + if including: + self.addItems(element, including=include) + elif element.tag == "elif": + including = include + elif element.tag == "else": + including = True + + def addItem(self, element): + if self.pluginLanguageDomain: + itemText = dgettext(self.pluginLanguageDomain, element.get("text", "??")) + itemDescription = dgettext(self.pluginLanguageDomain, element.get("description", " ")) + else: + itemText = _(element.get("text", "??")) + itemDescription = _(element.get("description", " ")) + item = eval(element.text) if element.text else "" + if item == "": + self.list.append((self.formatItemText(itemText),)) # Add the comment line to the config list. + elif not isinstance(item, ConfigNothing): + self.list.append((self.formatItemText(itemText), item, self.formatItemDescription(item, itemDescription))) # Add the item to the config list. + if item is config.usage.boolean_graphic: + self.graphicSwitchChanged = True + + def formatItemText(self, itemText): + return itemText.replace("%s %s", "%s %s" % getBoxDisplayName()) + + def formatItemDescription(self, item, itemDescription): + itemDescription = itemDescription.replace("%s %s", "%s %s" % getBoxDisplayName()) + return itemDescription + + def includeElement(self, element): + itemLevel = int(element.get("level", 0)) + if itemLevel > config.usage.setup_level.index: # The item is higher than the current setup level. + return False + requires = element.get("requires") + if requires: + for require in [x.strip() for x in requires.split(";")]: + negate = require.startswith("!") + if negate: + require = require[1:] + if require.startswith("config."): + try: + result = eval(require) + result = bool(result.value and str(result.value).lower() not in ("0", "disable", "false", "no", "off")) + except Exception: + return self.logIncludeElementError(element, "requires", require) else: - if "VKeyIcon" in self: - self["VirtualKB"].setEnabled(False) - self["VKeyIcon"].boolean = False - except: - if "VKeyIcon" in self: - self["VirtualKB"].setEnabled(False) - self["VKeyIcon"].boolean = False + result = bool(BoxInfo.getItem(require, False)) + if require and negate == result: # The item requirements are not met. + return False + conditional = element.get("conditional") + if conditional: + try: + if not bool(eval(conditional)): + return False + except Exception: + return self.logIncludeElementError(element, "conditional", conditional) + return True + + def logIncludeElementError(self, element, type, token): + item = "title" if element.tag == "screen" else "text" + text = element.get(item) + print("[Setup]") + print("[Setup] Error: Tag '%s' with %s of '%s' has a %s '%s' that can't be evaluated!" % (element.tag, item, text, type, token)) + print("[Setup] NOTE: Ignoring this error may have consequences like unexpected operation or system failures!") + print("[Setup]") + return False + + def selectionChanged(self): + if self["config"]: + self.setFootnote(None) + self["description"].setText(self.getCurrentDescription()) else: - if "VKeyIcon" in self: - self["VirtualKB"].setEnabled(False) - self["VKeyIcon"].boolean = False - - def HideHelp(self): - self.help_window_was_shown = False - try: - if isinstance(self["config"].getCurrent()[1], ConfigText) or isinstance(self["config"].getCurrent()[1], ConfigPassword): - if self["config"].getCurrent()[1].help_window.instance is not None: - self["config"].getCurrent()[1].help_window.hide() - self.help_window_was_shown = True - except: - pass - - def KeyText(self): - if isinstance(self["config"].getCurrent()[1], ConfigText) or isinstance(self["config"].getCurrent()[1], ConfigPassword): - if self["config"].getCurrent()[1].help_window.instance is not None: - self["config"].getCurrent()[1].help_window.hide() - from Screens.VirtualKeyBoard import VirtualKeyBoard - self.session.openWithCallback(self.VirtualKeyBoardCallback, VirtualKeyBoard, title=self["config"].getCurrent()[0], text=self["config"].getCurrent()[1].value) - - def VirtualKeyBoardCallback(self, callback=None): - if callback is not None and len(callback): - self["config"].getCurrent()[1].setValue(callback) - self["config"].invalidate(self["config"].getCurrent()) + self["description"].setText(_("There are no items currently available for this screen.")) def layoutFinished(self): - self.setTitle(_(self.setup_title)) + if not self["config"]: + print("[Setup] No setup items available!") + + def setFootnote(self, footnote): + if footnote is None: + if self.getCurrentEntry().endswith("*"): + self["footnote"].setText(_("* = Restart Required")) + self["footnote"].show() + else: + self["footnote"].setText("") + self["footnote"].hide() + else: + self["footnote"].setText(footnote) + self["footnote"].show() - # for summary: - def changedEntry(self): - self.item = self["config"].getCurrent() - try: - #FIXME This code prevents an LCD refresh for this ConfigElement(s) - if not isinstance(self["config"].getCurrent()[1], ConfigText): - self.createSetup() - except: - pass - - def addItems(self, list, parentNode): - for x in parentNode: - if not x.tag: - continue - if x.tag == 'item': - item_level = int(x.get("level", 0)) - item_tunerlevel = int(x.get("tunerlevel", 0)) - item_rectunerlevel = int(x.get("rectunerlevel", 0)) - item_tuxtxtlevel = int(x.get("tt_level", 0)) + def getFootnote(self): + return self["footnote"].text - if not self.onNotifiers: - self.onNotifiers.append(self.levelChanged) - self.onClose.append(self.removeNotifier) + def moveToItem(self, item): + if item != self["config"].getCurrent(): + self["config"].setCurrentIndex(self.getIndexFromItem(item)) - if item_level > config.usage.setup_level.index: - continue - if (item_tuxtxtlevel == 1) and (config.usage.tuxtxt_font_and_res.value != "expert_mode"): - continue - if item_tunerlevel == 1 and not config.usage.frontend_priority.value in ("expert_mode", "experimental_mode"): - continue - if item_tunerlevel == 2 and not config.usage.frontend_priority.value == "experimental_mode": - continue - if item_rectunerlevel == 1 and not config.usage.recording_frontend_priority.value in ("expert_mode", "experimental_mode"): - continue - if item_rectunerlevel == 2 and not config.usage.recording_frontend_priority.value == "experimental_mode": - continue + def getIndexFromItem(self, item): + if item is None: # If there is no item position at the top of the config list. + return 0 + if item in self["config"].list: # If the item is in the config list position to that item. + return self["config"].list.index(item) + for pos, data in enumerate(self["config"].list): + if data[0] == item[0] and data[1] == item[1]: # If the label and config class match then position to that item. + return pos + return 0 # We can't match the item to the config list then position to the top of the list. - requires = x.get("requires") - if requires and requires.startswith('config.'): - item = eval(requires or "") - if item.value and not item.value == "0": - BoxInfo.setItem(requires, True) - else: - BoxInfo.setItem(requires, False) + def createSummary(self): + return SetupSummary - if requires and not BoxInfo.getItem(requires, False): - continue - if self.PluginLanguageDomain: - item_text = dgettext(self.PluginLanguageDomain, ensure_str(x.get("text", "??"))) - item_description = dgettext(self.PluginLanguageDomain, ensure_str(x.get("description", " "))) - else: - item_text = _(ensure_str(x.get("text", "??"))) - item_description = _(ensure_str(x.get("description", " "))) +class SetupSummary(ScreenSummary): + def __init__(self, session, parent): + ScreenSummary.__init__(self, session, parent=parent) + self["entry"] = StaticText("") + self["value"] = StaticText("") + self["SetupTitle"] = StaticText(parent.getTitle()) # DEBUG: Deprecated widget name, this will be removed soon. + self["SetupEntry"] = StaticText("") # DEBUG: Deprecated widget name, this will be removed soon. + self["SetupValue"] = StaticText("") # DEBUG: Deprecated widget name, this will be removed soon. + if self.addWatcher not in self.onShow: + self.onShow.append(self.addWatcher) + if self.removeWatcher not in self.onHide: + self.onHide.append(self.removeWatcher) + + def addWatcher(self): + if self.selectionChanged not in self.parent.onChangedEntry: + self.parent.onChangedEntry.append(self.selectionChanged) + if self.selectionChanged not in self.parent["config"].onSelectionChanged: + self.parent["config"].onSelectionChanged.append(self.selectionChanged) + self.selectionChanged() + + def removeWatcher(self): + if self.selectionChanged in self.parent.onChangedEntry: + self.parent.onChangedEntry.remove(self.selectionChanged) + if self.selectionChanged in self.parent["config"].onSelectionChanged: + self.parent["config"].onSelectionChanged.remove(self.selectionChanged) - item_text = item_text.replace("%s %s", "%s %s" % (getMachineBrand(), getMachineName())) - item_description = item_description.replace("%s %s", "%s %s" % (getMachineBrand(), getMachineName())) - b = eval(x.text or "") - if b == "": + def selectionChanged(self): + self["entry"].setText(self.parent.getCurrentEntry()) + self["value"].setText(self.parent.getCurrentValue()) + self["SetupEntry"].setText(self.parent.getCurrentEntry()) # DEBUG: Deprecated widget name, this will be removed soon. + self["SetupValue"].setText(self.parent.getCurrentValue()) # DEBUG: Deprecated widget name, this will be removed soon. + + +# Read the setup XML file. +# +def setupDom(setup=None, plugin=None): + # Constants for checkItems() + ROOT_ALLOWED = ("setup", ) # Tags allowed in top level of setupxml entry. + ELEMENT_ALLOWED = ("item", "if") # Tags allowed in top level of setup entry. + IF_ALLOWED = ("item", "if", "elif", "else") # Tags allowed inside . + AFTER_ELSE_ALLOWED = ("item", "if") # Tags allowed after or . + CHILDREN_ALLOWED = ("setup", "if", ) # Tags that may have children. + TEXT_ALLOWED = ("item", ) # Tags that may have non-whitespace text (or tail). + KEY_ATTRIBUTES = { # Tags that have a reference key mandatory attribute. + "setup": "key", + "item": "text" + } + MANDATORY_ATTRIBUTES = { # Tags that have a list of mandatory attributes. + "setup": ("key", "title"), + "item": ("text", ) + } + + def checkItems(parentNode, key, allowed=ROOT_ALLOWED, mandatory=MANDATORY_ATTRIBUTES, reference=KEY_ATTRIBUTES): + keyText = " in '%s'" % key if key else "" + for element in parentNode: + if element.tag not in allowed: + print("[Setup] Error: Tag '%s' not permitted%s! (Permitted: '%s')" % (element.tag, keyText, ", ".join(allowed))) + continue + if mandatory and element.tag in mandatory: + valid = True + for attrib in mandatory[element.tag]: + if element.get(attrib) is None: + print("[Setup] Error: Tag '%s'%s does not contain the mandatory '%s' attribute!" % (element.tag, keyText, attrib)) + valid = False + if not valid: continue - #add to configlist - item = b - # the first b is the item itself, ignored by the configList. - # the second one is converted to string. - if not isinstance(item, ConfigNothing): - list.append((item_text, item, item_description)) + if element.tag not in TEXT_ALLOWED: + if element.text and not element.text.isspace(): + print("[Setup] Tag '%s'%s contains text '%s'." % (element.tag, keyText, element.text.strip())) + if element.tail and not element.tail.isspace(): + print("[Setup] Tag '%s'%s has trailing text '%s'." % (element.tag, keyText, element.text.strip())) + if element.tag not in CHILDREN_ALLOWED and len(element): + itemKey = "" + if element.tag in reference: + itemKey = " (%s)" % element.get(reference[element.tag]) + print("[Setup] Tag '%s'%s%s contains children where none expected." % (element.tag, itemKey, keyText)) + if element.tag in CHILDREN_ALLOWED: + if element.tag in reference: + key = element.get(reference[element.tag]) + checkItems(element, key, allowed=IF_ALLOWED) + elif element.tag == "else": + allowed = AFTER_ELSE_ALLOWED # Another else and elif not permitted after else. + elif element.tag == "elif": + pass + + setupFileDom = fromstring("") + setupFile = resolveFilename(SCOPE_PLUGINS, pathjoin(plugin, "setup.xml")) if plugin else resolveFilename(SCOPE_SKINS, "setup.xml") + global domSetups, setupModTimes + try: + modTime = getmtime(setupFile) + except OSError as err: + print("[Setup] Error %d: Unable to get '%s' modified time! (%s)" % (err.errno, setupFile, err.strerror)) + if setupFile in domSetups: + del domSetups[setupFile] + if setupFile in setupModTimes: + del setupModTimes[setupFile] + return setupFileDom + cached = setupFile in domSetups and setupFile in setupModTimes and setupModTimes[setupFile] == modTime + print("[Setup] XML%s setup file '%s', using element '%s'%s." % (" cached" if cached else "", setupFile, setup, " from plugin '%s'" % plugin if plugin else "")) + if cached: + return domSetups[setupFile] + if setupFile in domSetups: + del domSetups[setupFile] + if setupFile in setupModTimes: + del setupModTimes[setupFile] + fileDom = fileReadXML(setupFile, source=MODULE_NAME) + if fileDom is not None: + checkItems(fileDom, None) + setupFileDom = fileDom + domSetups[setupFile] = setupFileDom + setupModTimes[setupFile] = modTime + for setup in setupFileDom.findall("setup"): + key = setup.get("key") + if key: # If there is no key then this element is useless and can be skipped! + title = setup.get("title", "") + if title == "": + print("[Setup] Error: Setup key '%s' title is missing or blank!" % key) + title = "** Setup error: '%s' title is missing or blank!" % key + # print("[Setup] DEBUG: XML setup load: key='%s', title='%s'." % (key, setup.get("title", "").encode("UTF-8", errors="ignore"))) + return setupFileDom + + +# Temporary legacy interface. Known to be used by the Heinz plugin and possibly others. +# +def setupdom(setup=None, plugin=None): + return setupDom(setup, plugin) + + +# Only used in AudioSelection screen... +# +def getConfigMenuItem(configElement): + for item in setupDom().findall("./setup/item/."): + if item.text == configElement: + return _(item.attrib["text"]), eval(configElement) + return "", None -def getSetupTitle(setupId): - xmldata = setupdom().getroot() - for x in xmldata.findall("setup"): - if x.get("key") == setupId: - if _(ensure_str(x.get("title", ""))) == _("OSD Settings") or _(ensure_str(x.get("title", ""))) == _("Softcam Setup") or _(ensure_str(x.get("title", ""))) == _("EPG settings"): - return _("Settings...") - return ensure_str(x.get("title", "")) - raise SetupError("unknown setup id '%s'!" % repr(setupId)) +# Temporary legacy interfaces. Only used in Menu screen. +# +def getSetupTitle(id): + xmlData = setupDom() + for x in xmlData.findall("setup"): + if x.get("key") == id: + return x.get("title", "") + print("[Setup] Error: Unknown setup id '%s'!" % repr(id)) + return "Unknown setup id '%s'!" % repr(id) def getSetupTitleLevel(setupId): - xmldata = setupdom().getroot() - for x in xmldata.findall("setup"): + xmlData = setupDom() + for x in xmlData.findall("setup"): if x.get("key") == setupId: return int(x.get("level", 0)) return 0