def draw(self, panel, width, height): # line of the graph's x-axis labeling labelingLine = graphPanel.GraphStats.getContentHeight( self) + panel.graphHeight - 2 # if display is narrow, overwrites x-axis labels with avg / total stats if width <= COLLAPSE_WIDTH: # clears line panel.addstr(labelingLine, 0, " " * width) graphCol = min((width - 10) / 2, self.maxCol) primaryFooter = "%s, %s" % (self._getAvgLabel(True), self._getTotalLabel(True)) secondaryFooter = "%s, %s" % (self._getAvgLabel(False), self._getTotalLabel(False)) panel.addstr(labelingLine, 1, primaryFooter, uiTools.getColor(self.getColor(True))) panel.addstr(labelingLine, graphCol + 6, secondaryFooter, uiTools.getColor(self.getColor(False))) # provides accounting stats if enabled if self.isAccounting: if torTools.getConn().isAlive(): status = self.accountingInfo["status"] hibernateColor = "green" if status == "soft": hibernateColor = "yellow" elif status == "hard": hibernateColor = "red" elif status == "": # failed to be queried status, hibernateColor = "unknown", "red" panel.addfstr( labelingLine + 2, 0, "<b>Accounting (<%s>%s</%s>)</b>" % (hibernateColor, status, hibernateColor)) resetTime = self.accountingInfo["resetTime"] if not resetTime: resetTime = "unknown" panel.addstr(labelingLine + 2, 35, "Time to reset: %s" % resetTime) used, total = self.accountingInfo["read"], self.accountingInfo[ "readLimit"] if used and total: panel.addstr(labelingLine + 3, 2, "%s / %s" % (used, total), uiTools.getColor(self.getColor(True))) used, total = self.accountingInfo[ "written"], self.accountingInfo["writtenLimit"] if used and total: panel.addstr(labelingLine + 3, 37, "%s / %s" % (used, total), uiTools.getColor(self.getColor(False))) else: panel.addfstr(labelingLine + 2, 0, "<b>Accounting:</b> Connection Closed...")
def draw(popup, properties): popup.clear() popup.win.box() # top label popup.addstr(0, 0, "Open File Descriptors:", curses.A_STANDOUT) if properties.errorMsg: popup.addstr(1, 2, properties.errorMsg, curses.A_BOLD | uiTools.getColor("red")) else: # text with file descriptor count and limit fdCount = len(properties.fdFile) + len(properties.fdConn) + len(properties.fdMisc) fdCountPer = 100 * fdCount / max(properties.fdLimit, 1) statsColor = "green" if fdCountPer >= 90: statsColor = "red" elif fdCountPer >= 50: statsColor = "yellow" countMsg = "%i / %i (%i%%)" % (fdCount, properties.fdLimit, fdCountPer) popup.addstr(1, 2, countMsg, curses.A_BOLD | uiTools.getColor(statsColor)) # provides a progress bar reflecting the stats barWidth = popup.maxX - len(countMsg) - 6 # space between "[ ]" in progress bar barProgress = barWidth * fdCountPer / 100 # filled cells if fdCount > 0: barProgress = max(1, barProgress) # ensures one cell is filled unless really zero popup.addstr(1, len(countMsg) + 3, "[", curses.A_BOLD) popup.addstr(1, len(countMsg) + 4, " " * barProgress, curses.A_STANDOUT | uiTools.getColor(statsColor)) popup.addstr(1, len(countMsg) + 4 + barWidth, "]", curses.A_BOLD) popup.win.hline(2, 1, curses.ACS_HLINE, popup.maxX - 2) # scrollable file descriptor listing lineNum = 3 entryNum = properties.scroll while lineNum <= popup.maxY - 2: if entryNum < len(properties.fdFile): line = properties.fdFile[entryNum] color = "green" elif entryNum < len(properties.fdFile) + len(properties.fdMisc): line = properties.fdMisc[entryNum - len(properties.fdFile)] color = "cyan" else: line = properties.fdConn[entryNum - len(properties.fdFile) - len(properties.fdMisc)] color = "blue" popup.addstr(lineNum, 2, line, curses.A_BOLD | uiTools.getColor(color)) lineNum += 1 entryNum += 1 popup.refresh()
def _drawSubmenu(cursor, level, top, left): selectionHierarchy = cursor.getSelection().getHierarchy() # checks if there's nothing to display if len(selectionHierarchy) < level + 2: return # fetches the submenu and selection we're displaying submenu = selectionHierarchy[level] selection = selectionHierarchy[level + 1] # gets the size of the prefix, middle, and suffix columns allLabelSets = [entry.getLabel() for entry in submenu.getChildren()] prefixColSize = max([len(entry[0]) for entry in allLabelSets]) middleColSize = max([len(entry[1]) for entry in allLabelSets]) suffixColSize = max([len(entry[2]) for entry in allLabelSets]) # formatted string so we can display aligned menu entries labelFormat = " %%-%is%%-%is%%-%is " % (prefixColSize, middleColSize, suffixColSize) menuWidth = len(labelFormat % ("", "", "")) popup, _, _ = cli.popups.init(len(submenu.getChildren()), menuWidth, top, left, belowStatic=False) if not popup: return try: # sets the background color popup.win.bkgd(' ', curses.A_STANDOUT | uiTools.getColor("red")) drawTop, selectionTop = 0, 0 for menuItem in submenu.getChildren(): if menuItem == selection: drawFormat = curses.A_BOLD | uiTools.getColor("white") selectionTop = drawTop else: drawFormat = curses.A_NORMAL popup.addstr(drawTop, 0, labelFormat % menuItem.getLabel(), drawFormat) drawTop += 1 popup.win.refresh() # shows the next submenu _drawSubmenu(cursor, level + 1, top + selectionTop, left + menuWidth) finally: cli.popups.finalize()
def getDetails(self, width): if not self.isBuilt: detailFormat = curses.A_BOLD | uiTools.getColor( connEntry.CATEGORY_COLOR[self.getType()]) return [("Building Circuit...", detailFormat)] else: return connEntry.ConnectionLine.getDetails(self, width)
def _drawSortSelection(popup, y, x, prefix, options, optionColors): """ Draws a series of comma separated sort selections. The whole line is bold and sort options also have their specified color. Example: Current Order: Man Page Entry, Option Name, Is Default Arguments: popup - panel in which to draw sort selection y - vertical location x - horizontal location prefix - initial string description options - sort options to be shown optionColors - mappings of options to their color """ popup.addstr(y, x, prefix, curses.A_BOLD) x += len(prefix) for i in range(len(options)): sortType = options[i] sortColor = uiTools.getColor(optionColors.get(sortType, "white")) popup.addstr(y, x, sortType, sortColor | curses.A_BOLD) x += len(sortType) # comma divider between options, if this isn't the last if i < len(options) - 1: popup.addstr(y, x, ", ", curses.A_BOLD) x += 2
def _drawSelectionPanel(self, selection, width, detailPanelHeight, isScrollbarVisible): """ Renders a panel for the selected configuration option. """ # This is a solid border unless the scrollbar is visible, in which case a # 'T' pipe connects the border to the bar. uiTools.drawBox(self, 0, 0, width, detailPanelHeight + 1) if isScrollbarVisible: self.addch(detailPanelHeight, 1, curses.ACS_TTEE) selectionFormat = curses.A_BOLD | uiTools.getColor(CATEGORY_COLOR[selection.get(Field.CATEGORY)]) # first entry: # <option> (<category> Option) optionLabel = " (%s Option)" % selection.get(Field.CATEGORY) self.addstr(1, 2, selection.get(Field.OPTION) + optionLabel, selectionFormat) # second entry: # Value: <value> ([default|custom], <type>, usage: <argument usage>) if detailPanelHeight >= 3: valueAttr = [] valueAttr.append("default" if selection.get(Field.IS_DEFAULT) else "custom") valueAttr.append(selection.get(Field.TYPE)) valueAttr.append("usage: %s" % (selection.get(Field.ARG_USAGE))) valueAttrLabel = ", ".join(valueAttr) valueLabelWidth = width - 12 - len(valueAttrLabel) valueLabel = uiTools.cropStr(selection.get(Field.VALUE), valueLabelWidth) self.addstr(2, 2, "Value: %s (%s)" % (valueLabel, valueAttrLabel), selectionFormat) # remainder is filled with the man page description descriptionHeight = max(0, detailPanelHeight - 3) descriptionContent = "Description: " + selection.get(Field.DESCRIPTION) for i in range(descriptionHeight): # checks if we're done writing the description if not descriptionContent: break # there's a leading indent after the first line if i > 0: descriptionContent = " " + descriptionContent # we only want to work with content up until the next newline if "\n" in descriptionContent: lineContent, descriptionContent = descriptionContent.split("\n", 1) else: lineContent, descriptionContent = descriptionContent, "" if i != descriptionHeight - 1: # there's more lines to display msg, remainder = uiTools.cropStr(lineContent, width - 3, 4, 4, uiTools.Ending.HYPHEN, True) descriptionContent = remainder.strip() + descriptionContent else: # this is the last line, end it with an ellipse msg = uiTools.cropStr(lineContent, width - 3, 4, 4) self.addstr(3 + i, 2, msg, selectionFormat)
def draw(self, width, height): self.valsLock.acquire() # panel with details for the current selection detailPanelHeight = self._config["features.config.selectionDetails.height"] isScrollbarVisible = False if detailPanelHeight == 0 or detailPanelHeight + 2 >= height: # no detail panel detailPanelHeight = 0 scrollLoc = self.scroller.getScrollLoc(self._getConfigOptions(), height - 1) cursorSelection = self.getSelection() isScrollbarVisible = len(self._getConfigOptions()) > height - 1 else: # Shrink detail panel if there isn't sufficient room for the whole # thing. The extra line is for the bottom border. detailPanelHeight = min(height - 1, detailPanelHeight + 1) scrollLoc = self.scroller.getScrollLoc(self._getConfigOptions(), height - 1 - detailPanelHeight) cursorSelection = self.getSelection() isScrollbarVisible = len(self._getConfigOptions()) > height - detailPanelHeight - 1 if cursorSelection != None: self._drawSelectionPanel(cursorSelection, width, detailPanelHeight, isScrollbarVisible) # draws the top label if self.isTitleVisible(): configType = "Tor" if self.configType == State.TOR else "Arm" hiddenMsg = "press 'a' to hide most options" if self.showAll else "press 'a' to show all options" titleLabel = "%s Configuration (%s):" % (configType, hiddenMsg) self.addstr(0, 0, titleLabel, curses.A_STANDOUT) # draws left-hand scroll bar if content's longer than the height scrollOffset = 1 if isScrollbarVisible: scrollOffset = 3 self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelHeight - 1, len(self._getConfigOptions()), 1 + detailPanelHeight) optionWidth = self._config["features.config.state.colWidth.option"] valueWidth = self._config["features.config.state.colWidth.value"] descriptionWidth = max(0, width - scrollOffset - optionWidth - valueWidth - 2) # if the description column is overly long then use its space for the # value instead if descriptionWidth > 80: valueWidth += descriptionWidth - 80 descriptionWidth = 80 for lineNum in range(scrollLoc, len(self._getConfigOptions())): entry = self._getConfigOptions()[lineNum] drawLine = lineNum + detailPanelHeight + 1 - scrollLoc lineFormat = curses.A_NORMAL if entry.get(Field.IS_DEFAULT) else curses.A_BOLD if entry.get(Field.CATEGORY): lineFormat |= uiTools.getColor(CATEGORY_COLOR[entry.get(Field.CATEGORY)]) if entry == cursorSelection: lineFormat |= curses.A_STANDOUT lineText = entry.getLabel(optionWidth, valueWidth, descriptionWidth) self.addstr(drawLine, scrollOffset, lineText, lineFormat) if drawLine >= height: break self.valsLock.release()
def draw(self, width, height): self.valsLock.acquire() # panel with details for the current selection detailPanelHeight = CONFIG["features.config.selectionDetails.height"] isScrollbarVisible = False if detailPanelHeight == 0 or detailPanelHeight + 2 >= height: # no detail panel detailPanelHeight = 0 scrollLoc = self.scroller.getScrollLoc(self._getConfigOptions(), height - 1) cursorSelection = self.getSelection() isScrollbarVisible = len(self._getConfigOptions()) > height - 1 else: # Shrink detail panel if there isn't sufficient room for the whole # thing. The extra line is for the bottom border. detailPanelHeight = min(height - 1, detailPanelHeight + 1) scrollLoc = self.scroller.getScrollLoc(self._getConfigOptions(), height - 1 - detailPanelHeight) cursorSelection = self.getSelection() isScrollbarVisible = len(self._getConfigOptions()) > height - detailPanelHeight - 1 if cursorSelection != None: self._drawSelectionPanel(cursorSelection, width, detailPanelHeight, isScrollbarVisible) # draws the top label if self.isTitleVisible(): configType = "Tor" if self.configType == State.TOR else "Arm" hiddenMsg = "press 'a' to hide most options" if self.showAll else "press 'a' to show all options" titleLabel = "%s Configuration (%s):" % (configType, hiddenMsg) self.addstr(0, 0, titleLabel, curses.A_STANDOUT) # draws left-hand scroll bar if content's longer than the height scrollOffset = 1 if isScrollbarVisible: scrollOffset = 3 self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelHeight - 1, len(self._getConfigOptions()), 1 + detailPanelHeight) optionWidth = CONFIG["features.config.state.colWidth.option"] valueWidth = CONFIG["features.config.state.colWidth.value"] descriptionWidth = max(0, width - scrollOffset - optionWidth - valueWidth - 2) # if the description column is overly long then use its space for the # value instead if descriptionWidth > 80: valueWidth += descriptionWidth - 80 descriptionWidth = 80 for lineNum in range(scrollLoc, len(self._getConfigOptions())): entry = self._getConfigOptions()[lineNum] drawLine = lineNum + detailPanelHeight + 1 - scrollLoc lineFormat = curses.A_NORMAL if entry.get(Field.IS_DEFAULT) else curses.A_BOLD if entry.get(Field.CATEGORY): lineFormat |= uiTools.getColor(CATEGORY_COLOR[entry.get(Field.CATEGORY)]) if entry == cursorSelection: lineFormat |= curses.A_STANDOUT lineText = entry.getLabel(optionWidth, valueWidth, descriptionWidth) self.addstr(drawLine, scrollOffset, lineText, lineFormat) if drawLine >= height: break self.valsLock.release()
def draw(self, panel, width, height): # line of the graph's x-axis labeling labelingLine = graphPanel.GraphStats.getContentHeight(self) + panel.graphHeight - 2 # if display is narrow, overwrites x-axis labels with avg / total stats if width <= COLLAPSE_WIDTH: # clears line panel.addstr(labelingLine, 0, " " * width) graphCol = min((width - 10) / 2, self.maxCol) primaryFooter = "%s, %s" % (self._getAvgLabel(True), self._getTotalLabel(True)) secondaryFooter = "%s, %s" % (self._getAvgLabel(False), self._getTotalLabel(False)) panel.addstr(labelingLine, 1, primaryFooter, uiTools.getColor(self.getColor(True))) panel.addstr(labelingLine, graphCol + 6, secondaryFooter, uiTools.getColor(self.getColor(False))) # provides accounting stats if enabled if self.isAccounting: if torTools.getConn().isAlive(): status = self.accountingInfo["status"] hibernateColor = "green" if status == "soft": hibernateColor = "yellow" elif status == "hard": hibernateColor = "red" elif status == "": # failed to be queried status, hibernateColor = "unknown", "red" panel.addstr(labelingLine + 2, 0, "Accounting (", curses.A_BOLD) panel.addstr(labelingLine + 2, 12, status, curses.A_BOLD | uiTools.getColor(hibernateColor)) panel.addstr(labelingLine + 2, 12 + len(status), ")", curses.A_BOLD) resetTime = self.accountingInfo["resetTime"] if not resetTime: resetTime = "unknown" panel.addstr(labelingLine + 2, 35, "Time to reset: %s" % resetTime) used, total = self.accountingInfo["read"], self.accountingInfo["readLimit"] if used and total: panel.addstr(labelingLine + 3, 2, "%s / %s" % (used, total), uiTools.getColor(self.getColor(True))) used, total = self.accountingInfo["written"], self.accountingInfo["writtenLimit"] if used and total: panel.addstr(labelingLine + 3, 37, "%s / %s" % (used, total), uiTools.getColor(self.getColor(False))) else: panel.addstr(labelingLine + 2, 0, "Accounting:", curses.A_BOLD) panel.addstr(labelingLine + 2, 12, "Connection Closed...")
def _drawSelectionPanel(self, selection, width, detailPanelHeight, isScrollbarVisible): """ Renders a panel for the selected configuration option. """ # This is a solid border unless the scrollbar is visible, in which case a # 'T' pipe connects the border to the bar. uiTools.drawBox(self, 0, 0, width, detailPanelHeight + 1) if isScrollbarVisible: self.addch(detailPanelHeight, 1, curses.ACS_TTEE) selectionFormat = curses.A_BOLD | uiTools.getColor(CATEGORY_COLOR[selection.get(Field.CATEGORY)]) # first entry: # <option> (<category> Option) optionLabel =" (%s Option)" % selection.get(Field.CATEGORY) self.addstr(1, 2, selection.get(Field.OPTION) + optionLabel, selectionFormat) # second entry: # Value: <value> ([default|custom], <type>, usage: <argument usage>) if detailPanelHeight >= 3: valueAttr = [] valueAttr.append("default" if selection.get(Field.IS_DEFAULT) else "custom") valueAttr.append(selection.get(Field.TYPE)) valueAttr.append("usage: %s" % (selection.get(Field.ARG_USAGE))) valueAttrLabel = ", ".join(valueAttr) valueLabelWidth = width - 12 - len(valueAttrLabel) valueLabel = uiTools.cropStr(selection.get(Field.VALUE), valueLabelWidth) self.addstr(2, 2, "Value: %s (%s)" % (valueLabel, valueAttrLabel), selectionFormat) # remainder is filled with the man page description descriptionHeight = max(0, detailPanelHeight - 3) descriptionContent = "Description: " + selection.get(Field.DESCRIPTION) for i in range(descriptionHeight): # checks if we're done writing the description if not descriptionContent: break # there's a leading indent after the first line if i > 0: descriptionContent = " " + descriptionContent # we only want to work with content up until the next newline if "\n" in descriptionContent: lineContent, descriptionContent = descriptionContent.split("\n", 1) else: lineContent, descriptionContent = descriptionContent, "" if i != descriptionHeight - 1: # there's more lines to display msg, remainder = uiTools.cropStr(lineContent, width - 3, 4, 4, uiTools.Ending.HYPHEN, True) descriptionContent = remainder.strip() + descriptionContent else: # this is the last line, end it with an ellipse msg = uiTools.cropStr(lineContent, width - 3, 4, 4) self.addstr(3 + i, 2, msg, selectionFormat)
def _drawSubmenu(cursor, level, top, left): selectionHierarchy = cursor.getSelection().getHierarchy() # checks if there's nothing to display if len(selectionHierarchy) < level + 2: return # fetches the submenu and selection we're displaying submenu = selectionHierarchy[level] selection = selectionHierarchy[level + 1] # gets the size of the prefix, middle, and suffix columns allLabelSets = [entry.getLabel() for entry in submenu.getChildren()] prefixColSize = max([len(entry[0]) for entry in allLabelSets]) middleColSize = max([len(entry[1]) for entry in allLabelSets]) suffixColSize = max([len(entry[2]) for entry in allLabelSets]) # formatted string so we can display aligned menu entries labelFormat = " %%-%is%%-%is%%-%is " % (prefixColSize, middleColSize, suffixColSize) menuWidth = len(labelFormat % ("", "", "")) popup, _, _ = cli.popups.init(len(submenu.getChildren()), menuWidth, top, left, belowStatic = False) if not popup: return try: # sets the background color popup.win.bkgd(' ', curses.A_STANDOUT | uiTools.getColor("red")) drawTop, selectionTop = 0, 0 for menuItem in submenu.getChildren(): if menuItem == selection: drawFormat = curses.A_BOLD | uiTools.getColor("white") selectionTop = drawTop else: drawFormat = curses.A_NORMAL popup.addstr(drawTop, 0, labelFormat % menuItem.getLabel(), drawFormat) drawTop += 1 popup.win.refresh() # shows the next submenu _drawSubmenu(cursor, level + 1, top + selectionTop, left + menuWidth) finally: cli.popups.finalize()
def draw(self, subwindow, width, height): self.valsLock.acquire() # draws the top label titleLabel = "%s Configuration:" % ("Tor" if self.configType == TOR_STATE else "Arm") self.addstr(0, 0, titleLabel, curses.A_STANDOUT) # panel with details for the current selection detailPanelHeight = self._config["features.config.selectionDetails.height"] if detailPanelHeight == 0 or detailPanelHeight + 2 >= height: # no detail panel detailPanelHeight = 0 scrollLoc = self.scroller.getScrollLoc(self.confContents, height - 1) cursorSelection = self.getSelection() else: # Shrink detail panel if there isn't sufficient room for the whole # thing. The extra line is for the bottom border. detailPanelHeight = min(height - 1, detailPanelHeight + 1) scrollLoc = self.scroller.getScrollLoc(self.confContents, height - 1 - detailPanelHeight) cursorSelection = self.getSelection() self._drawSelectionPanel(cursorSelection, width, detailPanelHeight, titleLabel) # draws left-hand scroll bar if content's longer than the height scrollOffset = 0 if len(self.confContents) > height - detailPanelHeight - 1: scrollOffset = 3 self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelHeight - 1, len(self.confContents), 1 + detailPanelHeight) optionWidth = self._config["features.config.state.colWidth.option"] valueWidth = self._config["features.config.state.colWidth.value"] descriptionWidth = max(0, width - scrollOffset - optionWidth - valueWidth - 2) for lineNum in range(scrollLoc, len(self.confContents)): entry = self.confContents[lineNum] drawLine = lineNum + detailPanelHeight + 1 - scrollLoc optionLabel = uiTools.cropStr(entry.get(FIELD_OPTION), optionWidth) valueLabel = uiTools.cropStr(entry.get(FIELD_VALUE), valueWidth) # ends description at the first newline descriptionLabel = uiTools.cropStr(entry.get(FIELD_DESCRIPTION).split("\n")[0], descriptionWidth, None) lineFormat = curses.A_NORMAL if entry.get(FIELD_IS_DEFAULT) else curses.A_BOLD if entry.get(FIELD_CATEGORY): lineFormat |= uiTools.getColor(CATEGORY_COLOR[entry.get(FIELD_CATEGORY)]) if entry == cursorSelection: lineFormat |= curses.A_STANDOUT lineTextLayout = "%%-%is %%-%is %%-%is" % (optionWidth, valueWidth, descriptionWidth) lineText = lineTextLayout % (optionLabel, valueLabel, descriptionLabel) self.addstr(drawLine, scrollOffset, lineText, lineFormat) if drawLine >= height: break self.valsLock.release()
def _getDetails(self, width): """ Provides details on the connection, correlated against available consensus data. Arguments: width - available space to display in """ detailFormat = curses.A_BOLD | uiTools.getColor(CATEGORY_COLOR[self.getType()]) return [(line, detailFormat) for line in self._getDetailContent(width)]
def _getDetails(self, width): """ Provides details on the connection, correlated against available consensus data. Arguments: width - available space to display in """ detailFormat = curses.A_BOLD | uiTools.getColor( CATEGORY_COLOR[self.getType()]) return [(line, detailFormat) for line in self._getDetailContent(width)]
def draw(popup, properties): popup.clear() popup.win.box() xOffset = 2 if properties.text: if properties.fingerprint: popup.addstr(0, 0, "Consensus Descriptor (%s):" % properties.fingerprint, curses.A_STANDOUT) else: popup.addstr(0, 0, "Consensus Descriptor:", curses.A_STANDOUT) isEncryption = False # true if line is part of an encryption block # checks if first line is in an encryption block for i in range(0, properties.scroll): lineText = properties.text[i].strip() if lineText in SIG_START_KEYS: isEncryption = True elif lineText in SIG_END_KEYS: isEncryption = False pageHeight = popup.maxY - 2 numFieldWidth = int(math.log10(len(properties.text))) + 1 lineNum = 1 for i in range(properties.scroll, min(len(properties.text), properties.scroll + pageHeight)): lineText = properties.text[i].strip() numOffset = 0 # offset for line numbering if properties.showLineNum: popup.addstr(lineNum, xOffset, ("%%%ii" % numFieldWidth) % (i + 1), curses.A_BOLD | uiTools.getColor(LINE_NUM_COLOR)) numOffset = numFieldWidth + 1 if lineText: keyword = lineText.split()[0] # first word of line remainder = lineText[len(keyword):] keywordFormat = curses.A_BOLD | uiTools.getColor(properties.entryColor) remainderFormat = uiTools.getColor(properties.entryColor) if lineText.startswith(HEADER_PREFIX[0]) or lineText.startswith(HEADER_PREFIX[1]): keyword, remainder = lineText, "" keywordFormat = curses.A_BOLD | uiTools.getColor(HEADER_COLOR) if lineText == UNRESOLVED_MSG or lineText == ERROR_MSG: keyword, remainder = lineText, "" if lineText in SIG_START_KEYS: keyword, remainder = lineText, "" isEncryption = True keywordFormat = curses.A_BOLD | uiTools.getColor(SIG_COLOR) elif lineText in SIG_END_KEYS: keyword, remainder = lineText, "" isEncryption = False keywordFormat = curses.A_BOLD | uiTools.getColor(SIG_COLOR) elif isEncryption: keyword, remainder = lineText, "" keywordFormat = uiTools.getColor(SIG_COLOR) lineNum, xLoc = controller.addstr_wrap(popup, lineNum, 0, keyword, keywordFormat, xOffset + numOffset, popup.maxX - 1, popup.maxY - 1) lineNum, xLoc = controller.addstr_wrap(popup, lineNum, xLoc, remainder, remainderFormat, xOffset + numOffset, popup.maxX - 1, popup.maxY - 1) lineNum += 1 if lineNum > pageHeight: break popup.refresh()
def showMenu(): popup, _, _ = cli.popups.init(1, belowStatic=False) if not popup: return control = cli.controller.getController() try: # generates the menu and uses the initial selection of the first item in # the file menu menu = cli.menu.actions.makeMenu() cursor = MenuCursor(menu.getChildren()[0].getChildren()[0]) while not cursor.isDone(): # sets the background color popup.win.clear() popup.win.bkgd(' ', curses.A_STANDOUT | uiTools.getColor("red")) selectionHierarchy = cursor.getSelection().getHierarchy() # provide a message saying how to close the menu control.setMsg("Press m or esc to close the menu.", curses.A_BOLD, True) # renders the menu bar, noting where the open submenu is positioned drawLeft, selectionLeft = 0, 0 for topLevelItem in menu.getChildren(): drawFormat = curses.A_BOLD if topLevelItem == selectionHierarchy[1]: drawFormat |= curses.A_UNDERLINE selectionLeft = drawLeft drawLabel = " %s " % topLevelItem.getLabel()[1] popup.addstr(0, drawLeft, drawLabel, drawFormat) popup.addch(0, drawLeft + len(drawLabel), curses.ACS_VLINE) drawLeft += len(drawLabel) + 1 # recursively shows opened submenus _drawSubmenu(cursor, 1, 1, selectionLeft) popup.win.refresh() curses.cbreak() key = control.getScreen().getch() cursor.handleKey(key) # redraws the rest of the interface if we're rendering on it again if not cursor.isDone(): control.redraw() finally: control.setMsg() cli.popups.finalize()
def showMenu(): popup, _, _ = cli.popups.init(1, belowStatic = False) if not popup: return control = cli.controller.getController() try: # generates the menu and uses the initial selection of the first item in # the file menu menu = cli.menu.actions.makeMenu() cursor = MenuCursor(menu.getChildren()[0].getChildren()[0]) while not cursor.isDone(): # sets the background color popup.win.clear() popup.win.bkgd(' ', curses.A_STANDOUT | uiTools.getColor("red")) selectionHierarchy = cursor.getSelection().getHierarchy() # provide a message saying how to close the menu control.setMsg("Press m or esc to close the menu.", curses.A_BOLD, True) # renders the menu bar, noting where the open submenu is positioned drawLeft, selectionLeft = 0, 0 for topLevelItem in menu.getChildren(): drawFormat = curses.A_BOLD if topLevelItem == selectionHierarchy[1]: drawFormat |= curses.A_UNDERLINE selectionLeft = drawLeft drawLabel = " %s " % topLevelItem.getLabel()[1] popup.addstr(0, drawLeft, drawLabel, drawFormat) popup.addch(0, drawLeft + len(drawLabel), curses.ACS_VLINE) drawLeft += len(drawLabel) + 1 # recursively shows opened submenus _drawSubmenu(cursor, 1, 1, selectionLeft) popup.win.refresh() curses.cbreak() key = control.getScreen().getch() cursor.handleKey(key) # redraws the rest of the interface if we're rendering on it again if not cursor.isDone(): control.redraw() finally: control.setMsg() cli.popups.finalize()
def _getListingEntry(self, width, currentTime, listingType): lineFormat = uiTools.getColor(connEntry.CATEGORY_COLOR[self.getType()]) # The required widths are the sum of the following: # initial space (1 character) # bracketing (3 characters) # placementLabel (14 characters) # gap between etc and placement label (5 characters) baselineSpace = 14 + 5 dst, etc = "", "" if listingType == entries.ListingType.IP_ADDRESS: # TODO: include hostname when that's available # dst width is derived as: # src (21) + dst (26) + divider (7) + right gap (2) - bracket (3) = 53 char dst = "%-53s" % self.getDestinationLabel(53, includeLocale=True) # fills the nickname into the empty space here dst = "%s%-25s " % ( dst[:25], uiTools.cropStr(self.foreign.getNickname(), 25, 0)) etc = self.getEtcContent(width - baselineSpace - len(dst), listingType) elif listingType == entries.ListingType.HOSTNAME: # min space for the hostname is 40 characters etc = self.getEtcContent(width - baselineSpace - 40, listingType) dstLayout = "%%-%is" % (width - baselineSpace - len(etc)) dst = dstLayout % self.foreign.getHostname( self.foreign.getIpAddr()) elif listingType == entries.ListingType.FINGERPRINT: # dst width is derived as: # src (9) + dst (40) + divider (7) + right gap (2) - bracket (3) = 55 char dst = "%-55s" % self.foreign.getFingerprint() etc = self.getEtcContent(width - baselineSpace - len(dst), listingType) else: # min space for the nickname is 56 characters etc = self.getEtcContent(width - baselineSpace - 56, listingType) dstLayout = "%%-%is" % (width - baselineSpace - len(etc)) dst = dstLayout % self.foreign.getNickname() return ((dst + etc, lineFormat), (" " * (width - baselineSpace - len(dst) - len(etc) + 5), lineFormat), ("%-14s" % self.placementLabel, lineFormat))
def _getListingEntry(self, width, currentTime, listingType): lineFormat = uiTools.getColor(connEntry.CATEGORY_COLOR[self.getType()]) # The required widths are the sum of the following: # initial space (1 character) # bracketing (3 characters) # placementLabel (14 characters) # gap between etc and placement label (5 characters) baselineSpace = 14 + 5 dst, etc = "", "" if listingType == entries.ListingType.IP_ADDRESS: # TODO: include hostname when that's available # dst width is derived as: # src (21) + dst (26) + divider (7) + right gap (2) - bracket (3) = 53 char dst = "%-53s" % self.getDestinationLabel(53, includeLocale = True) # fills the nickname into the empty space here dst = "%s%-25s " % (dst[:25], uiTools.cropStr(self.foreign.getNickname(), 25, 0)) etc = self.getEtcContent(width - baselineSpace - len(dst), listingType) elif listingType == entries.ListingType.HOSTNAME: # min space for the hostname is 40 characters etc = self.getEtcContent(width - baselineSpace - 40, listingType) dstLayout = "%%-%is" % (width - baselineSpace - len(etc)) dst = dstLayout % self.foreign.getHostname(self.foreign.getIpAddr()) elif listingType == entries.ListingType.FINGERPRINT: # dst width is derived as: # src (9) + dst (40) + divider (7) + right gap (2) - bracket (3) = 55 char dst = "%-55s" % self.foreign.getFingerprint() etc = self.getEtcContent(width - baselineSpace - len(dst), listingType) else: # min space for the nickname is 56 characters etc = self.getEtcContent(width - baselineSpace - 56, listingType) dstLayout = "%%-%is" % (width - baselineSpace - len(etc)) dst = dstLayout % self.foreign.getNickname() return ((dst + etc, lineFormat), (" " * (width - baselineSpace - len(dst) - len(etc) + 5), lineFormat), ("%-14s" % self.placementLabel, lineFormat))
def _getListingEntry(self, width, currentTime, listingType): entryType = self.getType() # Lines are split into the following components in reverse: # init gap - " " # content - "<src> --> <dst> <etc> " # time - "<uptime>" # preType - " (" # category - "<type>" # postType - ") " lineFormat = uiTools.getColor(CATEGORY_COLOR[entryType]) timeWidth = 6 if CONFIG["features.connection.markInitialConnections"] else 5 drawEntry = [(" ", lineFormat), (self._getListingContent(width - (12 + timeWidth) - 1, listingType), lineFormat), (" " * timeWidth, lineFormat), (" (", lineFormat), (entryType.upper(), lineFormat | curses.A_BOLD), (")" + " " * (9 - len(entryType)), lineFormat)] return drawEntry
def _getListingEntry(self, width, currentTime, listingType): entryType = self.getType() # Lines are split into the following components in reverse: # init gap - " " # content - "<src> --> <dst> <etc> " # time - "<uptime>" # preType - " (" # category - "<type>" # postType - ") " lineFormat = uiTools.getColor(CATEGORY_COLOR[entryType]) timeWidth = 6 if CONFIG[ "features.connection.markInitialConnections"] else 5 drawEntry = [(" ", lineFormat), (self._getListingContent(width - (12 + timeWidth) - 1, listingType), lineFormat), (" " * timeWidth, lineFormat), (" (", lineFormat), (entryType.upper(), lineFormat | curses.A_BOLD), (")" + " " * (9 - len(entryType)), lineFormat)] return drawEntry
def getFormat(formatAttr): """ Provides the curses drawing attributes for torInterpretor formats. Arguments: formatAttr - list of formatting attributes """ # initializes formats if they haven't yet been loaded if not FORMATS: for colorEnum in torInterpretor.Color.values(): FORMATS[colorEnum] = uiTools.getColor(colorEnum.lower()) FORMATS[torInterpretor.Attr.BOLD] = curses.A_BOLD FORMATS[torInterpretor.Attr.UNDERLINE] = curses.A_UNDERLINE FORMATS[torInterpretor.Attr.HILIGHT] = curses.A_STANDOUT cursesFormatting = curses.A_NORMAL for attr in formatAttr: cursesFormatting |= FORMATS.get(attr, curses.A_NORMAL) return cursesFormatting
def showCountDialog(countType, counts): """ Provides a dialog with bar graphs and percentages for the given set of counts. Pressing any key closes the dialog. Arguments: countType - type of counts being presented counts - mapping of labels to counts """ isNoStats = not counts noStatsMsg = "Usage stats aren't available yet, press any key..." if isNoStats: popup, width, height = cli.popups.init(3, len(noStatsMsg) + 4) else: popup, width, height = cli.popups.init(4 + max(1, len(counts)), 80) if not popup: return try: control = cli.controller.getController() popup.win.box() # dialog title if countType == CountType.CLIENT_LOCALE: title = "Client Locales" elif countType == CountType.EXIT_PORT: title = "Exiting Port Usage" else: title = "" log.log(log.WARN, "Unrecognized count type: %s" % countType) popup.addstr(0, 0, title, curses.A_STANDOUT) if isNoStats: popup.addstr(1, 2, noStatsMsg, curses.A_BOLD | uiTools.getColor("cyan")) else: sortedCounts = sorted(counts.iteritems(), key=operator.itemgetter(1)) sortedCounts.reverse() # constructs string formatting for the max key and value display width keyWidth, valWidth, valueTotal = 3, 1, 0 for k, v in sortedCounts: keyWidth = max(keyWidth, len(k)) valWidth = max(valWidth, len(str(v))) valueTotal += v # extra space since we're adding usage informaion if countType == CountType.EXIT_PORT: keyWidth += EXIT_USAGE_WIDTH labelFormat = "%%-%is %%%ii (%%%%%%-2i)" % (keyWidth, valWidth) for i in range(height - 4): k, v = sortedCounts[i] # includes a port usage column if countType == CountType.EXIT_PORT: usage = connections.getPortUsage(k) if usage: keyFormat = "%%-%is %%s" % (keyWidth - EXIT_USAGE_WIDTH) k = keyFormat % (k, usage[:EXIT_USAGE_WIDTH - 3]) label = labelFormat % (k, v, v * 100 / valueTotal) popup.addstr(i + 1, 2, label, curses.A_BOLD | uiTools.getColor("green")) # All labels have the same size since they're based on the max widths. # If this changes then this'll need to be the max label width. labelWidth = len(label) # draws simple bar graph for percentages fillWidth = v * (width - 4 - labelWidth) / valueTotal for j in range(fillWidth): popup.addstr(i + 1, 3 + labelWidth + j, " ", curses.A_STANDOUT | uiTools.getColor("red")) popup.addstr(height - 2, 2, "Press any key...") popup.win.refresh() curses.cbreak() control.getScreen().getch() finally: cli.popups.finalize()
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 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 draw(self, subwindow, width, height): """ Redraws message log. Entries stretch to use available space and may contain up to two lines. Starts with newest entries. """ self.valsLock.acquire() self._lastLoggedEvents, self._lastUpdate = list( self.msgLog), time.time() # draws the top label self.addstr(0, 0, self._getTitle(width), curses.A_STANDOUT) # restricts scroll location to valid bounds self.scroll = max( 0, min(self.scroll, self.lastContentHeight - height + 1)) # draws left-hand scroll bar if content's longer than the height msgIndent, dividerIndent = 0, 0 # offsets for scroll bar isScrollBarVisible = self.lastContentHeight > height - 1 if isScrollBarVisible: msgIndent, dividerIndent = 3, 2 self.addScrollBar(self.scroll, self.scroll + height - 1, self.lastContentHeight, 1) # draws log entries lineCount = 1 - self.scroll seenFirstDateDivider = False dividerAttr, duplicateAttr = curses.A_BOLD | uiTools.getColor( "yellow"), curses.A_BOLD | uiTools.getColor("green") isDatesShown = self.regexFilter == None and self._config[ "features.log.showDateDividers"] eventLog = getDaybreaks( self.msgLog, self._isPaused) if isDatesShown else list(self.msgLog) if not self.showDuplicates: deduplicatedLog = getDuplicates(eventLog) if deduplicatedLog == None: msg = "Deduplication took too long. Its current implementation has difficulty handling large logs so disabling it to keep the interface responsive." log.log(log.WARN, msg) self.showDuplicates = True deduplicatedLog = [(entry, 0) for entry in eventLog] else: deduplicatedLog = [(entry, 0) for entry in eventLog] # determines if we have the minimum width to show date dividers showDaybreaks = width - dividerIndent >= 3 while deduplicatedLog: entry, duplicateCount = deduplicatedLog.pop(0) if self.regexFilter and not self.regexFilter.search( entry.getDisplayMessage()): continue # filter doesn't match log message - skip # checks if we should be showing a divider with the date if entry.type == DAYBREAK_EVENT: # bottom of the divider if seenFirstDateDivider: if lineCount >= 1 and lineCount < height and showDaybreaks: self.win.vline(lineCount, dividerIndent, curses.ACS_LLCORNER | dividerAttr, 1) self.win.hline(lineCount, dividerIndent + 1, curses.ACS_HLINE | dividerAttr, width - dividerIndent - 1) self.win.vline(lineCount, width, curses.ACS_LRCORNER | dividerAttr, 1) lineCount += 1 # top of the divider if lineCount >= 1 and lineCount < height and showDaybreaks: timeLabel = time.strftime(" %B %d, %Y ", time.localtime(entry.timestamp)) self.win.vline(lineCount, dividerIndent, curses.ACS_ULCORNER | dividerAttr, 1) self.win.hline(lineCount, dividerIndent + 1, curses.ACS_HLINE | dividerAttr, 1) self.addstr(lineCount, dividerIndent + 2, timeLabel, curses.A_BOLD | dividerAttr) if dividerIndent + len(timeLabel) + 2 <= width: lineLength = width - dividerIndent - len(timeLabel) - 2 self.win.hline(lineCount, dividerIndent + len(timeLabel) + 2, curses.ACS_HLINE | dividerAttr, lineLength) self.win.vline( lineCount, dividerIndent + len(timeLabel) + 2 + lineLength, curses.ACS_URCORNER | dividerAttr, 1) seenFirstDateDivider = True lineCount += 1 else: # entry contents to be displayed, tuples of the form: # (msg, formatting, includeLinebreak) displayQueue = [] msgComp = entry.getDisplayMessage().split("\n") for i in range(len(msgComp)): font = curses.A_BOLD if "ERR" in entry.type else curses.A_NORMAL # emphasizes ERR messages displayQueue.append((msgComp[i].strip(), font | uiTools.getColor(entry.color), i != len(msgComp) - 1)) if duplicateCount: pluralLabel = "s" if duplicateCount > 1 else "" duplicateMsg = DUPLICATE_MSG % (duplicateCount, pluralLabel) displayQueue.append((duplicateMsg, duplicateAttr, False)) cursorLoc, lineOffset = msgIndent, 0 maxEntriesPerLine = self._config[ "features.log.maxLinesPerEntry"] while displayQueue: msg, format, includeBreak = displayQueue.pop(0) drawLine = lineCount + lineOffset if lineOffset == maxEntriesPerLine: break maxMsgSize = width - cursorLoc if len(msg) > maxMsgSize: # message is too long - break it up if lineOffset == maxEntriesPerLine - 1: msg = uiTools.cropStr(msg, maxMsgSize) else: msg, remainder = uiTools.cropStr( msg, maxMsgSize, 4, 4, uiTools.END_WITH_HYPHEN, True) displayQueue.insert( 0, (remainder.strip(), format, includeBreak)) includeBreak = True if drawLine < height and drawLine >= 1: if seenFirstDateDivider and width - dividerIndent >= 3 and showDaybreaks: self.win.vline(drawLine, dividerIndent, curses.ACS_VLINE | dividerAttr, 1) self.win.vline(drawLine, width, curses.ACS_VLINE | dividerAttr, 1) self.addstr(drawLine, cursorLoc, msg, format) cursorLoc += len(msg) if includeBreak or not displayQueue: lineOffset += 1 cursorLoc = msgIndent + ENTRY_INDENT lineCount += lineOffset # if this is the last line and there's room, then draw the bottom of the divider if not deduplicatedLog and seenFirstDateDivider: if lineCount < height and showDaybreaks: # when resizing with a small width the following entries can be # problematc (though I'm not sure why) try: self.win.vline(lineCount, dividerIndent, curses.ACS_LLCORNER | dividerAttr, 1) self.win.hline(lineCount, dividerIndent + 1, curses.ACS_HLINE | dividerAttr, width - dividerIndent - 1) self.win.vline(lineCount, width, curses.ACS_LRCORNER | dividerAttr, 1) except: pass lineCount += 1 # redraw the display if... # - lastContentHeight was off by too much # - we're off the bottom of the page newContentHeight = lineCount + self.scroll - 1 contentHeightDelta = abs(self.lastContentHeight - newContentHeight) forceRedraw, forceRedrawReason = True, "" if contentHeightDelta >= CONTENT_HEIGHT_REDRAW_THRESHOLD: forceRedrawReason = "estimate was off by %i" % contentHeightDelta elif newContentHeight > height and self.scroll + height - 1 > newContentHeight: forceRedrawReason = "scrolled off the bottom of the page" elif not isScrollBarVisible and newContentHeight > height - 1: forceRedrawReason = "scroll bar wasn't previously visible" elif isScrollBarVisible and newContentHeight <= height - 1: forceRedrawReason = "scroll bar shouldn't be visible" else: forceRedraw = False self.lastContentHeight = newContentHeight if forceRedraw: forceRedrawReason = "redrawing the log panel with the corrected content height (%s)" % forceRedrawReason log.log(self._config["log.logPanel.forceDoubleRedraw"], forceRedrawReason) self.redraw(True) self.valsLock.release()
def draw(self, subwindow, width, height): """ Redraws graph panel """ if self.currentDisplay: param = self.stats[self.currentDisplay] graphCol = min((width - 10) / 2, param.maxCol) primaryColor = uiTools.getColor(param.getColor(True)) secondaryColor = uiTools.getColor(param.getColor(False)) if self.showLabel: self.addstr(0, 0, param.getTitle(width), curses.A_STANDOUT) # top labels left, right = param.getHeaderLabel(width / 2, True), param.getHeaderLabel( width / 2, False) if left: self.addstr(1, 0, left, curses.A_BOLD | primaryColor) if right: self.addstr(1, graphCol + 5, right, curses.A_BOLD | secondaryColor) # determines max/min value on the graph if self.bounds == BOUNDS_GLOBAL_MAX: primaryMaxBound = int(param.maxPrimary[self.updateInterval]) secondaryMaxBound = int( param.maxSecondary[self.updateInterval]) else: # both BOUNDS_LOCAL_MAX and BOUNDS_TIGHT use local maxima if graphCol < 2: # nothing being displayed primaryMaxBound, secondaryMaxBound = 0, 0 else: primaryMaxBound = int( max(param.primaryCounts[self.updateInterval] [1:graphCol + 1])) secondaryMaxBound = int( max(param.secondaryCounts[self.updateInterval] [1:graphCol + 1])) primaryMinBound = secondaryMinBound = 0 if self.bounds == BOUNDS_TIGHT: primaryMinBound = int( min(param.primaryCounts[self.updateInterval][1:graphCol + 1])) secondaryMinBound = int( min(param.secondaryCounts[self.updateInterval][1:graphCol + 1])) # if the max = min (ie, all values are the same) then use zero lower # bound so a graph is still displayed if primaryMinBound == primaryMaxBound: primaryMinBound = 0 if secondaryMinBound == secondaryMaxBound: secondaryMinBound = 0 # displays upper and lower bounds self.addstr(2, 0, "%4i" % primaryMaxBound, primaryColor) self.addstr(self.graphHeight + 1, 0, "%4i" % primaryMinBound, primaryColor) self.addstr(2, graphCol + 5, "%4i" % secondaryMaxBound, secondaryColor) self.addstr(self.graphHeight + 1, graphCol + 5, "%4i" % secondaryMinBound, secondaryColor) # displays intermediate bounds on every other row if CONFIG["features.graph.showIntermediateBounds"]: ticks = (self.graphHeight - 3) / 2 for i in range(ticks): row = self.graphHeight - (2 * i) - 3 if self.graphHeight % 2 == 0 and i >= (ticks / 2): row -= 1 if primaryMinBound != primaryMaxBound: primaryVal = (primaryMaxBound - primaryMinBound) / ( self.graphHeight - 1) * (self.graphHeight - row - 1) if not primaryVal in (primaryMinBound, primaryMaxBound): self.addstr(row + 2, 0, "%4i" % primaryVal, primaryColor) if secondaryMinBound != secondaryMaxBound: secondaryVal = (secondaryMaxBound - secondaryMinBound ) / (self.graphHeight - 1) * (self.graphHeight - row - 1) if not secondaryVal in (secondaryMinBound, secondaryMaxBound): self.addstr(row + 2, graphCol + 5, "%4i" % secondaryVal, secondaryColor) # creates bar graph (both primary and secondary) for col in range(graphCol): colCount = int(param.primaryCounts[self.updateInterval][ col + 1]) - primaryMinBound colHeight = min( self.graphHeight, self.graphHeight * colCount / (max(1, primaryMaxBound) - primaryMinBound)) for row in range(colHeight): self.addstr(self.graphHeight + 1 - row, col + 5, " ", curses.A_STANDOUT | primaryColor) colCount = int(param.secondaryCounts[self.updateInterval][ col + 1]) - secondaryMinBound colHeight = min( self.graphHeight, self.graphHeight * colCount / (max(1, secondaryMaxBound) - secondaryMinBound)) for row in range(colHeight): self.addstr(self.graphHeight + 1 - row, col + graphCol + 10, " ", curses.A_STANDOUT | secondaryColor) # bottom labeling of x-axis intervalSec = 1 # seconds per labeling for i in range(len(UPDATE_INTERVALS)): if i == self.updateInterval: intervalSec = UPDATE_INTERVALS[i][1] intervalSpacing = 10 if graphCol >= WIDE_LABELING_GRAPH_COL else 5 unitsLabel, decimalPrecision = None, 0 for i in range((graphCol - 4) / intervalSpacing): loc = (i + 1) * intervalSpacing timeLabel = uiTools.getTimeLabel(loc * intervalSec, decimalPrecision) if not unitsLabel: unitsLabel = timeLabel[-1] elif unitsLabel != timeLabel[-1]: # upped scale so also up precision of future measurements unitsLabel = timeLabel[-1] decimalPrecision += 1 else: # if constrained on space then strips labeling since already provided timeLabel = timeLabel[:-1] self.addstr(self.graphHeight + 2, 4 + loc, timeLabel, primaryColor) self.addstr(self.graphHeight + 2, graphCol + 10 + loc, timeLabel, secondaryColor) param.draw(self, width, height) # allows current stats to modify the display
def _drawSelectionPanel(self, cursorSelection, width, detailPanelHeight, titleLabel): """ Renders a panel for the selected configuration option. """ # border (top) if width >= len(titleLabel): self.win.hline(0, len(titleLabel), curses.ACS_HLINE, width - len(titleLabel)) self.win.addch(0, width, curses.ACS_URCORNER) # border (sides) self.win.vline(1, 0, curses.ACS_VLINE, detailPanelHeight - 1) self.win.vline(1, width, curses.ACS_VLINE, detailPanelHeight - 1) # border (bottom) self.win.addch(detailPanelHeight, 0, curses.ACS_LLCORNER) if width >= 2: self.win.addch(detailPanelHeight, 1, curses.ACS_TTEE) if width >= 3: self.win.hline(detailPanelHeight, 2, curses.ACS_HLINE, width - 2) self.win.addch(detailPanelHeight, width, curses.ACS_LRCORNER) selectionFormat = curses.A_BOLD | uiTools.getColor(CATEGORY_COLOR[cursorSelection.get(FIELD_CATEGORY)]) # first entry: # <option> (<category> Option) optionLabel =" (%s Option)" % torConfig.OPTION_CATEGORY_STR[cursorSelection.get(FIELD_CATEGORY)] self.addstr(1, 2, cursorSelection.get(FIELD_OPTION) + optionLabel, selectionFormat) # second entry: # Value: <value> ([default|custom], <type>, usage: <argument usage>) if detailPanelHeight >= 3: valueAttr = [] valueAttr.append("default" if cursorSelection.get(FIELD_IS_DEFAULT) else "custom") valueAttr.append(cursorSelection.get(FIELD_TYPE)) valueAttr.append("usage: %s" % (cursorSelection.get(FIELD_ARG_USAGE))) valueAttrLabel = ", ".join(valueAttr) valueLabelWidth = width - 12 - len(valueAttrLabel) valueLabel = uiTools.cropStr(cursorSelection.get(FIELD_VALUE), valueLabelWidth) self.addstr(2, 2, "Value: %s (%s)" % (valueLabel, valueAttrLabel), selectionFormat) # remainder is filled with the man page description descriptionHeight = max(0, detailPanelHeight - 3) descriptionContent = "Description: " + cursorSelection.get(FIELD_DESCRIPTION) for i in range(descriptionHeight): # checks if we're done writing the description if not descriptionContent: break # there's a leading indent after the first line if i > 0: descriptionContent = " " + descriptionContent # we only want to work with content up until the next newline if "\n" in descriptionContent: lineContent, descriptionContent = descriptionContent.split("\n", 1) else: lineContent, descriptionContent = descriptionContent, "" if i != descriptionHeight - 1: # there's more lines to display msg, remainder = uiTools.cropStr(lineContent, width - 2, 4, 4, uiTools.END_WITH_HYPHEN, True) descriptionContent = remainder.strip() + descriptionContent else: # this is the last line, end it with an ellipse msg = uiTools.cropStr(lineContent, width - 2, 4, 4) self.addstr(3 + i, 2, msg, selectionFormat)
def draw(popup, fingerprint, displayText, displayColor, scroll, showLineNumber): popup.win.erase() popup.win.box() xOffset = 2 if fingerprint: title = "Consensus Descriptor (%s):" % fingerprint else: title = "Consensus Descriptor:" popup.addstr(0, 0, title, curses.A_STANDOUT) lineNumWidth = int(math.log10(len(displayText))) + 1 isEncryptionBlock = False # flag indicating if we're currently displaying a key # checks if first line is in an encryption block for i in range(0, scroll): lineText = displayText[i].strip() if lineText in SIG_START_KEYS: isEncryptionBlock = True elif lineText in SIG_END_KEYS: isEncryptionBlock = False drawLine, pageHeight = 1, popup.maxY - 2 for i in range(scroll, scroll + pageHeight): lineText = displayText[i].strip() xOffset = 2 if showLineNumber: lineNumLabel = ("%%%ii" % lineNumWidth) % (i + 1) lineNumFormat = curses.A_BOLD | uiTools.getColor(LINE_NUM_COLOR) popup.addstr(drawLine, xOffset, lineNumLabel, lineNumFormat) xOffset += lineNumWidth + 1 # Most consensus and descriptor lines are keyword/value pairs. Both are # shown with the same color, but the keyword is bolded. keyword, value = lineText, "" drawFormat = uiTools.getColor(displayColor) if lineText.startswith(HEADER_PREFIX[0]) or lineText.startswith( HEADER_PREFIX[1]): keyword, value = lineText, "" drawFormat = uiTools.getColor(HEADER_COLOR) elif lineText == UNRESOLVED_MSG or lineText == ERROR_MSG: keyword, value = lineText, "" elif lineText in SIG_START_KEYS: keyword, value = lineText, "" isEncryptionBlock = True drawFormat = uiTools.getColor(SIG_COLOR) elif lineText in SIG_END_KEYS: keyword, value = lineText, "" isEncryptionBlock = False drawFormat = uiTools.getColor(SIG_COLOR) elif isEncryptionBlock: keyword, value = "", lineText drawFormat = uiTools.getColor(SIG_COLOR) elif " " in lineText: divIndex = lineText.find(" ") keyword, value = lineText[:divIndex], lineText[divIndex:] displayQueue = [(keyword, drawFormat | curses.A_BOLD), (value, drawFormat)] cursorLoc = xOffset while displayQueue: msg, format = displayQueue.pop(0) if not msg: continue maxMsgSize = popup.maxX - 1 - cursorLoc if len(msg) >= maxMsgSize: # needs to split up the line msg, remainder = uiTools.cropStr(msg, maxMsgSize, None, endType=None, getRemainder=True) if xOffset == cursorLoc and msg == "": # first word is longer than the line msg = uiTools.cropStr(remainder, maxMsgSize) if " " in remainder: remainder = remainder.split(" ", 1)[1] else: remainder = "" popup.addstr(drawLine, cursorLoc, msg, format) cursorLoc = xOffset if remainder: displayQueue.insert(0, (remainder.strip(), format)) drawLine += 1 else: popup.addstr(drawLine, cursorLoc, msg, format) cursorLoc += len(msg) if drawLine > pageHeight: break drawLine += 1 if drawLine > pageHeight: break popup.win.refresh()
def draw(self, width, height): self.valsLock.acquire() # If true, we assume that the cached value in self._lastContentHeight is # still accurate, and stop drawing when there's nothing more to display. # Otherwise the self._lastContentHeight is suspect, and we'll process all # the content to check if it's right (and redraw again with the corrected # height if not). trustLastContentHeight = self._lastContentHeightArgs == (width, height) # restricts scroll location to valid bounds self.scroll = max( 0, min(self.scroll, self._lastContentHeight - height + 1)) renderedContents, corrections, confLocation = None, {}, None if self.configType == Config.TORRC: loadedTorrc = torConfig.getTorrc() loadedTorrc.getLock().acquire() confLocation = loadedTorrc.getConfigLocation() if not loadedTorrc.isLoaded(): renderedContents = ["### Unable to load the torrc ###"] else: renderedContents = loadedTorrc.getDisplayContents( self.stripComments) # constructs a mapping of line numbers to the issue on it corrections = dict( (lineNum, (issue, msg)) for lineNum, issue, msg in loadedTorrc.getCorrections()) loadedTorrc.getLock().release() else: loadedArmrc = conf.getConfig("arm") confLocation = loadedArmrc.path renderedContents = list(loadedArmrc.rawContents) # offset to make room for the line numbers lineNumOffset = 0 if self.showLineNum: if len(renderedContents) == 0: lineNumOffset = 2 else: lineNumOffset = int(math.log10(len(renderedContents))) + 2 # draws left-hand scroll bar if content's longer than the height scrollOffset = 0 if self._config[ "features.config.file.showScrollbars"] and self._lastContentHeight > height - 1: scrollOffset = 3 self.addScrollBar(self.scroll, self.scroll + height - 1, self._lastContentHeight, 1) displayLine = -self.scroll + 1 # line we're drawing on # draws the top label if self.isTitleVisible(): sourceLabel = "Tor" if self.configType == Config.TORRC else "Arm" locationLabel = " (%s)" % confLocation if confLocation else "" self.addstr( 0, 0, "%s Configuration File%s:" % (sourceLabel, locationLabel), curses.A_STANDOUT) isMultiline = False # true if we're in the middle of a multiline torrc entry for lineNumber in range(0, len(renderedContents)): lineText = renderedContents[lineNumber] lineText = lineText.rstrip() # remove ending whitespace # blank lines are hidden when stripping comments if self.stripComments and not lineText: continue # splits the line into its component (msg, format) tuples lineComp = { "option": ["", curses.A_BOLD | uiTools.getColor("green")], "argument": ["", curses.A_BOLD | uiTools.getColor("cyan")], "correction": ["", curses.A_BOLD | uiTools.getColor("cyan")], "comment": ["", uiTools.getColor("white")] } # parses the comment commentIndex = lineText.find("#") if commentIndex != -1: lineComp["comment"][0] = lineText[commentIndex:] lineText = lineText[:commentIndex] # splits the option and argument, preserving any whitespace around them strippedLine = lineText.strip() optionIndex = strippedLine.find(" ") if isMultiline: # part of a multiline entry started on a previous line so everything # is part of the argument lineComp["argument"][0] = lineText elif optionIndex == -1: # no argument provided lineComp["option"][0] = lineText else: optionText = strippedLine[:optionIndex] optionEnd = lineText.find(optionText) + len(optionText) lineComp["option"][0] = lineText[:optionEnd] lineComp["argument"][0] = lineText[optionEnd:] # flags following lines as belonging to this multiline entry if it ends # with a slash if strippedLine: isMultiline = strippedLine.endswith("\\") # gets the correction if lineNumber in corrections: lineIssue, lineIssueMsg = corrections[lineNumber] if lineIssue in (torConfig.ValidationError.DUPLICATE, torConfig.ValidationError.IS_DEFAULT): lineComp["option"][1] = curses.A_BOLD | uiTools.getColor( "blue") lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor( "blue") elif lineIssue == torConfig.ValidationError.MISMATCH: lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor( "red") lineComp["correction"][0] = " (%s)" % lineIssueMsg else: # For some types of configs the correction field is simply used to # provide extra data (for instance, the type for tor state fields). lineComp["correction"][0] = " (%s)" % lineIssueMsg lineComp["correction"][ 1] = curses.A_BOLD | uiTools.getColor("magenta") # draws the line number if self.showLineNum and displayLine < height and displayLine >= 1: lineNumStr = ("%%%ii" % (lineNumOffset - 1)) % (lineNumber + 1) self.addstr(displayLine, scrollOffset, lineNumStr, curses.A_BOLD | uiTools.getColor("yellow")) # draws the rest of the components with line wrap cursorLoc, lineOffset = lineNumOffset + scrollOffset, 0 maxLinesPerEntry = self._config[ "features.config.file.maxLinesPerEntry"] displayQueue = [ lineComp[entry] for entry in ("option", "argument", "correction", "comment") ] while displayQueue: msg, format = displayQueue.pop(0) maxMsgSize, includeBreak = width - cursorLoc, False if len(msg) >= maxMsgSize: # message is too long - break it up if lineOffset == maxLinesPerEntry - 1: msg = uiTools.cropStr(msg, maxMsgSize) else: includeBreak = True msg, remainder = uiTools.cropStr( msg, maxMsgSize, 4, 4, uiTools.Ending.HYPHEN, True) displayQueue.insert(0, (remainder.strip(), format)) drawLine = displayLine + lineOffset if msg and drawLine < height and drawLine >= 1: self.addstr(drawLine, cursorLoc, msg, format) # If we're done, and have added content to this line, then start # further content on the next line. cursorLoc += len(msg) includeBreak |= not displayQueue and cursorLoc != lineNumOffset + scrollOffset if includeBreak: lineOffset += 1 cursorLoc = lineNumOffset + scrollOffset displayLine += max(lineOffset, 1) if trustLastContentHeight and displayLine >= height: break if not trustLastContentHeight: self._lastContentHeightArgs = (width, height) newContentHeight = displayLine + self.scroll - 1 if self._lastContentHeight != newContentHeight: self._lastContentHeight = newContentHeight self.redraw(True) self.valsLock.release()
def draw(popup, properties): popup.clear() popup.win.box() # top label popup.addstr(0, 0, "Open File Descriptors:", curses.A_STANDOUT) if properties.errorMsg: popup.addstr(1, 2, properties.errorMsg, curses.A_BOLD | uiTools.getColor("red")) else: # text with file descriptor count and limit fdCount = len(properties.fdFile) + len(properties.fdConn) + len( properties.fdMisc) fdCountPer = 100 * fdCount / max(properties.fdLimit, 1) statsColor = "green" if fdCountPer >= 90: statsColor = "red" elif fdCountPer >= 50: statsColor = "yellow" countMsg = "%i / %i (%i%%)" % (fdCount, properties.fdLimit, fdCountPer) popup.addstr(1, 2, countMsg, curses.A_BOLD | uiTools.getColor(statsColor)) # provides a progress bar reflecting the stats barWidth = popup.maxX - len( countMsg) - 6 # space between "[ ]" in progress bar barProgress = barWidth * fdCountPer / 100 # filled cells if fdCount > 0: barProgress = max( 1, barProgress) # ensures one cell is filled unless really zero popup.addstr(1, len(countMsg) + 3, "[", curses.A_BOLD) popup.addstr(1, len(countMsg) + 4, " " * barProgress, curses.A_STANDOUT | uiTools.getColor(statsColor)) popup.addstr(1, len(countMsg) + 4 + barWidth, "]", curses.A_BOLD) popup.win.hline(2, 1, curses.ACS_HLINE, popup.maxX - 2) # scrollable file descriptor listing lineNum = 3 entryNum = properties.scroll while lineNum <= popup.maxY - 2: if entryNum < len(properties.fdFile): line = properties.fdFile[entryNum] color = "green" elif entryNum < len(properties.fdFile) + len(properties.fdMisc): line = properties.fdMisc[entryNum - len(properties.fdFile)] color = "cyan" else: line = properties.fdConn[entryNum - len(properties.fdFile) - len(properties.fdMisc)] color = "blue" popup.addstr(lineNum, 2, line, curses.A_BOLD | uiTools.getColor(color)) lineNum += 1 entryNum += 1 popup.refresh()
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 draw(popup, fingerprint, displayText, displayColor, scroll, showLineNumber): popup.win.erase() popup.win.box() xOffset = 2 if fingerprint: title = "Consensus Descriptor (%s):" % fingerprint else: title = "Consensus Descriptor:" popup.addstr(0, 0, title, curses.A_STANDOUT) lineNumWidth = int(math.log10(len(displayText))) + 1 isEncryptionBlock = False # flag indicating if we're currently displaying a key # checks if first line is in an encryption block for i in range(0, scroll): lineText = displayText[i].strip() if lineText in SIG_START_KEYS: isEncryptionBlock = True elif lineText in SIG_END_KEYS: isEncryptionBlock = False drawLine, pageHeight = 1, popup.maxY - 2 for i in range(scroll, scroll + pageHeight): lineText = displayText[i].strip() xOffset = 2 if showLineNumber: lineNumLabel = ("%%%ii" % lineNumWidth) % (i + 1) lineNumFormat = curses.A_BOLD | uiTools.getColor(LINE_NUM_COLOR) popup.addstr(drawLine, xOffset, lineNumLabel, lineNumFormat) xOffset += lineNumWidth + 1 # Most consensus and descriptor lines are keyword/value pairs. Both are # shown with the same color, but the keyword is bolded. keyword, value = lineText, "" drawFormat = uiTools.getColor(displayColor) if lineText.startswith(HEADER_PREFIX[0]) or lineText.startswith(HEADER_PREFIX[1]): keyword, value = lineText, "" drawFormat = uiTools.getColor(HEADER_COLOR) elif lineText == UNRESOLVED_MSG or lineText == ERROR_MSG: keyword, value = lineText, "" elif lineText in SIG_START_KEYS: keyword, value = lineText, "" isEncryptionBlock = True drawFormat = uiTools.getColor(SIG_COLOR) elif lineText in SIG_END_KEYS: keyword, value = lineText, "" isEncryptionBlock = False drawFormat = uiTools.getColor(SIG_COLOR) elif isEncryptionBlock: keyword, value = "", lineText drawFormat = uiTools.getColor(SIG_COLOR) elif " " in lineText: divIndex = lineText.find(" ") keyword, value = lineText[:divIndex], lineText[divIndex:] displayQueue = [(keyword, drawFormat | curses.A_BOLD), (value, drawFormat)] cursorLoc = xOffset while displayQueue: msg, format = displayQueue.pop(0) if not msg: continue maxMsgSize = popup.maxX - 1 - cursorLoc if len(msg) >= maxMsgSize: # needs to split up the line msg, remainder = uiTools.cropStr(msg, maxMsgSize, None, endType = None, getRemainder = True) if xOffset == cursorLoc and msg == "": # first word is longer than the line msg = uiTools.cropStr(remainder, maxMsgSize) if " " in remainder: remainder = remainder.split(" ", 1)[1] else: remainder = "" popup.addstr(drawLine, cursorLoc, msg, format) cursorLoc = xOffset if remainder: displayQueue.insert(0, (remainder.strip(), format)) drawLine += 1 else: popup.addstr(drawLine, cursorLoc, msg, format) cursorLoc += len(msg) if drawLine > pageHeight: break drawLine += 1 if drawLine > pageHeight: break popup.win.refresh()
def _drawSelectionPanel(self, cursorSelection, width, detailPanelHeight, titleLabel): """ Renders a panel for the selected configuration option. """ # border (top) if width >= len(titleLabel): self.win.hline(0, len(titleLabel), curses.ACS_HLINE, width - len(titleLabel)) self.win.addch(0, width, curses.ACS_URCORNER) # border (sides) self.win.vline(1, 0, curses.ACS_VLINE, detailPanelHeight - 1) self.win.vline(1, width, curses.ACS_VLINE, detailPanelHeight - 1) # border (bottom) self.win.addch(detailPanelHeight, 0, curses.ACS_LLCORNER) if width >= 2: self.win.addch(detailPanelHeight, 1, curses.ACS_TTEE) if width >= 3: self.win.hline(detailPanelHeight, 2, curses.ACS_HLINE, width - 2) self.win.addch(detailPanelHeight, width, curses.ACS_LRCORNER) selectionFormat = curses.A_BOLD | uiTools.getColor( CATEGORY_COLOR[cursorSelection.get(FIELD_CATEGORY)]) # first entry: # <option> (<category> Option) optionLabel = " (%s Option)" % torConfig.OPTION_CATEGORY_STR[ cursorSelection.get(FIELD_CATEGORY)] self.addstr(1, 2, cursorSelection.get(FIELD_OPTION) + optionLabel, selectionFormat) # second entry: # Value: <value> ([default|custom], <type>, usage: <argument usage>) if detailPanelHeight >= 3: valueAttr = [] valueAttr.append("default" if cursorSelection.get(FIELD_IS_DEFAULT ) else "custom") valueAttr.append(cursorSelection.get(FIELD_TYPE)) valueAttr.append("usage: %s" % (cursorSelection.get(FIELD_ARG_USAGE))) valueAttrLabel = ", ".join(valueAttr) valueLabelWidth = width - 12 - len(valueAttrLabel) valueLabel = uiTools.cropStr(cursorSelection.get(FIELD_VALUE), valueLabelWidth) self.addstr(2, 2, "Value: %s (%s)" % (valueLabel, valueAttrLabel), selectionFormat) # remainder is filled with the man page description descriptionHeight = max(0, detailPanelHeight - 3) descriptionContent = "Description: " + cursorSelection.get( FIELD_DESCRIPTION) for i in range(descriptionHeight): # checks if we're done writing the description if not descriptionContent: break # there's a leading indent after the first line if i > 0: descriptionContent = " " + descriptionContent # we only want to work with content up until the next newline if "\n" in descriptionContent: lineContent, descriptionContent = descriptionContent.split( "\n", 1) else: lineContent, descriptionContent = descriptionContent, "" if i != descriptionHeight - 1: # there's more lines to display msg, remainder = uiTools.cropStr(lineContent, width - 2, 4, 4, uiTools.END_WITH_HYPHEN, True) descriptionContent = remainder.strip() + descriptionContent else: # this is the last line, end it with an ellipse msg = uiTools.cropStr(lineContent, width - 2, 4, 4) self.addstr(3 + i, 2, msg, selectionFormat)
def draw(self, subwindow, width, height): self.valsLock.acquire() # draws the top label titleLabel = "%s Configuration:" % ("Tor" if self.configType == TOR_STATE else "Arm") self.addstr(0, 0, titleLabel, curses.A_STANDOUT) # panel with details for the current selection detailPanelHeight = self._config[ "features.config.selectionDetails.height"] if detailPanelHeight == 0 or detailPanelHeight + 2 >= height: # no detail panel detailPanelHeight = 0 scrollLoc = self.scroller.getScrollLoc(self.confContents, height - 1) cursorSelection = self.getSelection() else: # Shrink detail panel if there isn't sufficient room for the whole # thing. The extra line is for the bottom border. detailPanelHeight = min(height - 1, detailPanelHeight + 1) scrollLoc = self.scroller.getScrollLoc( self.confContents, height - 1 - detailPanelHeight) cursorSelection = self.getSelection() self._drawSelectionPanel(cursorSelection, width, detailPanelHeight, titleLabel) # draws left-hand scroll bar if content's longer than the height scrollOffset = 0 if len(self.confContents) > height - detailPanelHeight - 1: scrollOffset = 3 self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelHeight - 1, len(self.confContents), 1 + detailPanelHeight) optionWidth = self._config["features.config.state.colWidth.option"] valueWidth = self._config["features.config.state.colWidth.value"] descriptionWidth = max( 0, width - scrollOffset - optionWidth - valueWidth - 2) for lineNum in range(scrollLoc, len(self.confContents)): entry = self.confContents[lineNum] drawLine = lineNum + detailPanelHeight + 1 - scrollLoc optionLabel = uiTools.cropStr(entry.get(FIELD_OPTION), optionWidth) valueLabel = uiTools.cropStr(entry.get(FIELD_VALUE), valueWidth) # ends description at the first newline descriptionLabel = uiTools.cropStr( entry.get(FIELD_DESCRIPTION).split("\n")[0], descriptionWidth, None) lineFormat = curses.A_NORMAL if entry.get( FIELD_IS_DEFAULT) else curses.A_BOLD if entry.get(FIELD_CATEGORY): lineFormat |= uiTools.getColor( CATEGORY_COLOR[entry.get(FIELD_CATEGORY)]) if entry == cursorSelection: lineFormat |= curses.A_STANDOUT lineTextLayout = "%%-%is %%-%is %%-%is" % (optionWidth, valueWidth, descriptionWidth) lineText = lineTextLayout % (optionLabel, valueLabel, descriptionLabel) self.addstr(drawLine, scrollOffset, lineText, lineFormat) if drawLine >= height: break self.valsLock.release()
def draw(popup, properties): popup.clear() popup.win.box() xOffset = 2 if properties.text: if properties.fingerprint: popup.addstr(0, 0, "Consensus Descriptor (%s):" % properties.fingerprint, curses.A_STANDOUT) else: popup.addstr(0, 0, "Consensus Descriptor:", curses.A_STANDOUT) isEncryption = False # true if line is part of an encryption block # checks if first line is in an encryption block for i in range(0, properties.scroll): lineText = properties.text[i].strip() if lineText in SIG_START_KEYS: isEncryption = True elif lineText in SIG_END_KEYS: isEncryption = False pageHeight = popup.maxY - 2 numFieldWidth = int(math.log10(len(properties.text))) + 1 lineNum = 1 for i in range( properties.scroll, min(len(properties.text), properties.scroll + pageHeight)): lineText = properties.text[i].strip() numOffset = 0 # offset for line numbering if properties.showLineNum: popup.addstr(lineNum, xOffset, ("%%%ii" % numFieldWidth) % (i + 1), curses.A_BOLD | uiTools.getColor(LINE_NUM_COLOR)) numOffset = numFieldWidth + 1 if lineText: keyword = lineText.split()[0] # first word of line remainder = lineText[len(keyword):] keywordFormat = curses.A_BOLD | uiTools.getColor( properties.entryColor) remainderFormat = uiTools.getColor(properties.entryColor) if lineText.startswith( HEADER_PREFIX[0]) or lineText.startswith( HEADER_PREFIX[1]): keyword, remainder = lineText, "" keywordFormat = curses.A_BOLD | uiTools.getColor( HEADER_COLOR) if lineText == UNRESOLVED_MSG or lineText == ERROR_MSG: keyword, remainder = lineText, "" if lineText in SIG_START_KEYS: keyword, remainder = lineText, "" isEncryption = True keywordFormat = curses.A_BOLD | uiTools.getColor(SIG_COLOR) elif lineText in SIG_END_KEYS: keyword, remainder = lineText, "" isEncryption = False keywordFormat = curses.A_BOLD | uiTools.getColor(SIG_COLOR) elif isEncryption: keyword, remainder = lineText, "" keywordFormat = uiTools.getColor(SIG_COLOR) lineNum, xLoc = controller.addstr_wrap(popup, lineNum, 0, keyword, keywordFormat, xOffset + numOffset, popup.maxX - 1, popup.maxY - 1) lineNum, xLoc = controller.addstr_wrap( popup, lineNum, xLoc, remainder, remainderFormat, xOffset + numOffset, popup.maxX - 1, popup.maxY - 1) lineNum += 1 if lineNum > pageHeight: break popup.refresh()
def showCountDialog(countType, counts): """ Provides a dialog with bar graphs and percentages for the given set of counts. Pressing any key closes the dialog. Arguments: countType - type of counts being presented counts - mapping of labels to counts """ isNoStats = not counts noStatsMsg = "Usage stats aren't available yet, press any key..." if isNoStats: popup, width, height = cli.popups.init(3, len(noStatsMsg) + 4) else: popup, width, height = cli.popups.init(4 + max(1, len(counts)), 80) if not popup: return try: control = cli.controller.getController() popup.win.box() # dialog title if countType == CountType.CLIENT_LOCALE: title = "Client Locales" elif countType == CountType.EXIT_PORT: title = "Exiting Port Usage" else: title = "" log.warn("Unrecognized count type: %s" % countType) popup.addstr(0, 0, title, curses.A_STANDOUT) if isNoStats: popup.addstr(1, 2, noStatsMsg, curses.A_BOLD | uiTools.getColor("cyan")) else: sortedCounts = sorted(counts.iteritems(), key=operator.itemgetter(1)) sortedCounts.reverse() # constructs string formatting for the max key and value display width keyWidth, valWidth, valueTotal = 3, 1, 0 for k, v in sortedCounts: keyWidth = max(keyWidth, len(k)) valWidth = max(valWidth, len(str(v))) valueTotal += v # extra space since we're adding usage informaion if countType == CountType.EXIT_PORT: keyWidth += EXIT_USAGE_WIDTH labelFormat = "%%-%is %%%ii (%%%%%%-2i)" % (keyWidth, valWidth) for i in range(height - 4): k, v = sortedCounts[i] # includes a port usage column if countType == CountType.EXIT_PORT: usage = connections.getPortUsage(k) if usage: keyFormat = "%%-%is %%s" % (keyWidth - EXIT_USAGE_WIDTH) k = keyFormat % (k, usage[:EXIT_USAGE_WIDTH - 3]) label = labelFormat % (k, v, v * 100 / valueTotal) popup.addstr(i + 1, 2, label, curses.A_BOLD | uiTools.getColor("green")) # All labels have the same size since they're based on the max widths. # If this changes then this'll need to be the max label width. labelWidth = len(label) # draws simple bar graph for percentages fillWidth = v * (width - 4 - labelWidth) / valueTotal for j in range(fillWidth): popup.addstr(i + 1, 3 + labelWidth + j, " ", curses.A_STANDOUT | uiTools.getColor("red")) popup.addstr(height - 2, 2, "Press any key...") popup.win.refresh() curses.cbreak() control.getScreen().getch() finally: cli.popups.finalize()
def getDisplayAttr(self): myColor = OPTION_COLOR if self.isEnabled() else DISABLED_COLOR return curses.A_BOLD | uiTools.getColor(myColor)
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()
def getDetails(self, width): if not self.isBuilt: detailFormat = curses.A_BOLD | uiTools.getColor(connEntry.CATEGORY_COLOR[self.getType()]) return [("Building Circuit...", detailFormat)] else: return connEntry.ConnectionLine.getDetails(self, width)
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 draw(self, width, height): """ Redraws message log. Entries stretch to use available space and may contain up to two lines. Starts with newest entries. """ currentLog = self.getAttr("msgLog") self.valsLock.acquire() self._lastLoggedEvents, self._lastUpdate = list(currentLog), time.time() # draws the top label if self.isTitleVisible(): self.addstr(0, 0, self._getTitle(width), curses.A_STANDOUT) # restricts scroll location to valid bounds self.scroll = max(0, min(self.scroll, self.lastContentHeight - height + 1)) # draws left-hand scroll bar if content's longer than the height msgIndent, dividerIndent = 1, 0 # offsets for scroll bar isScrollBarVisible = self.lastContentHeight > height - 1 if isScrollBarVisible: msgIndent, dividerIndent = 3, 2 self.addScrollBar(self.scroll, self.scroll + height - 1, self.lastContentHeight, 1) # draws log entries lineCount = 1 - self.scroll seenFirstDateDivider = False dividerAttr, duplicateAttr = curses.A_BOLD | uiTools.getColor("yellow"), curses.A_BOLD | uiTools.getColor("green") isDatesShown = self.regexFilter == None and self._config["features.log.showDateDividers"] eventLog = getDaybreaks(currentLog, self.isPaused()) if isDatesShown else list(currentLog) if not self.showDuplicates: deduplicatedLog = getDuplicates(eventLog) if deduplicatedLog == None: msg = "Deduplication took too long. Its current implementation has difficulty handling large logs so disabling it to keep the interface responsive." log.log(log.WARN, msg) self.showDuplicates = True deduplicatedLog = [(entry, 0) for entry in eventLog] else: deduplicatedLog = [(entry, 0) for entry in eventLog] # determines if we have the minimum width to show date dividers showDaybreaks = width - dividerIndent >= 3 while deduplicatedLog: entry, duplicateCount = deduplicatedLog.pop(0) if self.regexFilter and not self.regexFilter.search(entry.getDisplayMessage()): continue # filter doesn't match log message - skip # checks if we should be showing a divider with the date if entry.type == DAYBREAK_EVENT: # bottom of the divider if seenFirstDateDivider: if lineCount >= 1 and lineCount < height and showDaybreaks: self.addch(lineCount, dividerIndent, curses.ACS_LLCORNER, dividerAttr) self.hline(lineCount, dividerIndent + 1, width - dividerIndent - 2, dividerAttr) self.addch(lineCount, width - 1, curses.ACS_LRCORNER, dividerAttr) lineCount += 1 # top of the divider if lineCount >= 1 and lineCount < height and showDaybreaks: timeLabel = time.strftime(" %B %d, %Y ", time.localtime(entry.timestamp)) self.addch(lineCount, dividerIndent, curses.ACS_ULCORNER, dividerAttr) self.addch(lineCount, dividerIndent + 1, curses.ACS_HLINE, dividerAttr) self.addstr(lineCount, dividerIndent + 2, timeLabel, curses.A_BOLD | dividerAttr) lineLength = width - dividerIndent - len(timeLabel) - 3 self.hline(lineCount, dividerIndent + len(timeLabel) + 2, lineLength, dividerAttr) self.addch(lineCount, dividerIndent + len(timeLabel) + 2 + lineLength, curses.ACS_URCORNER, dividerAttr) seenFirstDateDivider = True lineCount += 1 else: # entry contents to be displayed, tuples of the form: # (msg, formatting, includeLinebreak) displayQueue = [] msgComp = entry.getDisplayMessage().split("\n") for i in range(len(msgComp)): font = curses.A_BOLD if "ERR" in entry.type else curses.A_NORMAL # emphasizes ERR messages displayQueue.append((msgComp[i].strip(), font | uiTools.getColor(entry.color), i != len(msgComp) - 1)) if duplicateCount: pluralLabel = "s" if duplicateCount > 1 else "" duplicateMsg = DUPLICATE_MSG % (duplicateCount, pluralLabel) displayQueue.append((duplicateMsg, duplicateAttr, False)) cursorLoc, lineOffset = msgIndent, 0 maxEntriesPerLine = self._config["features.log.maxLinesPerEntry"] while displayQueue: msg, format, includeBreak = displayQueue.pop(0) drawLine = lineCount + lineOffset if lineOffset == maxEntriesPerLine: break maxMsgSize = width - cursorLoc - 1 if len(msg) > maxMsgSize: # message is too long - break it up if lineOffset == maxEntriesPerLine - 1: msg = uiTools.cropStr(msg, maxMsgSize) else: msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.Ending.HYPHEN, True) displayQueue.insert(0, (remainder.strip(), format, includeBreak)) includeBreak = True if drawLine < height and drawLine >= 1: if seenFirstDateDivider and width - dividerIndent >= 3 and showDaybreaks: self.addch(drawLine, dividerIndent, curses.ACS_VLINE, dividerAttr) self.addch(drawLine, width - 1, curses.ACS_VLINE, dividerAttr) self.addstr(drawLine, cursorLoc, msg, format) cursorLoc += len(msg) if includeBreak or not displayQueue: lineOffset += 1 cursorLoc = msgIndent + ENTRY_INDENT lineCount += lineOffset # if this is the last line and there's room, then draw the bottom of the divider if not deduplicatedLog and seenFirstDateDivider: if lineCount < height and showDaybreaks: self.addch(lineCount, dividerIndent, curses.ACS_LLCORNER, dividerAttr) self.hline(lineCount, dividerIndent + 1, width - dividerIndent - 2, dividerAttr) self.addch(lineCount, width - 1, curses.ACS_LRCORNER, dividerAttr) lineCount += 1 # redraw the display if... # - lastContentHeight was off by too much # - we're off the bottom of the page newContentHeight = lineCount + self.scroll - 1 contentHeightDelta = abs(self.lastContentHeight - newContentHeight) forceRedraw, forceRedrawReason = True, "" if contentHeightDelta >= CONTENT_HEIGHT_REDRAW_THRESHOLD: forceRedrawReason = "estimate was off by %i" % contentHeightDelta elif newContentHeight > height and self.scroll + height - 1 > newContentHeight: forceRedrawReason = "scrolled off the bottom of the page" elif not isScrollBarVisible and newContentHeight > height - 1: forceRedrawReason = "scroll bar wasn't previously visible" elif isScrollBarVisible and newContentHeight <= height - 1: forceRedrawReason = "scroll bar shouldn't be visible" else: forceRedraw = False self.lastContentHeight = newContentHeight if forceRedraw: forceRedrawReason = "redrawing the log panel with the corrected content height (%s)" % forceRedrawReason log.log(self._config["log.logPanel.forceDoubleRedraw"], forceRedrawReason) self.redraw(True) self.valsLock.release()
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 draw(self, subwindow, width, height): self.valsLock.acquire() # If true, we assume that the cached value in self._lastContentHeight is # still accurate, and stop drawing when there's nothing more to display. # Otherwise the self._lastContentHeight is suspect, and we'll process all # the content to check if it's right (and redraw again with the corrected # height if not). trustLastContentHeight = self._lastContentHeightArgs == (width, height) # restricts scroll location to valid bounds self.scroll = max(0, min(self.scroll, self._lastContentHeight - height + 1)) renderedContents, corrections, confLocation = None, {}, None if self.configType == TORRC: loadedTorrc = torConfig.getTorrc() loadedTorrc.getLock().acquire() confLocation = loadedTorrc.getConfigLocation() if not loadedTorrc.isLoaded(): renderedContents = ["### Unable to load the torrc ###"] else: renderedContents = loadedTorrc.getDisplayContents(self.stripComments) # constructs a mapping of line numbers to the issue on it corrections = dict((lineNum, (issue, msg)) for lineNum, issue, msg in loadedTorrc.getCorrections()) loadedTorrc.getLock().release() else: loadedArmrc = conf.getConfig("arm") confLocation = loadedArmrc.path renderedContents = list(loadedArmrc.rawContents) # offset to make room for the line numbers lineNumOffset = 0 if self.showLineNum: if len(renderedContents) == 0: lineNumOffset = 2 else: lineNumOffset = int(math.log10(len(renderedContents))) + 2 # draws left-hand scroll bar if content's longer than the height scrollOffset = 0 if self._config["features.config.file.showScrollbars"] and self._lastContentHeight > height - 1: scrollOffset = 3 self.addScrollBar(self.scroll, self.scroll + height - 1, self._lastContentHeight, 1) displayLine = -self.scroll + 1 # line we're drawing on # draws the top label if self.showLabel: sourceLabel = "Tor" if self.configType == TORRC else "Arm" locationLabel = " (%s)" % confLocation if confLocation else "" self.addstr(0, 0, "%s Configuration File%s:" % (sourceLabel, locationLabel), curses.A_STANDOUT) isMultiline = False # true if we're in the middle of a multiline torrc entry for lineNumber in range(0, len(renderedContents)): lineText = renderedContents[lineNumber] lineText = lineText.rstrip() # remove ending whitespace # blank lines are hidden when stripping comments if self.stripComments and not lineText: continue # splits the line into its component (msg, format) tuples lineComp = {"option": ["", curses.A_BOLD | uiTools.getColor("green")], "argument": ["", curses.A_BOLD | uiTools.getColor("cyan")], "correction": ["", curses.A_BOLD | uiTools.getColor("cyan")], "comment": ["", uiTools.getColor("white")]} # parses the comment commentIndex = lineText.find("#") if commentIndex != -1: lineComp["comment"][0] = lineText[commentIndex:] lineText = lineText[:commentIndex] # splits the option and argument, preserving any whitespace around them strippedLine = lineText.strip() optionIndex = strippedLine.find(" ") if isMultiline: # part of a multiline entry started on a previous line so everything # is part of the argument lineComp["argument"][0] = lineText elif optionIndex == -1: # no argument provided lineComp["option"][0] = lineText else: optionText = strippedLine[:optionIndex] optionEnd = lineText.find(optionText) + len(optionText) lineComp["option"][0] = lineText[:optionEnd] lineComp["argument"][0] = lineText[optionEnd:] # flags following lines as belonging to this multiline entry if it ends # with a slash if strippedLine: isMultiline = strippedLine.endswith("\\") # gets the correction if lineNumber in corrections: lineIssue, lineIssueMsg = corrections[lineNumber] if lineIssue in (torConfig.VAL_DUPLICATE, torConfig.VAL_IS_DEFAULT): lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue") lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("blue") elif lineIssue == torConfig.VAL_MISMATCH: lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("red") lineComp["correction"][0] = " (%s)" % lineIssueMsg else: # For some types of configs the correction field is simply used to # provide extra data (for instance, the type for tor state fields). lineComp["correction"][0] = " (%s)" % lineIssueMsg lineComp["correction"][1] = curses.A_BOLD | uiTools.getColor("magenta") # draws the line number if self.showLineNum and displayLine < height and displayLine >= 1: lineNumStr = ("%%%ii" % (lineNumOffset - 1)) % (lineNumber + 1) self.addstr(displayLine, scrollOffset, lineNumStr, curses.A_BOLD | uiTools.getColor("yellow")) # draws the rest of the components with line wrap cursorLoc, lineOffset = lineNumOffset + scrollOffset, 0 maxLinesPerEntry = self._config["features.config.file.maxLinesPerEntry"] displayQueue = [lineComp[entry] for entry in ("option", "argument", "correction", "comment")] while displayQueue: msg, format = displayQueue.pop(0) maxMsgSize, includeBreak = width - cursorLoc, False if len(msg) >= maxMsgSize: # message is too long - break it up if lineOffset == maxLinesPerEntry - 1: msg = uiTools.cropStr(msg, maxMsgSize) else: includeBreak = True msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.END_WITH_HYPHEN, True) displayQueue.insert(0, (remainder.strip(), format)) drawLine = displayLine + lineOffset if msg and drawLine < height and drawLine >= 1: self.addstr(drawLine, cursorLoc, msg, format) # If we're done, and have added content to this line, then start # further content on the next line. cursorLoc += len(msg) includeBreak |= not displayQueue and cursorLoc != lineNumOffset + scrollOffset if includeBreak: lineOffset += 1 cursorLoc = lineNumOffset + scrollOffset displayLine += max(lineOffset, 1) if trustLastContentHeight and displayLine >= height: break if not trustLastContentHeight: self._lastContentHeightArgs = (width, height) newContentHeight = displayLine + self.scroll - 1 if self._lastContentHeight != newContentHeight: self._lastContentHeight = newContentHeight self.redraw(True) self.valsLock.release()
def draw(self, width, height): self.valsLock.acquire() isWide = width + 1 >= MIN_DUAL_COL_WIDTH # space available for content if isWide: leftWidth = max(width / 2, 77) rightWidth = width - leftWidth else: leftWidth = rightWidth = width # Line 1 / Line 1 Left (system and tor version information) sysNameLabel = "arm - %s" % self.vals["sys/hostname"] contentSpace = min(leftWidth, 40) if len(sysNameLabel) + 10 <= contentSpace: sysTypeLabel = "%s %s" % (self.vals["sys/os"], self.vals["sys/version"]) sysTypeLabel = uiTools.cropStr(sysTypeLabel, contentSpace - len(sysNameLabel) - 3, 4) self.addstr(0, 0, "%s (%s)" % (sysNameLabel, sysTypeLabel)) else: self.addstr(0, 0, uiTools.cropStr(sysNameLabel, contentSpace)) contentSpace = leftWidth - 43 if 7 + len(self.vals["tor/version"]) + len(self.vals["tor/versionStatus"]) <= contentSpace: if self.vals["tor/version"] != "Unknown": versionColor = VERSION_STATUS_COLORS[self.vals["tor/versionStatus"]] if \ self.vals["tor/versionStatus"] in VERSION_STATUS_COLORS else "white" labelPrefix = "Tor %s (" % self.vals["tor/version"] self.addstr(0, 43, labelPrefix) self.addstr(0, 43 + len(labelPrefix), self.vals["tor/versionStatus"], uiTools.getColor(versionColor)) self.addstr(0, 43 + len(labelPrefix) + len(self.vals["tor/versionStatus"]), ")") elif 11 <= contentSpace: self.addstr(0, 43, uiTools.cropStr("Tor %s" % self.vals["tor/version"], contentSpace, 4)) # Line 2 / Line 2 Left (tor ip/port information) x, includeControlPort = 0, True if self.vals["tor/orPort"]: myAddress = "Unknown" if self.vals["tor/orListenAddr"]: myAddress = self.vals["tor/orListenAddr"] elif self.vals["tor/address"]: myAddress = self.vals["tor/address"] # acting as a relay (we can assume certain parameters are set dirPortLabel = ", Dir Port: %s" % self.vals["tor/dirPort"] if self.vals["tor/dirPort"] != "0" else "" for label in (self.vals["tor/nickname"], " - " + myAddress, ":" + self.vals["tor/orPort"], dirPortLabel): if x + len(label) <= leftWidth: self.addstr(1, x, label) x += len(label) else: break else: # non-relay (client only) if self._isTorConnected: self.addstr(1, x, "Relaying Disabled", uiTools.getColor("cyan")) x += 17 else: statusTime = torTools.getConn().getStatus()[1] if statusTime: statusTimeLabel = time.strftime("%H:%M %m/%d/%Y, ", time.localtime(statusTime)) else: statusTimeLabel = "" # never connected to tor self.addstr(1, x, "Tor Disconnected", curses.A_BOLD | uiTools.getColor("red")) self.addstr(1, x + 16, " (%spress r to reconnect)" % statusTimeLabel) x += 39 + len(statusTimeLabel) includeControlPort = False if includeControlPort: if self.vals["tor/controlPort"] == "0": # connected via a control socket self.addstr(1, x, ", Control Socket: %s" % self.vals["tor/socketPath"]) else: if self.vals["tor/isAuthPassword"]: authType = "password" elif self.vals["tor/isAuthCookie"]: authType = "cookie" else: authType = "open" if x + 19 + len(self.vals["tor/controlPort"]) + len(authType) <= leftWidth: authColor = "red" if authType == "open" else "green" self.addstr(1, x, ", Control Port (") self.addstr(1, x + 16, authType, uiTools.getColor(authColor)) self.addstr(1, x + 16 + len(authType), "): %s" % self.vals["tor/controlPort"]) elif x + 16 + len(self.vals["tor/controlPort"]) <= leftWidth: self.addstr(1, 0, ", Control Port: %s" % self.vals["tor/controlPort"]) # Line 3 / Line 1 Right (system usage info) y, x = (0, leftWidth) if isWide else (2, 0) if self.vals["stat/rss"] != "0": memoryLabel = uiTools.getSizeLabel(int(self.vals["stat/rss"])) else: memoryLabel = "0" uptimeLabel = "" if self.vals["tor/startTime"]: if self.isPaused() or not self._isTorConnected: # freeze the uptime when paused or the tor process is stopped uptimeLabel = uiTools.getShortTimeLabel(self.getPauseTime() - self.vals["tor/startTime"]) else: uptimeLabel = uiTools.getShortTimeLabel(time.time() - self.vals["tor/startTime"]) sysFields = ((0, "cpu: %s%% tor, %s%% arm" % (self.vals["stat/%torCpu"], self.vals["stat/%armCpu"])), (27, "mem: %s (%s%%)" % (memoryLabel, self.vals["stat/%mem"])), (47, "pid: %s" % (self.vals["tor/pid"] if self._isTorConnected else "")), (59, "uptime: %s" % uptimeLabel)) for (start, label) in sysFields: if start + len(label) <= rightWidth: self.addstr(y, x + start, label) else: break if self.vals["tor/orPort"]: # Line 4 / Line 2 Right (fingerprint, and possibly file descriptor usage) y, x = (1, leftWidth) if isWide else (3, 0) fingerprintLabel = uiTools.cropStr("fingerprint: %s" % self.vals["tor/fingerprint"], width) self.addstr(y, x, fingerprintLabel) # if there's room and we're able to retrieve both the file descriptor # usage and limit then it might be presented if width - x - 59 >= 20 and self.vals["tor/fdUsed"] and self.vals["tor/fdLimit"]: # display file descriptor usage if we're either configured to do so or # running out fdPercent = 100 * self.vals["tor/fdUsed"] / self.vals["tor/fdLimit"] if fdPercent >= 60 or self._config["features.showFdUsage"]: fdPercentLabel, fdPercentFormat = "%i%%" % fdPercent, curses.A_NORMAL if fdPercent >= 95: fdPercentFormat = curses.A_BOLD | uiTools.getColor("red") elif fdPercent >= 90: fdPercentFormat = uiTools.getColor("red") elif fdPercent >= 60: fdPercentFormat = uiTools.getColor("yellow") estimateChar = "?" if self.vals["tor/isFdLimitEstimate"] else "" baseLabel = "file desc: %i / %i%s (" % (self.vals["tor/fdUsed"], self.vals["tor/fdLimit"], estimateChar) self.addstr(y, x + 59, baseLabel) self.addstr(y, x + 59 + len(baseLabel), fdPercentLabel, fdPercentFormat) self.addstr(y, x + 59 + len(baseLabel) + len(fdPercentLabel), ")") # Line 5 / Line 3 Left (flags) if self._isTorConnected: y, x = (2 if isWide else 4, 0) self.addstr(y, x, "flags: ") x += 7 if len(self.vals["tor/flags"]) > 0: for i in range(len(self.vals["tor/flags"])): flag = self.vals["tor/flags"][i] flagColor = FLAG_COLORS[flag] if flag in FLAG_COLORS.keys() else "white" self.addstr(y, x, flag, curses.A_BOLD | uiTools.getColor(flagColor)) x += len(flag) if i < len(self.vals["tor/flags"]) - 1: self.addstr(y, x, ", ") x += 2 else: self.addstr(y, x, "none", curses.A_BOLD | uiTools.getColor("cyan")) else: y = 2 if isWide else 4 statusTime = torTools.getConn().getStatus()[1] statusTimeLabel = time.strftime("%H:%M %m/%d/%Y", time.localtime(statusTime)) self.addstr(y, 0, "Tor Disconnected", curses.A_BOLD | uiTools.getColor("red")) self.addstr(y, 16, " (%s) - press r to reconnect" % statusTimeLabel) # Undisplayed / Line 3 Right (exit policy) if isWide: exitPolicy = self.vals["tor/exitPolicy"] # adds note when default exit policy is appended if exitPolicy == "": exitPolicy = "<default>" elif not exitPolicy.endswith((" *:*", " *")): exitPolicy += ", <default>" self.addstr(2, leftWidth, "exit policy: ") x = leftWidth + 13 # color codes accepts to be green, rejects to be red, and default marker to be cyan isSimple = len(exitPolicy) > rightWidth - 13 policies = exitPolicy.split(", ") for i in range(len(policies)): policy = policies[i].strip() policyLabel = policy.replace("accept", "").replace("reject", "").strip() if isSimple else policy policyColor = "white" if policy.startswith("accept"): policyColor = "green" elif policy.startswith("reject"): policyColor = "red" elif policy.startswith("<default>"): policyColor = "cyan" self.addstr(2, x, policyLabel, curses.A_BOLD | uiTools.getColor(policyColor)) x += len(policyLabel) if i < len(policies) - 1: self.addstr(2, x, ", ") x += 2 else: # (Client only) Undisplayed / Line 2 Right (new identity option) if isWide: conn = torTools.getConn() newnymWait = conn.getNewnymWait() msg = "press 'n' for a new identity" if newnymWait > 0: pluralLabel = "s" if newnymWait > 1 else "" msg = "building circuits, available again in %i second%s" % (newnymWait, pluralLabel) self.addstr(1, leftWidth, msg) self.valsLock.release()
def draw(self, width, height): self.valsLock.acquire() isWide = width + 1 >= MIN_DUAL_COL_WIDTH # space available for content if isWide: leftWidth = max(width / 2, 77) rightWidth = width - leftWidth else: leftWidth = rightWidth = width # Line 1 / Line 1 Left (system and tor version information) sysNameLabel = "arm - %s" % self.vals["sys/hostname"] contentSpace = min(leftWidth, 40) if len(sysNameLabel) + 10 <= contentSpace: sysTypeLabel = "%s %s" % (self.vals["sys/os"], self.vals["sys/version"]) sysTypeLabel = uiTools.cropStr( sysTypeLabel, contentSpace - len(sysNameLabel) - 3, 4) self.addstr(0, 0, "%s (%s)" % (sysNameLabel, sysTypeLabel)) else: self.addstr(0, 0, uiTools.cropStr(sysNameLabel, contentSpace)) contentSpace = leftWidth - 43 if 7 + len(self.vals["tor/version"]) + len( self.vals["tor/versionStatus"]) <= contentSpace: if self.vals["tor/version"] != "Unknown": versionColor = VERSION_STATUS_COLORS[self.vals["tor/versionStatus"]] if \ self.vals["tor/versionStatus"] in VERSION_STATUS_COLORS else "white" labelPrefix = "Tor %s (" % self.vals["tor/version"] self.addstr(0, 43, labelPrefix) self.addstr(0, 43 + len(labelPrefix), self.vals["tor/versionStatus"], uiTools.getColor(versionColor)) self.addstr( 0, 43 + len(labelPrefix) + len(self.vals["tor/versionStatus"]), ")") elif 11 <= contentSpace: self.addstr( 0, 43, uiTools.cropStr("Tor %s" % self.vals["tor/version"], contentSpace, 4)) # Line 2 / Line 2 Left (tor ip/port information) x, includeControlPort = 0, True if self.vals["tor/orPort"]: myAddress = "Unknown" if self.vals["tor/orListenAddr"]: myAddress = self.vals["tor/orListenAddr"] elif self.vals["tor/address"]: myAddress = self.vals["tor/address"] # acting as a relay (we can assume certain parameters are set dirPortLabel = ", Dir Port: %s" % self.vals[ "tor/dirPort"] if self.vals["tor/dirPort"] != "0" else "" for label in (self.vals["tor/nickname"], " - " + myAddress, ":" + self.vals["tor/orPort"], dirPortLabel): if x + len(label) <= leftWidth: self.addstr(1, x, label) x += len(label) else: break else: # non-relay (client only) if self._isTorConnected: self.addstr(1, x, "Relaying Disabled", uiTools.getColor("cyan")) x += 17 else: statusTime = torTools.getConn().getHeartbeat() if statusTime: statusTimeLabel = time.strftime("%H:%M %m/%d/%Y, ", time.localtime(statusTime)) else: statusTimeLabel = "" # never connected to tor self.addstr(1, x, "Tor Disconnected", curses.A_BOLD | uiTools.getColor("red")) self.addstr(1, x + 16, " (%spress r to reconnect)" % statusTimeLabel) x += 39 + len(statusTimeLabel) includeControlPort = False if includeControlPort: if self.vals["tor/controlPort"] == "0": # connected via a control socket self.addstr( 1, x, ", Control Socket: %s" % self.vals["tor/socketPath"]) else: if self.vals["tor/isAuthPassword"]: authType = "password" elif self.vals["tor/isAuthCookie"]: authType = "cookie" else: authType = "open" if x + 19 + len(self.vals["tor/controlPort"]) + len( authType) <= leftWidth: authColor = "red" if authType == "open" else "green" self.addstr(1, x, ", Control Port (") self.addstr(1, x + 16, authType, uiTools.getColor(authColor)) self.addstr(1, x + 16 + len(authType), "): %s" % self.vals["tor/controlPort"]) elif x + 16 + len(self.vals["tor/controlPort"]) <= leftWidth: self.addstr( 1, 0, ", Control Port: %s" % self.vals["tor/controlPort"]) # Line 3 / Line 1 Right (system usage info) y, x = (0, leftWidth) if isWide else (2, 0) if self.vals["stat/rss"] != "0": memoryLabel = str_tools.get_size_label(int(self.vals["stat/rss"])) else: memoryLabel = "0" uptimeLabel = "" if self.vals["tor/startTime"]: if self.isPaused() or not self._isTorConnected: # freeze the uptime when paused or the tor process is stopped uptimeLabel = str_tools.get_short_time_label( self.getPauseTime() - self.vals["tor/startTime"]) else: uptimeLabel = str_tools.get_short_time_label( time.time() - self.vals["tor/startTime"]) sysFields = ((0, "cpu: %s%% tor, %s%% arm" % (self.vals["stat/%torCpu"], self.vals["stat/%armCpu"])), (27, "mem: %s (%s%%)" % (memoryLabel, self.vals["stat/%mem"])), (47, "pid: %s" % (self.vals["tor/pid"] if self._isTorConnected else "")), (59, "uptime: %s" % uptimeLabel)) for (start, label) in sysFields: if start + len(label) <= rightWidth: self.addstr(y, x + start, label) else: break if self.vals["tor/orPort"]: # Line 4 / Line 2 Right (fingerprint, and possibly file descriptor usage) y, x = (1, leftWidth) if isWide else (3, 0) fingerprintLabel = uiTools.cropStr( "fingerprint: %s" % self.vals["tor/fingerprint"], width) self.addstr(y, x, fingerprintLabel) # if there's room and we're able to retrieve both the file descriptor # usage and limit then it might be presented if width - x - 59 >= 20 and self.vals["tor/fdUsed"] and self.vals[ "tor/fdLimit"]: # display file descriptor usage if we're either configured to do so or # running out fdPercent = 100 * self.vals["tor/fdUsed"] / self.vals[ "tor/fdLimit"] if fdPercent >= 60 or CONFIG["features.showFdUsage"]: fdPercentLabel, fdPercentFormat = "%i%%" % fdPercent, curses.A_NORMAL if fdPercent >= 95: fdPercentFormat = curses.A_BOLD | uiTools.getColor( "red") elif fdPercent >= 90: fdPercentFormat = uiTools.getColor("red") elif fdPercent >= 60: fdPercentFormat = uiTools.getColor("yellow") estimateChar = "?" if self.vals[ "tor/isFdLimitEstimate"] else "" baseLabel = "file desc: %i / %i%s (" % ( self.vals["tor/fdUsed"], self.vals["tor/fdLimit"], estimateChar) self.addstr(y, x + 59, baseLabel) self.addstr(y, x + 59 + len(baseLabel), fdPercentLabel, fdPercentFormat) self.addstr(y, x + 59 + len(baseLabel) + len(fdPercentLabel), ")") # Line 5 / Line 3 Left (flags) if self._isTorConnected: y, x = (2 if isWide else 4, 0) self.addstr(y, x, "flags: ") x += 7 if len(self.vals["tor/flags"]) > 0: for i in range(len(self.vals["tor/flags"])): flag = self.vals["tor/flags"][i] flagColor = FLAG_COLORS[ flag] if flag in FLAG_COLORS.keys() else "white" self.addstr( y, x, flag, curses.A_BOLD | uiTools.getColor(flagColor)) x += len(flag) if i < len(self.vals["tor/flags"]) - 1: self.addstr(y, x, ", ") x += 2 else: self.addstr(y, x, "none", curses.A_BOLD | uiTools.getColor("cyan")) else: y = 2 if isWide else 4 statusTime = torTools.getConn().getHeartbeat() statusTimeLabel = time.strftime("%H:%M %m/%d/%Y", time.localtime(statusTime)) self.addstr(y, 0, "Tor Disconnected", curses.A_BOLD | uiTools.getColor("red")) self.addstr(y, 16, " (%s) - press r to reconnect" % statusTimeLabel) # Undisplayed / Line 3 Right (exit policy) if isWide: exitPolicy = self.vals["tor/exitPolicy"] # adds note when default exit policy is appended if exitPolicy == "": exitPolicy = "<default>" elif not exitPolicy.endswith((" *:*", " *")): exitPolicy += ", <default>" self.addstr(2, leftWidth, "exit policy: ") x = leftWidth + 13 # color codes accepts to be green, rejects to be red, and default marker to be cyan isSimple = len(exitPolicy) > rightWidth - 13 policies = exitPolicy.split(", ") for i in range(len(policies)): policy = policies[i].strip() policyLabel = policy.replace("accept", "").replace( "reject", "").strip() if isSimple else policy policyColor = "white" if policy.startswith("accept"): policyColor = "green" elif policy.startswith("reject"): policyColor = "red" elif policy.startswith("<default>"): policyColor = "cyan" self.addstr(2, x, policyLabel, curses.A_BOLD | uiTools.getColor(policyColor)) x += len(policyLabel) if i < len(policies) - 1: self.addstr(2, x, ", ") x += 2 else: # (Client only) Undisplayed / Line 2 Right (new identity option) if isWide: conn = torTools.getConn() newnymWait = conn.getNewnymWait() msg = "press 'n' for a new identity" if newnymWait > 0: pluralLabel = "s" if newnymWait > 1 else "" msg = "building circuits, available again in %i second%s" % ( newnymWait, pluralLabel) self.addstr(1, leftWidth, msg) self.valsLock.release()
def draw(self, width, height): """ Redraws graph panel """ if self.currentDisplay: param = self.getAttr("stats")[self.currentDisplay] graphCol = min((width - 10) / 2, param.maxCol) primaryColor = uiTools.getColor(param.getColor(True)) secondaryColor = uiTools.getColor(param.getColor(False)) if self.isTitleVisible(): self.addstr(0, 0, param.getTitle(width), curses.A_STANDOUT) # top labels left, right = param.getHeaderLabel(width / 2, True), param.getHeaderLabel(width / 2, False) if left: self.addstr(1, 0, left, curses.A_BOLD | primaryColor) if right: self.addstr(1, graphCol + 5, right, curses.A_BOLD | secondaryColor) # determines max/min value on the graph if self.bounds == Bounds.GLOBAL_MAX: primaryMaxBound = int(param.maxPrimary[self.updateInterval]) secondaryMaxBound = int(param.maxSecondary[self.updateInterval]) else: # both Bounds.LOCAL_MAX and Bounds.TIGHT use local maxima if graphCol < 2: # nothing being displayed primaryMaxBound, secondaryMaxBound = 0, 0 else: primaryMaxBound = int(max(param.primaryCounts[self.updateInterval][1:graphCol + 1])) secondaryMaxBound = int(max(param.secondaryCounts[self.updateInterval][1:graphCol + 1])) primaryMinBound = secondaryMinBound = 0 if self.bounds == Bounds.TIGHT: primaryMinBound = int(min(param.primaryCounts[self.updateInterval][1:graphCol + 1])) secondaryMinBound = int(min(param.secondaryCounts[self.updateInterval][1:graphCol + 1])) # if the max = min (ie, all values are the same) then use zero lower # bound so a graph is still displayed if primaryMinBound == primaryMaxBound: primaryMinBound = 0 if secondaryMinBound == secondaryMaxBound: secondaryMinBound = 0 # displays upper and lower bounds self.addstr(2, 0, "%4i" % primaryMaxBound, primaryColor) self.addstr(self.graphHeight + 1, 0, "%4i" % primaryMinBound, primaryColor) self.addstr(2, graphCol + 5, "%4i" % secondaryMaxBound, secondaryColor) self.addstr(self.graphHeight + 1, graphCol + 5, "%4i" % secondaryMinBound, secondaryColor) # displays intermediate bounds on every other row if CONFIG["features.graph.showIntermediateBounds"]: ticks = (self.graphHeight - 3) / 2 for i in range(ticks): row = self.graphHeight - (2 * i) - 3 if self.graphHeight % 2 == 0 and i >= (ticks / 2): row -= 1 if primaryMinBound != primaryMaxBound: primaryVal = (primaryMaxBound - primaryMinBound) * (self.graphHeight - row - 1) / (self.graphHeight - 1) if not primaryVal in (primaryMinBound, primaryMaxBound): self.addstr(row + 2, 0, "%4i" % primaryVal, primaryColor) if secondaryMinBound != secondaryMaxBound: secondaryVal = (secondaryMaxBound - secondaryMinBound) * (self.graphHeight - row - 1) / (self.graphHeight - 1) if not secondaryVal in (secondaryMinBound, secondaryMaxBound): self.addstr(row + 2, graphCol + 5, "%4i" % secondaryVal, secondaryColor) # creates bar graph (both primary and secondary) for col in range(graphCol): colCount = int(param.primaryCounts[self.updateInterval][col + 1]) - primaryMinBound colHeight = min(self.graphHeight, self.graphHeight * colCount / (max(1, primaryMaxBound) - primaryMinBound)) for row in range(colHeight): self.addstr(self.graphHeight + 1 - row, col + 5, " ", curses.A_STANDOUT | primaryColor) colCount = int(param.secondaryCounts[self.updateInterval][col + 1]) - secondaryMinBound colHeight = min(self.graphHeight, self.graphHeight * colCount / (max(1, secondaryMaxBound) - secondaryMinBound)) for row in range(colHeight): self.addstr(self.graphHeight + 1 - row, col + graphCol + 10, " ", curses.A_STANDOUT | secondaryColor) # bottom labeling of x-axis intervalSec = 1 # seconds per labeling for i in range(len(UPDATE_INTERVALS)): if i == self.updateInterval: intervalSec = UPDATE_INTERVALS[i][1] intervalSpacing = 10 if graphCol >= WIDE_LABELING_GRAPH_COL else 5 unitsLabel, decimalPrecision = None, 0 for i in range((graphCol - 4) / intervalSpacing): loc = (i + 1) * intervalSpacing timeLabel = uiTools.getTimeLabel(loc * intervalSec, decimalPrecision) if not unitsLabel: unitsLabel = timeLabel[-1] elif unitsLabel != timeLabel[-1]: # upped scale so also up precision of future measurements unitsLabel = timeLabel[-1] decimalPrecision += 1 else: # if constrained on space then strips labeling since already provided timeLabel = timeLabel[:-1] self.addstr(self.graphHeight + 2, 4 + loc, timeLabel, primaryColor) self.addstr(self.graphHeight + 2, graphCol + 10 + loc, timeLabel, secondaryColor) param.draw(self, width, height) # allows current stats to modify the display