def _resize_graph(self): """ Prompts for user input to resize the graph panel. Options include... * down arrow - grow graph * up arrow - shrink graph * enter / space - set size """ with nyx.curses.CURSES_LOCK: try: while True: show_message('press the down/up to resize the graph, and enter when done', BOLD) key = nyx.curses.key_input() if key.match('down'): # don't grow the graph if it's already consuming the whole display # (plus an extra line for the graph/log gap) max_height = nyx.curses.screen_size().height - self.get_top() current_height = self.get_height() if current_height < max_height + 1: self.set_graph_height(self._graph_height + 1) elif key.match('up'): self.set_graph_height(self._graph_height - 1) elif key.is_selection(): break nyx_interface().redraw() finally: show_message()
def _update_accounting(self, event): if not CONFIG['show_accounting']: self._accounting_stats = None elif not self._accounting_stats or time.time() - self._accounting_stats.retrieved >= ACCOUNTING_RATE: old_accounting_stats = self._accounting_stats self._accounting_stats = tor_controller().get_accounting_stats(None) if not nyx_interface().is_paused(): # if we either added or removed accounting info then redraw the whole # screen to account for resizing if bool(old_accounting_stats) != bool(self._accounting_stats): nyx_interface().redraw()
def show_menu(): menu = _make_menu() cursor = MenuCursor(menu.children[0].children[0]) with nyx.curses.CURSES_LOCK: show_message('Press m or esc to close the menu.', BOLD) while not cursor.is_done: selection_x = _draw_top_menubar(menu, cursor.selection) _draw_submenu(cursor.selection, cursor.selection.submenu, 1, selection_x) cursor.handle_key(nyx.curses.key_input()) nyx_interface().redraw(True) show_message()
def _draw(self, subwindow): if not self._displayed_stat: return if not nyx_interface().is_paused(): stat = self._stats[self._displayed_stat] accounting_stats = self._accounting_stats else: if not self._stats_paused: return # when first paused concurrency could mean this isn't set yet stat = self._stats_paused[self._displayed_stat] accounting_stats = self._accounting_stats_paused with self._stats_lock: subgraph_height = self._graph_height + 2 # graph rows + header + x-axis label subgraph_width = min(subwindow.width // 2, CONFIG['max_graph_width']) interval, bounds_type = self._update_interval, self._bounds_type subwindow.addstr(0, 0, stat.title(subwindow.width), HIGHLIGHT) _draw_subgraph(subwindow, stat.primary, 0, subgraph_width, subgraph_height, bounds_type, interval, PRIMARY_COLOR) _draw_subgraph(subwindow, stat.secondary, subgraph_width, subgraph_width, subgraph_height, bounds_type, interval, SECONDARY_COLOR) if stat.stat_type() == GraphStat.BANDWIDTH and accounting_stats: _draw_accounting_stats(subwindow, DEFAULT_CONTENT_HEIGHT + subgraph_height - 2, accounting_stats)
def _draw(self, subwindow, is_correction=False): scroll = self._scroller.location(self._last_content_height, subwindow.height - 1) event_filter = self._filter.clone() event_types = list(self._event_types) last_content_height = self._last_content_height show_duplicates = self._show_duplicates event_log = self._event_log_paused if nyx_interface().is_paused( ) else self._event_log event_log = list( filter(lambda entry: event_filter.match(entry.display_message), event_log)) event_log = list( filter(lambda entry: not entry.is_duplicate or show_duplicates, event_log)) is_scrollbar_visible = last_content_height > subwindow.height - 1 if is_scrollbar_visible: subwindow.scrollbar(1, scroll, last_content_height) x, y = 2 if is_scrollbar_visible else 0, 1 - scroll y = _draw_entries(subwindow, x, y, event_log, show_duplicates) # drawing the title after the content, so we'll clear content from the top line _draw_title(subwindow, event_types, event_filter) # redraw the display if... # - last_content_height was off by too much # - we're off the bottom of the page new_content_height = y + scroll - 1 content_height_delta = abs(last_content_height - new_content_height) force_redraw, force_redraw_reason = True, '' if content_height_delta >= CONTENT_HEIGHT_REDRAW_THRESHOLD: force_redraw_reason = 'estimate was off by %i' % content_height_delta elif new_content_height > subwindow.height and scroll + subwindow.height - 1 > new_content_height: force_redraw_reason = 'scrolled off the bottom of the page' elif not is_scrollbar_visible and new_content_height > subwindow.height - 1: force_redraw_reason = "scroll bar wasn't previously visible" elif is_scrollbar_visible and new_content_height <= subwindow.height - 1: force_redraw_reason = "scroll bar shouldn't be visible" else: force_redraw = False self._last_content_height = new_content_height self._has_new_event = False if force_redraw and not is_correction: log.debug( 'redrawing the log panel with the corrected content height (%s)' % force_redraw_reason) self._draw(subwindow, True)
def show_help(): """ Presents a popup with the current page's hotkeys. :returns: :class:`~nyx.curses.KeyInput` that was pressed to close the popup if it's one panels should act upon, **None** otherwise """ interface = nyx_interface() handlers = [] for panel in reversed(interface.page_panels()[1:]): handlers += [ handler for handler in panel.key_handlers() if handler.description ] def _render(subwindow): subwindow.box() subwindow.addstr(0, 0, 'Page %i Commands:' % (interface.get_page() + 1), HIGHLIGHT) for i, option in enumerate(handlers): if i // 2 >= subwindow.height - 2: break # Entries are shown in the form '<key>: <description>[ (<selection>)]', # such as... # # u: duplicate log entries (hidden) x = 2 if i % 2 == 0 else 41 y = (i // 2) + 1 x = subwindow.addstr(x, y, option.key, BOLD) x = subwindow.addstr(x, y, ': ' + option.description) if option.current: x = subwindow.addstr(x, y, ' (') x = subwindow.addstr(x, y, option.current, BOLD) x = subwindow.addstr(x, y, ')') # tells user to press a key if the lower left is unoccupied if len(handlers) < 13 and subwindow.height == 9: subwindow.addstr(2, 7, 'Press any key...') with nyx.curses.CURSES_LOCK: nyx.curses.draw(_render, top=_top(), width=80, height=9) keypress = nyx.curses.key_input() if keypress.is_selection() or keypress.is_scroll() or keypress.match( 'left', 'right'): return None else: return keypress
def _draw_status(subwindow, x, y, is_paused, message, *attr): """ Provides general usage information or a custom message. """ if message: subwindow.addstr(x, y, message, *attr) elif not is_paused: interface = nyx_interface() subwindow.addstr( x, y, 'page %i / %i - m: menu, p: pause, h: page help, q: quit' % (interface.get_page() + 1, interface.page_count())) else: subwindow.addstr(x, y, 'Paused', HIGHLIGHT)
def _shutdown_daemons(controller): """ Stops and joins on worker threads. """ halt_threads = [nyx.tracker.stop_trackers()] interface = nyx_interface() if interface: halt_threads.append(interface.halt()) for thread in halt_threads: thread.join() controller.close()
def get_height(self): """ Provides the height of the content. """ max_height = nyx.panel.Panel.get_height(self) if not self._displayed_stat: return 0 height = DEFAULT_CONTENT_HEIGHT + self._graph_height accounting_stats = self._accounting_stats if not nyx_interface().is_paused() else self._accounting_stats_paused if self._displayed_stat == GraphStat.BANDWIDTH and accounting_stats: height += 3 return min(max_height, height)
def _draw(self, subwindow): controller = tor_controller() interface = nyx_interface() entries = self._entries lines = list( itertools.chain.from_iterable( [entry.get_lines() for entry in entries])) is_showing_details = self._show_details and lines details_offset = DETAILS_HEIGHT + 1 if is_showing_details else 0 selected, scroll = self._scroller.selection( lines, subwindow.height - details_offset - 1) if interface.is_paused(): current_time = self._pause_time elif not controller.is_alive(): current_time = controller.connection_time() else: current_time = time.time() is_scrollbar_visible = len( lines) > subwindow.height - details_offset - 1 scroll_offset = 2 if is_scrollbar_visible else 0 _draw_title(subwindow, entries, self._show_details) if is_showing_details: _draw_details(subwindow, selected) # draw a 'T' pipe if connecting with the scrollbar if is_scrollbar_visible: subwindow._addch(1, DETAILS_HEIGHT + 1, curses.ACS_TTEE) if is_scrollbar_visible: subwindow.scrollbar(1 + details_offset, scroll, len(lines)) for line_number in range(scroll, len(lines)): y = line_number + details_offset + 1 - scroll _draw_line(subwindow, scroll_offset, y, lines[line_number], lines[line_number] == selected, subwindow.width - scroll_offset, current_time) if y >= subwindow.height: break
def _draw(self, subwindow): vals = self._vals # local reference to avoid concurrency concerns self._last_width = subwindow.width is_wide = self.is_wide() # space available for content interface = nyx_interface() left_width = max(subwindow.width // 2, 77) if is_wide else subwindow.width right_width = subwindow.width - left_width _draw_platform_section(subwindow, 0, 0, left_width, vals) if vals.is_connected: _draw_ports_section(subwindow, 0, 1, left_width, vals) else: _draw_disconnected(subwindow, 0, 1, vals.last_heartbeat) if is_wide: _draw_resource_usage(subwindow, left_width, 0, right_width, vals, self._pause_time) if vals.is_relay: _draw_fingerprint_and_fd_usage(subwindow, left_width, 1, right_width, vals) _draw_flags(subwindow, 0, 2, vals.flags) _draw_exit_policy(subwindow, left_width, 2, vals.exit_policy) elif vals.is_connected: _draw_newnym_option(subwindow, left_width, 1, vals.newnym_wait) else: _draw_resource_usage(subwindow, 0, 2, left_width, vals, self._pause_time) if vals.is_relay: _draw_fingerprint_and_fd_usage(subwindow, 0, 3, left_width, vals) _draw_flags(subwindow, 0, 4, vals.flags) _draw_status(subwindow, 0, self.get_height() - 1, interface.is_paused(), self._message, *self._message_attr)
def _make_menu(): """ Constructs the base menu and all of its contents. """ interface = nyx_interface() if not interface.is_paused(): pause_item = MenuItem('Pause', interface.set_paused, True) else: pause_item = MenuItem('Unpause', interface.set_paused, False) root_menu = Submenu('') root_menu.add( Submenu('Actions', [ MenuItem('Close Menu', None), MenuItem('New Identity', interface.header_panel().send_newnym), MenuItem('Reset Tor', tor_controller().signal, stem.Signal.RELOAD), pause_item, MenuItem('Exit', interface.quit), ])) root_menu.add(_view_menu()) for panel in interface.page_panels(): submenu = panel.submenu() if submenu: root_menu.add(submenu) root_menu.add( Submenu('Help', [ MenuItem('Hotkeys', nyx.popups.show_help), MenuItem('About', nyx.popups.show_about), ])) return root_menu
def _view_menu(): """ Submenu consisting of... [X] <Page 1> [ ] <Page 2> [ ] etc... Color (Submenu) """ interface = nyx_interface() view_menu = Submenu('View') page_group = RadioGroup(interface.set_page, interface.get_page()) for i in range(interface.page_count()): page_panels = interface.page_panels(page_number=i)[1:] label = ' / '.join([ type(panel).__name__.replace('Panel', '') for panel in page_panels ]) view_menu.add(RadioMenuItem(label, page_group, i)) if nyx.curses.is_color_supported(): color_group = RadioGroup(nyx.curses.set_color_override, nyx.curses.get_color_override()) view_menu.add( Submenu('Color', [ RadioMenuItem('All', color_group, None), [ RadioMenuItem(str_tools._to_camel_case(opt), color_group, opt) for opt in nyx.curses.Color ], ])) return view_menu
def _top(): return nyx_interface().header_panel().get_height()