def handleKey(self, key): isSelectionSubmenu = isinstance(self._selection, cli.menu.item.Submenu) selectionHierarchy = self._selection.getHierarchy() if uiTools.isSelectionKey(key): if isSelectionSubmenu: if not self._selection.isEmpty(): self._selection = self._selection.getChildren()[0] else: self._isDone = self._selection.select() elif key == curses.KEY_UP: self._selection = self._selection.prev() elif key == curses.KEY_DOWN: self._selection = self._selection.next() elif key == curses.KEY_LEFT: if len(selectionHierarchy) <= 3: # shift to the previous main submenu prevSubmenu = selectionHierarchy[1].prev() self._selection = prevSubmenu.getChildren()[0] else: # go up a submenu level self._selection = self._selection.getParent() elif key == curses.KEY_RIGHT: if isSelectionSubmenu: # open submenu (same as making a selection) if not self._selection.isEmpty(): self._selection = self._selection.getChildren()[0] else: # shift to the next main submenu nextSubmenu = selectionHierarchy[1].next() self._selection = nextSubmenu.getChildren()[0] elif key in (27, ord('m'), ord('M')): # close menu self._isDone = True
def resizeGraph(self): """ Prompts for user input to resize the graph panel. Options include... down arrow - grow graph up arrow - shrink graph enter / space - set size """ control = cli.controller.getController() panel.CURSES_LOCK.acquire() try: while True: msg = "press the down/up to resize the graph, and enter when done" control.setMsg(msg, curses.A_BOLD, True) curses.cbreak() key = control.getScreen().getch() if key == curses.KEY_DOWN: # don't grow the graph if it's already consuming the whole display # (plus an extra line for the graph/log gap) maxHeight = self.parent.getmaxyx()[0] - self.top currentHeight = self.getHeight() if currentHeight < maxHeight + 1: self.setGraphHeight(self.graphHeight + 1) elif key == curses.KEY_UP: self.setGraphHeight(self.graphHeight - 1) elif uiTools.isSelectionKey(key): break control.redraw() finally: control.setMsg() panel.CURSES_LOCK.release()
def handleKey(self, key): self.valsLock.acquire() isKeystrokeConsumed = True if uiTools.isScrollKey(key): pageHeight = self.getPreferredSize()[0] - 1 if self._showDetails: pageHeight -= (DETAILS_HEIGHT + 1) isChanged = self._scroller.handleKey(key, self._entryLines, pageHeight) if isChanged: self.redraw(True) elif uiTools.isSelectionKey(key): self._showDetails = not self._showDetails self.redraw(True) elif key == ord('s') or key == ord('S'): self.showSortDialog() elif key == ord('u') or key == ord('U'): # provides a menu to pick the connection resolver title = "Resolver Util:" options = ["auto"] + list(connections.Resolver) connResolver = connections.getResolver("tor") currentOverwrite = connResolver.overwriteResolver if currentOverwrite == None: oldSelection = 0 else: oldSelection = options.index(currentOverwrite) selection = cli.popups.showMenu(title, options, oldSelection) # applies new setting if selection != -1: selectedOption = options[selection] if selection != 0 else None connResolver.overwriteResolver = selectedOption elif key == ord('l') or key == ord('L'): # provides a menu to pick the primary information we list connections by title = "List By:" options = list(entries.ListingType) # dropping the HOSTNAME listing type until we support displaying that content options.remove(cli.connections.entries.ListingType.HOSTNAME) oldSelection = options.index(self.getListingType()) selection = cli.popups.showMenu(title, options, oldSelection) # applies new setting if selection != -1: self.setListingType(options[selection]) elif key == ord('d') or key == ord('D'): # presents popup for raw consensus data descriptorPopup.showDescriptorPopup(self) elif (key == ord('c') or key == ord('C')) and self.isClientsAllowed(): countPopup.showCountDialog(countPopup.CountType.CLIENT_LOCALE, self._clientLocaleUsage) elif (key == ord('e') or key == ord('E')) and self.isExitsAllowed(): countPopup.showCountDialog(countPopup.CountType.EXIT_PORT, self._exitPortUsage) else: isKeystrokeConsumed = False self.valsLock.release() return isKeystrokeConsumed
def handleKey(self, key): self.valsLock.acquire() isKeystrokeConsumed = True if uiTools.isScrollKey(key): pageHeight = self.getPreferredSize()[0] - 1 if self._showDetails: pageHeight -= (DETAILS_HEIGHT + 1) isChanged = self._scroller.handleKey(key, self._entryLines, pageHeight) if isChanged: self.redraw(True) elif uiTools.isSelectionKey(key): self._showDetails = not self._showDetails self.redraw(True) elif key == ord('s') or key == ord('S'): self.showSortDialog() elif key == ord('u') or key == ord('U'): # provides a menu to pick the connection resolver title = "Resolver Util:" options = ["auto"] + connections.Resolver.values() connResolver = connections.getResolver("tor") currentOverwrite = connResolver.overwriteResolver if currentOverwrite == None: oldSelection = 0 else: oldSelection = options.index(currentOverwrite) selection = cli.popups.showMenu(title, options, oldSelection) # applies new setting if selection != -1: selectedOption = options[selection] if selection != 0 else None connResolver.overwriteResolver = selectedOption elif key == ord('l') or key == ord('L'): # provides a menu to pick the primary information we list connections by title = "List By:" options = entries.ListingType.values() # dropping the HOSTNAME listing type until we support displaying that content options.remove(cli.connections.entries.ListingType.HOSTNAME) oldSelection = options.index(self._listingType) selection = cli.popups.showMenu(title, options, oldSelection) # applies new setting if selection != -1: self.setListingType(options[selection]) elif key == ord('d') or key == ord('D'): # presents popup for raw consensus data descriptorPopup.showDescriptorPopup(self) elif (key == ord('c') or key == ord('C')) and self.isClientsAllowed(): countPopup.showCountDialog(countPopup.CountType.CLIENT_LOCALE, self._clientLocaleUsage) elif (key == ord('e') or key == ord('E')) and self.isExitsAllowed(): countPopup.showCountDialog(countPopup.CountType.EXIT_PORT, self._exitPortUsage) else: isKeystrokeConsumed = False self.valsLock.release() return isKeystrokeConsumed
def showMenu(title, options, oldSelection): """ Provides menu with options laid out in a single column. User can cancel selection with the escape key, in which case this proives -1. Otherwise this returns the index of the selection. Arguments: title - title displayed for the popup window options - ordered listing of options to display oldSelection - index of the initially selected option (uses the first selection without a carrot if -1) """ maxWidth = max(map(len, options)) + 9 popup, _, _ = init(len(options) + 2, maxWidth) if not popup: return key, selection = 0, oldSelection if oldSelection != -1 else 0 try: # hides the title of the first panel on the page control = cli.controller.getController() topPanel = control.getDisplayPanels(includeSticky = False)[0] topPanel.setTitleVisible(False) topPanel.redraw(True) curses.cbreak() # wait indefinitely for key presses (no timeout) while not uiTools.isSelectionKey(key): popup.win.erase() popup.win.box() popup.addstr(0, 0, title, curses.A_STANDOUT) for i in range(len(options)): label = options[i] format = curses.A_STANDOUT if i == selection else curses.A_NORMAL tab = "> " if i == oldSelection else " " popup.addstr(i + 1, 2, tab) popup.addstr(i + 1, 4, " %s " % label, format) popup.win.refresh() key = control.getScreen().getch() if key == curses.KEY_UP: selection = max(0, selection - 1) elif key == curses.KEY_DOWN: selection = min(len(options) - 1, selection + 1) elif key == 27: selection, key = -1, curses.KEY_ENTER # esc - cancel finally: topPanel.setTitleVisible(True) finalize() return selection
def handleKey(self, key): isKeystrokeConsumed = True if uiTools.isSelectionKey(key): self.prompt() elif uiTools.isScrollKey(key) and not self.isInputMode: pageHeight = self.getPreferredSize()[0] - 1 displayLength = len(self.interpretor.getDisplayContents(PROMPT_LINE)) newScroll = uiTools.getScrollPosition(key, self.scroll, pageHeight, displayLength) if self.scroll != newScroll: self.scroll = newScroll self.redraw(True) else: isKeystrokeConsumed = False return isKeystrokeConsumed
def showHelpPopup(): """ Presents a popup with instructions for the current page's hotkeys. This returns the user input used to close the popup. If the popup didn't close properly, this is an arrow, enter, or scroll key then this returns None. """ popup, _, height = init(9, 80) if not popup: return exitKey = None try: control = cli.controller.getController() pagePanels = control.getDisplayPanels() # the first page is the only one with multiple panels, and it looks better # with the log entries first, so reversing the order pagePanels.reverse() helpOptions = [] for entry in pagePanels: helpOptions += entry.getHelp() # test doing afterward in case of overwriting popup.win.box() popup.addstr(0, 0, "Page %i Commands:" % (control.getPage() + 1), curses.A_STANDOUT) for i in range(len(helpOptions)): if i / 2 >= height - 2: break # draws entries in the form '<key>: <description>[ (<selection>)]', for # instance... # u: duplicate log entries (hidden) key, description, selection = helpOptions[i] if key: description = ": " + description row = (i / 2) + 1 col = 2 if i % 2 == 0 else 41 popup.addstr(row, col, key, curses.A_BOLD) col += len(key) popup.addstr(row, col, description) col += len(description) if selection: popup.addstr(row, col, " (") popup.addstr(row, col + 2, selection, curses.A_BOLD) popup.addstr(row, col + 2 + len(selection), ")") # tells user to press a key if the lower left is unoccupied if len(helpOptions) < 13 and height == 9: popup.addstr(7, 2, "Press any key...") popup.win.refresh() curses.cbreak() exitKey = control.getScreen().getch() finally: finalize() if not uiTools.isSelectionKey(exitKey) and \ not uiTools.isScrollKey(exitKey) and \ not exitKey in (curses.KEY_LEFT, curses.KEY_RIGHT): return exitKey else: return None
def showSortDialog(title, options, oldSelection, optionColors): """ Displays a sorting dialog of the form: Current Order: <previous selection> New Order: <selections made> <option 1> <option 2> <option 3> Cancel Options are colored when among the "Current Order" or "New Order", but not when an option below them. If cancel is selected or the user presses escape then this returns None. Otherwise, the new ordering is provided. Arguments: title - title displayed for the popup window options - ordered listing of option labels oldSelection - current ordering optionColors - mappings of options to their color """ popup, _, _ = init(9, 80) if not popup: return newSelections = [] # new ordering try: cursorLoc = 0 # index of highlighted option curses.cbreak() # wait indefinitely for key presses (no timeout) selectionOptions = list(options) selectionOptions.append("Cancel") while len(newSelections) < len(oldSelection): popup.win.erase() popup.win.box() popup.addstr(0, 0, title, curses.A_STANDOUT) _drawSortSelection(popup, 1, 2, "Current Order: ", oldSelection, optionColors) _drawSortSelection(popup, 2, 2, "New Order: ", newSelections, optionColors) # presents remaining options, each row having up to four options with # spacing of nineteen cells row, col = 4, 0 for i in range(len(selectionOptions)): optionFormat = curses.A_STANDOUT if cursorLoc == i else curses.A_NORMAL popup.addstr(row, col * 19 + 2, selectionOptions[i], optionFormat) col += 1 if col == 4: row, col = row + 1, 0 popup.win.refresh() key = cli.controller.getController().getScreen().getch() if key == curses.KEY_LEFT: cursorLoc = max(0, cursorLoc - 1) elif key == curses.KEY_RIGHT: cursorLoc = min(len(selectionOptions) - 1, cursorLoc + 1) elif key == curses.KEY_UP: cursorLoc = max(0, cursorLoc - 4) elif key == curses.KEY_DOWN: cursorLoc = min(len(selectionOptions) - 1, cursorLoc + 4) elif uiTools.isSelectionKey(key): selection = selectionOptions[cursorLoc] if selection == "Cancel": break else: newSelections.append(selection) selectionOptions.remove(selection) cursorLoc = min(cursorLoc, len(selectionOptions) - 1) elif key == 27: break # esc - cancel finally: finalize() if len(newSelections) == len(oldSelection): return newSelections else: return None
def showConfirmationDialog(torrcContents, torrcLocation): """ Shows a confirmation dialog with the given torrc contents, returning CANCEL, NEXT, or BACK based on the selection. Arguments: torrcContents - lines of torrc contents to be presented torrcLocation - path where the torrc will be placed """ torrcLines = torrcContents.split("\n") options = ["Cancel", "Back to Setup", "Start Tor"] control = cli.controller.getController() screenHeight = control.getScreen().getmaxyx()[0] stickyHeight = sum([stickyPanel.getHeight() for stickyPanel in control.getStickyPanels()]) isScrollbarVisible = len(torrcLines) + stickyHeight + 5 > screenHeight xOffset = 3 if isScrollbarVisible else 0 popup, width, height = cli.popups.init(len(torrcLines) + 5, 84 + xOffset) if not popup: return False try: scroll, selection = 0, 2 curses.cbreak() while True: popup.win.erase() popup.win.box() # renders the scrollbar if isScrollbarVisible: popup.addScrollBar(scroll, scroll + height - 5, len(torrcLines), 1, height - 4, 1) # shows the path where the torrc will be placed titleMsg = "The following will be placed at '%s':" % torrcLocation popup.addstr(0, 0, titleMsg, curses.A_STANDOUT) # renders the torrc contents for i in range(scroll, min(len(torrcLines), height - 5 + scroll)): # parses the argument and comment from options option, arg, comment = uiTools.cropStr(torrcLines[i], width - 4 - xOffset), "", "" div = option.find("#") if div != -1: option, comment = option[:div], option[div:] div = option.strip().find(" ") if div != -1: option, arg = option[:div], option[div:] drawX = 2 + xOffset popup.addstr(i + 1 - scroll, drawX, option, curses.A_BOLD | uiTools.getColor("green")) drawX += len(option) popup.addstr(i + 1 - scroll, drawX, arg, curses.A_BOLD | uiTools.getColor("cyan")) drawX += len(arg) popup.addstr(i + 1 - scroll, drawX, comment, uiTools.getColor("white")) # divider between the torrc and the options popup.addch(height - 4, 0, curses.ACS_LTEE) popup.addch(height - 4, width, curses.ACS_RTEE) popup.hline(height - 4, 1, width - 1) if isScrollbarVisible: popup.addch(height - 4, 2, curses.ACS_BTEE) # renders the selection options confirmationMsg = "Run tor with the above configuration?" popup.addstr(height - 3, width - len(confirmationMsg) - 1, confirmationMsg, uiTools.getColor("green") | curses.A_BOLD) drawX = width - 1 for i in range(len(options) - 1, -1, -1): optionLabel = " %s " % options[i] drawX -= (len(optionLabel) + 4) selectionFormat = curses.A_STANDOUT if i == selection else curses.A_NORMAL popup.addstr(height - 2, drawX, "[", uiTools.getColor("green")) popup.addstr(height - 2, drawX + 1, optionLabel, uiTools.getColor("green") | selectionFormat | curses.A_BOLD) popup.addstr(height - 2, drawX + len(optionLabel) + 1, "]", uiTools.getColor("green")) drawX -= 1 # space gap between the options popup.win.refresh() key = cli.controller.getController().getScreen().getch() if key == curses.KEY_LEFT: selection = (selection - 1) % len(options) elif key == curses.KEY_RIGHT: selection = (selection + 1) % len(options) elif uiTools.isScrollKey(key): scroll = uiTools.getScrollPosition(key, scroll, height - 5, len(torrcLines)) elif uiTools.isSelectionKey(key): if selection == 0: return CANCEL elif selection == 1: return BACK else: return NEXT elif key in (27, ord('q'), ord('Q')): return CANCEL finally: cli.popups.finalize()
def handleKey(self, key): self.valsLock.acquire() isKeystrokeConsumed = True if uiTools.isScrollKey(key): pageHeight = self.getPreferredSize()[0] - 1 detailPanelHeight = self._config["features.config.selectionDetails.height"] if detailPanelHeight > 0 and detailPanelHeight + 2 <= pageHeight: pageHeight -= (detailPanelHeight + 1) isChanged = self.scroller.handleKey(key, self._getConfigOptions(), pageHeight) if isChanged: self.redraw(True) elif uiTools.isSelectionKey(key) and self._getConfigOptions(): # Prompts the user to edit the selected configuration value. The # interface is locked to prevent updates between setting the value # and showing any errors. panel.CURSES_LOCK.acquire() try: selection = self.getSelection() configOption = selection.get(Field.OPTION) if selection.isUnset(): initialValue = "" else: initialValue = selection.get(Field.VALUE) promptMsg = "%s Value (esc to cancel): " % configOption isPrepopulated = self._config["features.config.prepopulateEditValues"] newValue = popups.inputPrompt(promptMsg, initialValue if isPrepopulated else "") if newValue != None and newValue != initialValue: try: if selection.get(Field.TYPE) == "Boolean": # if the value's a boolean then allow for 'true' and 'false' inputs if newValue.lower() == "true": newValue = "1" elif newValue.lower() == "false": newValue = "0" elif selection.get(Field.TYPE) == "LineList": # setOption accepts list inputs when there's multiple values newValue = newValue.split(",") torTools.getConn().setOption(configOption, newValue) # forces the label to be remade with the new value selection.labelCache = None # resets the isDefault flag customOptions = torConfig.getCustomOptions() selection.fields[Field.IS_DEFAULT] = not configOption in customOptions self.redraw(True) except Exception, exc: popups.showMsg("%s (press any key)" % exc) finally: panel.CURSES_LOCK.release() elif key == ord('a') or key == ord('A'): self.showAll = not self.showAll self.redraw(True) elif key == ord('s') or key == ord('S'): self.showSortDialog() elif key == ord('v') or key == ord('V'): self.showWriteDialog() else: isKeystrokeConsumed = False self.valsLock.release() return isKeystrokeConsumed
def promptConfigOptions(relayType, config, disabledOpt): """ Prompts the user for the configuration of an internal relay. """ topContent = _splitStr( CONFIG.get("wizard.message.%s" % relayType.lower(), ""), 54) options = [ config[opt] for opt in RelayOptions[relayType] if not opt in disabledOpt ] options.append(Options.DIVIDER) options.append(ConfigOption(BACK, "general", "(to role selection)")) options.append(ConfigOption(NEXT, "general", "(to confirm options)")) popupHeight = len(topContent) + len(options) + DESC_SIZE + 5 popup, _, _ = cli.popups.init(popupHeight, 58) if not popup: return control = cli.controller.getController() key, selection = 0, 0 try: curses.cbreak() while True: popup.win.erase() popup.win.box() # provides the description for the relay type for i in range(len(topContent)): popup.addstr(i + 1, 2, topContent[i], curses.A_BOLD | uiTools.getColor(MSG_COLOR)) y, offset = len(topContent) + 1, 0 for opt in options: if opt == Options.DIVIDER: offset += 1 continue optionFormat = opt.getDisplayAttr() if opt == options[selection]: optionFormat |= curses.A_STANDOUT offset, indent = offset + 1, 0 if opt.getKey() in CONFIG["wizard.suboptions"]: # If the next entry is also a suboption then show a 'T', otherwise # end the bracketing. bracketChar, nextIndex = curses.ACS_LLCORNER, options.index( opt) + 1 if nextIndex < len(options) and isinstance( options[nextIndex], ConfigOption): if options[nextIndex].getKey( ) in CONFIG["wizard.suboptions"]: bracketChar = curses.ACS_LTEE popup.addch(y + offset, 3, bracketChar, opt.getDisplayAttr()) popup.addch(y + offset, 4, curses.ACS_HLINE, opt.getDisplayAttr()) indent = 3 labelFormat = " %%-%is%%s" % (30 - indent) label = labelFormat % (opt.getLabel(), opt.getDisplayValue()) popup.addstr(y + offset, 2 + indent, uiTools.padStr(label, 54 - indent), optionFormat) # little hack to make "Block" policies red if opt != options[selection] and not opt.getValue( ) and opt.getKey() in CUSTOM_POLICIES: optionFormat = curses.A_BOLD | uiTools.getColor("red") popup.addstr(y + offset, 33, opt.getDisplayValue(), optionFormat) # divider between the options and description offset += 2 popup.addch(y + offset, 0, curses.ACS_LTEE) popup.addch(y + offset, popup.getWidth() - 1, curses.ACS_RTEE) popup.hline(y + offset, 1, popup.getWidth() - 2) # description for the currently selected option for line in options[selection].getDescription(54, " "): offset += 1 popup.addstr(y + offset, 1, line, uiTools.getColor(MSG_COLOR)) popup.win.refresh() key = control.getScreen().getch() if key in (curses.KEY_UP, curses.KEY_DOWN): posOffset = -1 if key == curses.KEY_UP else 1 selection = (selection + posOffset) % len(options) # skips disabled options and dividers while options[selection] == Options.DIVIDER or not options[ selection].isEnabled(): selection = (selection + posOffset) % len(options) elif uiTools.isSelectionKey(key): if selection == len(options) - 2: return BACK # selected back elif selection == len(options) - 1: return NEXT # selected next elif isinstance(options[selection], ToggleConfigOption): options[selection].toggle() else: newValue = popup.getstr( y + selection + 1, 33, options[selection].getValue(), curses.A_STANDOUT | uiTools.getColor(OPTION_COLOR), 23) if newValue: try: options[selection].setValue(newValue.strip()) except ValueError, exc: cli.popups.showMsg(str(exc), 3) cli.controller.getController().redraw() elif key in (27, ord('q'), ord('Q')): return CANCEL
def showWriteDialog(self): """ Provies an interface to confirm if the configuration is saved and, if so, where. """ # display a popup for saving the current configuration configLines = torConfig.getCustomOptions(True) popup, width, height = popups.init(len(configLines) + 2) if not popup: return try: # displayed options (truncating the labels if there's limited room) if width >= 30: selectionOptions = ("Save", "Save As...", "Cancel") else: selectionOptions = ("Save", "Save As", "X") # checks if we can show options beside the last line of visible content isOptionLineSeparate = False lastIndex = min(height - 2, len(configLines) - 1) # if we don't have room to display the selection options and room to # grow then display the selection options on its own line if width < (30 + len(configLines[lastIndex])): popup.setHeight(height + 1) popup.redraw(True) # recreates the window instance newHeight, _ = popup.getPreferredSize() if newHeight > height: height = newHeight isOptionLineSeparate = True key, selection = 0, 2 while not uiTools.isSelectionKey(key): # if the popup has been resized then recreate it (needed for the # proper border height) newHeight, newWidth = popup.getPreferredSize() if (height, width) != (newHeight, newWidth): height, width = newHeight, newWidth popup.redraw(True) # if there isn't room to display the popup then cancel it if height <= 2: selection = 2 break popup.win.erase() popup.win.box() popup.addstr(0, 0, "Configuration being saved:", curses.A_STANDOUT) visibleConfigLines = height - 3 if isOptionLineSeparate else height - 2 for i in range(visibleConfigLines): line = uiTools.cropStr(configLines[i], width - 2) if " " in line: option, arg = line.split(" ", 1) popup.addstr(i + 1, 1, option, curses.A_BOLD | uiTools.getColor("green")) popup.addstr(i + 1, len(option) + 2, arg, curses.A_BOLD | uiTools.getColor("cyan")) else: popup.addstr(i + 1, 1, line, curses.A_BOLD | uiTools.getColor("green")) # draws selection options (drawn right to left) drawX = width - 1 for i in range(len(selectionOptions) - 1, -1, -1): optionLabel = selectionOptions[i] drawX -= (len(optionLabel) + 2) # if we've run out of room then drop the option (this will only # occure on tiny displays) if drawX < 1: break selectionFormat = curses.A_STANDOUT if i == selection else curses.A_NORMAL popup.addstr(height - 2, drawX, "[") popup.addstr(height - 2, drawX + 1, optionLabel, selectionFormat | curses.A_BOLD) popup.addstr(height - 2, drawX + len(optionLabel) + 1, "]") drawX -= 1 # space gap between the options popup.win.refresh() key = cli.controller.getController().getScreen().getch() if key == curses.KEY_LEFT: selection = max(0, selection - 1) elif key == curses.KEY_RIGHT: selection = min(len(selectionOptions) - 1, selection + 1) if selection in (0, 1): loadedTorrc, promptCanceled = torConfig.getTorrc(), False try: configLocation = loadedTorrc.getConfigLocation() except IOError: configLocation = "" if selection == 1: # prompts user for a configuration location configLocation = popups.inputPrompt("Save to (esc to cancel): ", configLocation) if not configLocation: promptCanceled = True if not promptCanceled: try: torConfig.saveConf(configLocation, configLines) msg = "Saved configuration to %s" % configLocation except IOError, exc: msg = "Unable to save configuration (%s)" % sysTools.getFileErrorMsg(exc) popups.showMsg(msg, 2) finally: popups.finalize()
def handleKey(self, key): self.valsLock.acquire() isKeystrokeConsumed = True if uiTools.isScrollKey(key): pageHeight = self.getPreferredSize()[0] - 1 detailPanelHeight = CONFIG["features.config.selectionDetails.height"] if detailPanelHeight > 0 and detailPanelHeight + 2 <= pageHeight: pageHeight -= (detailPanelHeight + 1) isChanged = self.scroller.handleKey(key, self._getConfigOptions(), pageHeight) if isChanged: self.redraw(True) elif uiTools.isSelectionKey(key) and self._getConfigOptions(): # Prompts the user to edit the selected configuration value. The # interface is locked to prevent updates between setting the value # and showing any errors. panel.CURSES_LOCK.acquire() try: selection = self.getSelection() configOption = selection.get(Field.OPTION) if selection.isUnset(): initialValue = "" else: initialValue = selection.get(Field.VALUE) promptMsg = "%s Value (esc to cancel): " % configOption isPrepopulated = CONFIG["features.config.prepopulateEditValues"] newValue = popups.inputPrompt(promptMsg, initialValue if isPrepopulated else "") if newValue != None and newValue != initialValue: try: if selection.get(Field.TYPE) == "Boolean": # if the value's a boolean then allow for 'true' and 'false' inputs if newValue.lower() == "true": newValue = "1" elif newValue.lower() == "false": newValue = "0" elif selection.get(Field.TYPE) == "LineList": # setOption accepts list inputs when there's multiple values newValue = newValue.split(",") torTools.getConn().setOption(configOption, newValue) # forces the label to be remade with the new value selection.labelCache = None # resets the isDefault flag customOptions = torConfig.getCustomOptions() selection.fields[Field.IS_DEFAULT] = not configOption in customOptions self.redraw(True) except Exception, exc: popups.showMsg("%s (press any key)" % exc) finally: panel.CURSES_LOCK.release() elif key == ord('a') or key == ord('A'): self.showAll = not self.showAll self.redraw(True) elif key == ord('s') or key == ord('S'): self.showSortDialog() elif key == ord('v') or key == ord('V'): self.showWriteDialog() else: isKeystrokeConsumed = False self.valsLock.release() return isKeystrokeConsumed
def showDescriptorPopup(connPanel): """ Presents consensus descriptor in popup window with the following controls: Up, Down, Page Up, Page Down - scroll descriptor Right, Left - next / previous connection Enter, Space, d, D - close popup Arguments: connPanel - connection panel providing the dialog """ # hides the title of the connection panel connPanel.setTitleVisible(False) connPanel.redraw(True) control = cli.controller.getController() panel.CURSES_LOCK.acquire() isDone = False try: while not isDone: selection = connPanel.getSelection() if not selection: break fingerprint = selection.foreign.getFingerprint() if fingerprint == "UNKNOWN": fingerprint = None displayText = getDisplayText(fingerprint) displayColor = cli.connections.connEntry.CATEGORY_COLOR[selection.getType()] showLineNumber = fingerprint != None # determines the maximum popup size the displayText can fill pHeight, pWidth = getPreferredSize(displayText, connPanel.maxX, showLineNumber) popup, _, height = cli.popups.init(pHeight, pWidth) if not popup: break scroll, isChanged = 0, True try: while not isDone: if isChanged: draw(popup, fingerprint, displayText, displayColor, scroll, showLineNumber) isChanged = False key = control.getScreen().getch() if uiTools.isScrollKey(key): # TODO: This is a bit buggy in that scrolling is by displayText # lines rather than the displayed lines, causing issues when # content wraps. The result is that we can't have a scrollbar and # can't scroll to the bottom if there's a multi-line being # displayed. However, trying to correct this introduces a big can # of worms and after hours decided that this isn't worth the # effort... newScroll = uiTools.getScrollPosition(key, scroll, height - 2, len(displayText)) if scroll != newScroll: scroll, isChanged = newScroll, True elif uiTools.isSelectionKey(key) or key in (ord('d'), ord('D')): isDone = True # closes popup elif key in (curses.KEY_LEFT, curses.KEY_RIGHT): # navigation - pass on to connPanel and recreate popup connPanel.handleKey(curses.KEY_UP if key == curses.KEY_LEFT else curses.KEY_DOWN) break finally: cli.popups.finalize() finally: connPanel.setTitleVisible(True) connPanel.redraw(True) panel.CURSES_LOCK.release()
def promptRelayType(initialSelection): """ Provides a prompt for selecting the general role we'd like Tor to run with. This returns a RelayType enumeration for the selection, or CANCEL if the dialog was canceled. """ options = [ConfigOption(opt, "role", opt) for opt in RelayType.values()] options.append(ConfigOption(CANCEL, "general", CANCEL)) selection = RelayType.indexOf(initialSelection) height = 28 # drops the resume option if it isn't applicable control = cli.controller.getController() if not control.getTorManager().isTorrcAvailable(): options.pop(0) height -= 3 selection -= 1 popup, _, _ = cli.popups.init(height, 58) if not popup: return try: popup.win.box() curses.cbreak() # provides the welcoming message topContent = _splitStr(CONFIG["wizard.message.role"], 54) for i in range(len(topContent)): popup.addstr(i + 1, 2, topContent[i], curses.A_BOLD | uiTools.getColor(MSG_COLOR)) while True: y, offset = len(topContent) + 1, 0 for opt in options: optionFormat = uiTools.getColor(MSG_COLOR) if opt == options[selection]: optionFormat |= curses.A_STANDOUT # Curses has a weird bug where there's a one-pixel alignment # difference between bold and regular text, so it looks better # to render the whitespace here as not being bold. offset += 1 label = opt.getLabel(" ") popup.addstr(y + offset, 2, label, optionFormat | curses.A_BOLD) popup.addstr(y + offset, 2 + len(label), " " * (54 - len(label)), optionFormat) offset += 1 for line in opt.getDescription(52, " "): popup.addstr(y + offset, 2, uiTools.padStr(line, 54), optionFormat) offset += 1 popup.win.refresh() key = control.getScreen().getch() if key == curses.KEY_UP: selection = (selection - 1) % len(options) elif key == curses.KEY_DOWN: selection = (selection + 1) % len(options) elif uiTools.isSelectionKey(key): return options[selection].getValue() elif key in (27, ord('q'), ord('Q')): return CANCEL # esc or q - cancel finally: cli.popups.finalize()
def showDescriptorPopup(connPanel): """ Presents consensus descriptor in popup window with the following controls: Up, Down, Page Up, Page Down - scroll descriptor Right, Left - next / previous connection Enter, Space, d, D - close popup Arguments: connPanel - connection panel providing the dialog """ # hides the title of the connection panel connPanel.setTitleVisible(False) connPanel.redraw(True) control = cli.controller.getController() panel.CURSES_LOCK.acquire() isDone = False try: while not isDone: selection = connPanel.getSelection() if not selection: break fingerprint = selection.foreign.getFingerprint() if fingerprint == "UNKNOWN": fingerprint = None displayText = getDisplayText(fingerprint) displayColor = cli.connections.connEntry.CATEGORY_COLOR[ selection.getType()] showLineNumber = fingerprint != None # determines the maximum popup size the displayText can fill pHeight, pWidth = getPreferredSize(displayText, connPanel.maxX, showLineNumber) popup, _, height = cli.popups.init(pHeight, pWidth) if not popup: break scroll, isChanged = 0, True try: while not isDone: if isChanged: draw(popup, fingerprint, displayText, displayColor, scroll, showLineNumber) isChanged = False key = control.getScreen().getch() if uiTools.isScrollKey(key): # TODO: This is a bit buggy in that scrolling is by displayText # lines rather than the displayed lines, causing issues when # content wraps. The result is that we can't have a scrollbar and # can't scroll to the bottom if there's a multi-line being # displayed. However, trying to correct this introduces a big can # of worms and after hours decided that this isn't worth the # effort... newScroll = uiTools.getScrollPosition( key, scroll, height - 2, len(displayText)) if scroll != newScroll: scroll, isChanged = newScroll, True elif uiTools.isSelectionKey(key) or key in (ord('d'), ord('D')): isDone = True # closes popup elif key in (curses.KEY_LEFT, curses.KEY_RIGHT): # navigation - pass on to connPanel and recreate popup connPanel.handleKey(curses.KEY_UP if key == curses. KEY_LEFT else curses.KEY_DOWN) break finally: cli.popups.finalize() finally: connPanel.setTitleVisible(True) connPanel.redraw(True) panel.CURSES_LOCK.release()
def showConfirmationDialog(torrcContents, torrcLocation): """ Shows a confirmation dialog with the given torrc contents, returning CANCEL, NEXT, or BACK based on the selection. Arguments: torrcContents - lines of torrc contents to be presented torrcLocation - path where the torrc will be placed """ torrcLines = torrcContents.split("\n") options = ["Cancel", "Back to Setup", "Start Tor"] control = cli.controller.getController() screenHeight = control.getScreen().getmaxyx()[0] stickyHeight = sum( [stickyPanel.getHeight() for stickyPanel in control.getStickyPanels()]) isScrollbarVisible = len(torrcLines) + stickyHeight + 5 > screenHeight xOffset = 3 if isScrollbarVisible else 0 popup, width, height = cli.popups.init(len(torrcLines) + 5, 84 + xOffset) if not popup: return False try: scroll, selection = 0, 2 curses.cbreak() while True: popup.win.erase() popup.win.box() # renders the scrollbar if isScrollbarVisible: popup.addScrollBar(scroll, scroll + height - 5, len(torrcLines), 1, height - 4, 1) # shows the path where the torrc will be placed titleMsg = "The following will be placed at '%s':" % torrcLocation popup.addstr(0, 0, titleMsg, curses.A_STANDOUT) # renders the torrc contents for i in range(scroll, min(len(torrcLines), height - 5 + scroll)): # parses the argument and comment from options option, arg, comment = uiTools.cropStr(torrcLines[i], width - 4 - xOffset), "", "" div = option.find("#") if div != -1: option, comment = option[:div], option[div:] div = option.strip().find(" ") if div != -1: option, arg = option[:div], option[div:] drawX = 2 + xOffset popup.addstr(i + 1 - scroll, drawX, option, curses.A_BOLD | uiTools.getColor("green")) drawX += len(option) popup.addstr(i + 1 - scroll, drawX, arg, curses.A_BOLD | uiTools.getColor("cyan")) drawX += len(arg) popup.addstr(i + 1 - scroll, drawX, comment, uiTools.getColor("white")) # divider between the torrc and the options popup.addch(height - 4, 0, curses.ACS_LTEE) popup.addch(height - 4, width, curses.ACS_RTEE) popup.hline(height - 4, 1, width - 1) if isScrollbarVisible: popup.addch(height - 4, 2, curses.ACS_BTEE) # renders the selection options confirmationMsg = "Run tor with the above configuration?" popup.addstr(height - 3, width - len(confirmationMsg) - 1, confirmationMsg, uiTools.getColor("green") | curses.A_BOLD) drawX = width - 1 for i in range(len(options) - 1, -1, -1): optionLabel = " %s " % options[i] drawX -= (len(optionLabel) + 4) selectionFormat = curses.A_STANDOUT if i == selection else curses.A_NORMAL popup.addstr(height - 2, drawX, "[", uiTools.getColor("green")) popup.addstr( height - 2, drawX + 1, optionLabel, uiTools.getColor("green") | selectionFormat | curses.A_BOLD) popup.addstr(height - 2, drawX + len(optionLabel) + 1, "]", uiTools.getColor("green")) drawX -= 1 # space gap between the options popup.win.refresh() key = cli.controller.getController().getScreen().getch() if key == curses.KEY_LEFT: selection = (selection - 1) % len(options) elif key == curses.KEY_RIGHT: selection = (selection + 1) % len(options) elif uiTools.isScrollKey(key): scroll = uiTools.getScrollPosition(key, scroll, height - 5, len(torrcLines)) elif uiTools.isSelectionKey(key): if selection == 0: return CANCEL elif selection == 1: return BACK else: return NEXT elif key in (27, ord('q'), ord('Q')): return CANCEL finally: cli.popups.finalize()
def promptConfigOptions(relayType, config, disabledOpt): """ Prompts the user for the configuration of an internal relay. """ topContent = _splitStr(CONFIG.get("wizard.message.%s" % relayType.lower(), ""), 54) options = [config[opt] for opt in RelayOptions[relayType] if not opt in disabledOpt] options.append(Options.DIVIDER) options.append(ConfigOption(BACK, "general", "(to role selection)")) options.append(ConfigOption(NEXT, "general", "(to confirm options)")) popupHeight = len(topContent) + len(options) + DESC_SIZE + 5 popup, _, _ = cli.popups.init(popupHeight, 58) if not popup: return control = cli.controller.getController() key, selection = 0, 0 try: curses.cbreak() while True: popup.win.erase() popup.win.box() # provides the description for the relay type for i in range(len(topContent)): popup.addstr(i + 1, 2, topContent[i], curses.A_BOLD | uiTools.getColor(MSG_COLOR)) y, offset = len(topContent) + 1, 0 for opt in options: if opt == Options.DIVIDER: offset += 1 continue optionFormat = opt.getDisplayAttr() if opt == options[selection]: optionFormat |= curses.A_STANDOUT offset, indent = offset + 1, 0 if opt.getKey() in CONFIG["wizard.suboptions"]: # If the next entry is also a suboption then show a 'T', otherwise # end the bracketing. bracketChar, nextIndex = curses.ACS_LLCORNER, options.index(opt) + 1 if nextIndex < len(options) and isinstance(options[nextIndex], ConfigOption): if options[nextIndex].getKey() in CONFIG["wizard.suboptions"]: bracketChar = curses.ACS_LTEE popup.addch(y + offset, 3, bracketChar, opt.getDisplayAttr()) popup.addch(y + offset, 4, curses.ACS_HLINE, opt.getDisplayAttr()) indent = 3 labelFormat = " %%-%is%%s" % (30 - indent) label = labelFormat % (opt.getLabel(), opt.getDisplayValue()) popup.addstr(y + offset, 2 + indent, uiTools.padStr(label, 54 - indent), optionFormat) # little hack to make "Block" policies red if opt != options[selection] and not opt.getValue() and opt.getKey() in CUSTOM_POLICIES: optionFormat = curses.A_BOLD | uiTools.getColor("red") popup.addstr(y + offset, 33, opt.getDisplayValue(), optionFormat) # divider between the options and description offset += 2 popup.addch(y + offset, 0, curses.ACS_LTEE) popup.addch(y + offset, popup.getWidth() - 1, curses.ACS_RTEE) popup.hline(y + offset, 1, popup.getWidth() - 2) # description for the currently selected option for line in options[selection].getDescription(54, " "): offset += 1 popup.addstr(y + offset, 1, line, uiTools.getColor(MSG_COLOR)) popup.win.refresh() key = control.getScreen().getch() if key in (curses.KEY_UP, curses.KEY_DOWN): posOffset = -1 if key == curses.KEY_UP else 1 selection = (selection + posOffset) % len(options) # skips disabled options and dividers while options[selection] == Options.DIVIDER or not options[selection].isEnabled(): selection = (selection + posOffset) % len(options) elif uiTools.isSelectionKey(key): if selection == len(options) - 2: return BACK # selected back elif selection == len(options) - 1: return NEXT # selected next elif isinstance(options[selection], ToggleConfigOption): options[selection].toggle() else: newValue = popup.getstr(y + selection + 1, 33, options[selection].getValue(), curses.A_STANDOUT | uiTools.getColor(OPTION_COLOR), 23) if newValue: try: options[selection].setValue(newValue.strip()) except ValueError, exc: cli.popups.showMsg(str(exc), 3) cli.controller.getController().redraw() elif key in (27, ord('q'), ord('Q')): return CANCEL finally: cli.popups.finalize()