Exemplo n.º 1
0
  def get_label(self, option_width, value_width, summary_width):
    """
    Provides display string of the configuration entry with the given
    constraints on the width of the contents.

    Arguments:
      option_width  - width of the option column
      value_width   - width of the value column
      summary_width - width of the summary column
    """

    # Fetching the display entries is very common so this caches the values.
    # Doing this substantially drops cpu usage when scrolling (by around 40%).

    arg_set = (option_width, value_width, summary_width)

    if not self.label_cache or self.label_cache_args != arg_set:
      option_label = str_tools.crop(self.get(Field.OPTION), option_width)
      value_label = str_tools.crop(self.get(Field.VALUE), value_width)
      summary_label = str_tools.crop(self.get(Field.SUMMARY), summary_width, None)
      line_text_layout = '%%-%is %%-%is %%-%is' % (option_width, value_width, summary_width)
      self.label_cache = line_text_layout % (option_label, value_label, summary_label)
      self.label_cache_args = arg_set

    return self.label_cache
Exemplo n.º 2
0
 def test_crop(self):
     # test the pydoc examples
     self.assertEqual('This is a looo...',
                      str_tools.crop('This is a looooong message', 17))
     self.assertEqual('This is a...',
                      str_tools.crop('This is a looooong message', 12))
     self.assertEqual('', str_tools.crop('This is a looooong message', 3))
Exemplo n.º 3
0
def _draw_selection_details(subwindow, selected):
  """
  Shows details of the currently selected option.
  """

  attr = ', '.join(('custom' if selected.is_set() else 'default', selected.value_type, 'usage: %s' % selected.manual.usage))
  selected_color = CONFIG['attr.config.category_color'].get(selected.manual.category, WHITE)
  subwindow.box(0, 0, subwindow.width, DETAILS_HEIGHT)

  subwindow.addstr(2, 1, '%s (%s Option)' % (selected.name, selected.manual.category), selected_color, BOLD)
  subwindow.addstr(2, 2, 'Value: %s (%s)' % (selected.value(), str_tools.crop(attr, subwindow.width - len(selected.value()) - 13)), selected_color, BOLD)

  description = 'Description: %s' % selected.manual.description

  for i in range(DETAILS_HEIGHT - 4):
    if not description:
      break  # done writing description

    line, description = description.split('\n', 1) if '\n' in description else (description, '')

    if i < DETAILS_HEIGHT - 5:
      line, remainder = str_tools.crop(line, subwindow.width - 3, 4, 4, str_tools.Ending.HYPHEN, True)
      description = '  ' + remainder.strip() + description
      subwindow.addstr(2, 3 + i, line, selected_color, BOLD)
    else:
      subwindow.addstr(2, 3 + i, str_tools.crop(line, subwindow.width - 3, 4, 4), selected_color, BOLD)
Exemplo n.º 4
0
def _draw_line(subwindow, x, y, entry, is_selected, value_width, description_width):
  """
  Show an individual configuration line.
  """

  attr = [CONFIG['attr.config.category_color'].get(entry.manual.category, WHITE)]
  attr.append(BOLD if entry.is_set() else NORMAL)
  attr.append(HIGHLIGHT if is_selected else NORMAL)

  option_label = str_tools.crop(entry.name, NAME_WIDTH).ljust(NAME_WIDTH + 1)
  value_label = str_tools.crop(entry.value(), value_width).ljust(value_width + 1)
  summary_label = str_tools.crop(entry.manual.summary, description_width).ljust(description_width)

  subwindow.addstr(x, y, option_label + value_label + summary_label, *attr)
Exemplo n.º 5
0
def _draw_address_column(subwindow, x, y, line, attr):
    src = tor_controller().get_info('address', line.connection.local_address)

    if line.line_type == LineType.CONNECTION:
        src = '%s:%s' % (src, line.connection.local_port)

    if line.line_type == LineType.CIRCUIT_HEADER and line.circuit.status != 'BUILT':
        dst = 'Building...'
    else:
        dst = '<scrubbed>' if line.entry.is_private(
        ) else line.connection.remote_address
        dst += ':%s' % line.connection.remote_port

        if line.entry.get_type() == Category.EXIT:
            purpose = connection.port_usage(line.connection.remote_port)

            if purpose:
                dst += ' (%s)' % str_tools.crop(purpose, 26 - len(dst) - 3)
        elif not tor_controller().is_geoip_unavailable(
        ) and not line.entry.is_private():
            dst += ' (%s)' % (line.locale if line.locale else '??')

    src = '%-21s' % src
    dst = '%-21s' % dst if tor_controller().is_geoip_unavailable(
    ) else '%-26s' % dst

    if line.entry.get_type() in (Category.INBOUND, Category.SOCKS,
                                 Category.CONTROL):
        dst, src = src, dst

    if line.line_type == LineType.CIRCUIT:
        return subwindow.addstr(x, y, dst, *attr)
    else:
        return subwindow.addstr(x, y, '%s  -->  %s' % (src, dst), *attr)
Exemplo n.º 6
0
def _draw_address_column(subwindow, x, y, line, attr):
  src = tor_controller().get_info('address', line.connection.local_address)
  src += ':%s' % line.connection.local_port if line.line_type == LineType.CONNECTION else ''

  if line.line_type == LineType.CIRCUIT_HEADER and line.circuit.status != 'BUILT':
    dst = 'Building...'
  else:
    dst = '<scrubbed>' if line.entry.is_private() else line.connection.remote_address
    dst += ':%s' % line.connection.remote_port

    if line.entry.get_type() == Category.EXIT:
      purpose = connection.port_usage(line.connection.remote_port)

      if purpose:
        dst += ' (%s)' % str_tools.crop(purpose, 26 - len(dst) - 3)
    elif not tor_controller().is_geoip_unavailable() and not line.entry.is_private():
      dst += ' (%s)' % (line.locale if line.locale else '??')

  if line.entry.get_type() in (Category.INBOUND, Category.SOCKS, Category.CONTROL):
    dst, src = src, dst

  if line.line_type == LineType.CIRCUIT:
    subwindow.addstr(x, y, dst, *attr)
  else:
    subwindow.addstr(x, y, '%-21s  -->  %-26s' % (src, dst), *attr)
Exemplo n.º 7
0
    def format(self, message, crop_width = None):
      formatted_msg = message.format(**self._attr)

      if crop_width:
        formatted_msg = str_tools.crop(formatted_msg, crop_width)

      return formatted_msg
Exemplo n.º 8
0
def _draw_selection_details(subwindow, selected):
    """
  Shows details of the currently selected option.
  """

    attr = ['custom' if selected.is_set() else 'default', selected.value_type]

    if selected.usage:
        attr.append('usage: %s' % selected.usage)

    selected_color = CONFIG['attr.config.category_color'].get(
        selected.category, WHITE)
    subwindow.box(0, 0, subwindow.width, DETAILS_HEIGHT)

    if selected.category:
        subwindow.addstr(2, 1,
                         '%s (%s Option)' % (selected.name, selected.category),
                         selected_color, BOLD)
    else:
        subwindow.addstr(2, 1, selected.name, selected_color, BOLD)

    subwindow.addstr(
        2, 2, 'Value: %s (%s)' %
        (selected.value(),
         str_tools.crop(', '.join(attr),
                        max(0, subwindow.width - len(selected.value()) - 13))),
        selected_color, BOLD)

    description = 'Description: %s' % selected.description

    for i in range(DETAILS_HEIGHT - 4):
        if not description:
            break  # done writing description

        line, description = description.split(
            '\n', 1) if '\n' in description else (description, '')

        if i < DETAILS_HEIGHT - 5:
            line, remainder = str_tools.crop(line, subwindow.width - 3, 4, 4,
                                             str_tools.Ending.HYPHEN, True)
            description = '  ' + remainder.strip() + description
            subwindow.addstr(2, 3 + i, line, selected_color, BOLD)
        else:
            subwindow.addstr(2, 3 + i,
                             str_tools.crop(line, subwindow.width - 3, 4, 4),
                             selected_color, BOLD)
Exemplo n.º 9
0
def _draw_line(subwindow, x, y, entry, is_selected, value_width,
               description_width):
    """
  Show an individual configuration line.
  """

    attr = [CONFIG['attr.config.category_color'].get(entry.category, WHITE)]
    attr.append(BOLD if entry.is_set() else NORMAL)
    attr.append(HIGHLIGHT if is_selected else NORMAL)

    option_label = str_tools.crop(entry.name, NAME_WIDTH).ljust(NAME_WIDTH + 1)
    value_label = str_tools.crop(entry.value(),
                                 value_width).ljust(value_width + 1)
    summary_label = str_tools.crop(entry.summary,
                                   description_width).ljust(description_width)

    subwindow.addstr(x, y, option_label + value_label + summary_label, *attr)
Exemplo n.º 10
0
  def addstr_wrap(self, y, x, msg, width, min_x = 0, *attr):
    orig_y = y

    while msg:
      draw_msg, msg = str_tools.crop(msg, width - x, None, ending = None, get_remainder = True)

      if not draw_msg:
        draw_msg, msg = str_tools.crop(msg, width - x), ''  # first word is longer than the line

      x = self.addstr(y, x, draw_msg, *attr)

      if (y - orig_y + 1) >= CONFIG['features.maxLineWrap']:
        break  # maximum number we'll wrap

      if msg:
        x, y = min_x, y + 1

    return x, y
Exemplo n.º 11
0
 def test_crop(self):
   # test the pydoc examples
   self.assertEqual('This is a looo...', str_tools.crop('This is a looooong message', 17))
   self.assertEqual('This is a...', str_tools.crop('This is a looooong message', 12))
   self.assertEqual('', str_tools.crop('This is a looooong message', 3))
   self.assertEqual('', str_tools.crop('This is a looooong message', 0))
Exemplo n.º 12
0
  def draw(self, width, height):
    with self._vals_lock:
      # If true, we assume that the cached value in self._last_content_height is
      # still accurate, and stop drawing when there's nothing more to display.
      # Otherwise the self._last_content_height is suspect, and we'll process all
      # the content to check if it's right (and redraw again with the corrected
      # height if not).

      trust_last_content_height = self._last_content_height_args == (width, height)

      # restricts scroll location to valid bounds

      self.scroll = max(0, min(self.scroll, self._last_content_height - height + 1))

      rendered_contents, corrections, conf_location = None, {}, None

      if self.config_type == Config.TORRC:
        loaded_torrc = tor_config.get_torrc()

        with loaded_torrc.get_lock():
          conf_location = loaded_torrc.get_config_location()

          if not loaded_torrc.is_loaded():
            rendered_contents = ['### Unable to load the torrc ###']
          else:
            rendered_contents = loaded_torrc.get_display_contents(self.strip_comments)

            # constructs a mapping of line numbers to the issue on it

            corrections = dict((line_number, (issue, msg)) for line_number, issue, msg in loaded_torrc.get_corrections())
      else:
        loaded_nyxrc = conf.get_config('nyx')
        conf_location = loaded_nyxrc._path
        rendered_contents = list(loaded_nyxrc._raw_contents)

      # offset to make room for the line numbers

      line_number_offset = 0

      if self.show_line_num:
        if len(rendered_contents) == 0:
          line_number_offset = 2
        else:
          line_number_offset = int(math.log10(len(rendered_contents))) + 2

      # draws left-hand scroll bar if content's longer than the height

      scroll_offset = 0

      if CONFIG['features.config.file.showScrollbars'] and self._last_content_height > height - 1:
        scroll_offset = 3
        self.add_scroll_bar(self.scroll, self.scroll + height - 1, self._last_content_height, 1)

      display_line = -self.scroll + 1  # line we're drawing on

      # draws the top label

      if self.is_title_visible():
        source_label = 'Tor' if self.config_type == Config.TORRC else 'Nyx'
        location_label = ' (%s)' % conf_location if conf_location else ''
        self.addstr(0, 0, '%s Configuration File%s:' % (source_label, location_label), curses.A_STANDOUT)

      is_multiline = False  # true if we're in the middle of a multiline torrc entry

      for line_number in range(0, len(rendered_contents)):
        line_text = rendered_contents[line_number]
        line_text = line_text.rstrip()  # remove ending whitespace

        # blank lines are hidden when stripping comments

        if self.strip_comments and not line_text:
          continue

        # splits the line into its component (msg, format) tuples

        line_comp = {
          'option': ['', (curses.A_BOLD, 'green')],
          'argument': ['', (curses.A_BOLD, 'cyan')],
          'correction': ['', (curses.A_BOLD, 'cyan')],
          'comment': ['', ('white',)],
        }

        # parses the comment

        comment_index = line_text.find('#')

        if comment_index != -1:
          line_comp['comment'][0] = line_text[comment_index:]
          line_text = line_text[:comment_index]

        # splits the option and argument, preserving any whitespace around them

        stripped_line = line_text.strip()
        option_index = stripped_line.find(' ')

        if is_multiline:
          # part of a multiline entry started on a previous line so everything
          # is part of the argument
          line_comp['argument'][0] = line_text
        elif option_index == -1:
          # no argument provided
          line_comp['option'][0] = line_text
        else:
          option_text = stripped_line[:option_index]
          option_end = line_text.find(option_text) + len(option_text)
          line_comp['option'][0] = line_text[:option_end]
          line_comp['argument'][0] = line_text[option_end:]

        # flags following lines as belonging to this multiline entry if it ends
        # with a slash

        if stripped_line:
          is_multiline = stripped_line.endswith('\\')

        # gets the correction

        if line_number in corrections:
          line_issue, line_issue_msg = corrections[line_number]

          if line_issue in (tor_config.ValidationError.DUPLICATE, tor_config.ValidationError.IS_DEFAULT):
            line_comp['option'][1] = (curses.A_BOLD, 'blue')
            line_comp['argument'][1] = (curses.A_BOLD, 'blue')
          elif line_issue == tor_config.ValidationError.MISMATCH:
            line_comp['argument'][1] = (curses.A_BOLD, 'red')
            line_comp['correction'][0] = ' (%s)' % line_issue_msg
          else:
            # For some types of configs the correction field is simply used to
            # provide extra data (for instance, the type for tor state fields).

            line_comp['correction'][0] = ' (%s)' % line_issue_msg
            line_comp['correction'][1] = (curses.A_BOLD, 'magenta')

        # draws the line number

        if self.show_line_num and display_line < height and display_line >= 1:
          line_number_str = ('%%%ii' % (line_number_offset - 1)) % (line_number + 1)
          self.addstr(display_line, scroll_offset, line_number_str, curses.A_BOLD, 'yellow')

        # draws the rest of the components with line wrap

        cursor_location, line_offset = line_number_offset + scroll_offset, 0
        max_lines_per_entry = CONFIG['features.config.file.max_lines_per_entry']
        display_queue = [line_comp[entry] for entry in ('option', 'argument', 'correction', 'comment')]

        while display_queue:
          msg, format = display_queue.pop(0)

          max_msg_size, include_break = width - cursor_location, False

          if len(msg) >= max_msg_size:
            # message is too long - break it up

            if line_offset == max_lines_per_entry - 1:
              msg = str_tools.crop(msg, max_msg_size)
            else:
              include_break = True
              msg, remainder = str_tools.crop(msg, max_msg_size, 4, 4, str_tools.Ending.HYPHEN, True)
              display_queue.insert(0, (remainder.strip(), format))

          draw_line = display_line + line_offset

          if msg and draw_line < height and draw_line >= 1:
            self.addstr(draw_line, cursor_location, msg, *format)

          # If we're done, and have added content to this line, then start
          # further content on the next line.

          cursor_location += len(msg)
          include_break |= not display_queue and cursor_location != line_number_offset + scroll_offset

          if include_break:
            line_offset += 1
            cursor_location = line_number_offset + scroll_offset

        display_line += max(line_offset, 1)

        if trust_last_content_height and display_line >= height:
          break

      if not trust_last_content_height:
        self._last_content_height_args = (width, height)
        new_content_height = display_line + self.scroll - 1

        if self._last_content_height != new_content_height:
          self._last_content_height = new_content_height
          self.redraw(True)
Exemplo n.º 13
0
 def draw_line(x, y, width, msg, *attr):
   msg, remaining_lines = msg.split('\n', 1) if ('\n' in msg) else (msg, '')
   msg, cropped = str_tools.crop(msg, width - x - 1, min_crop = 4, ending = str_tools.Ending.HYPHEN, get_remainder = True)
   x = self.addstr(y, x, msg, *attr)
   return x, (cropped + '\n' + remaining_lines).strip()
Exemplo n.º 14
0
  def _draw_selection_panel(self, selection, width, detail_panel_height, is_scrollbar_visible):
    """
    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.

    ui_tools.draw_box(self, 0, 0, width, detail_panel_height + 1)

    if is_scrollbar_visible:
      self.addch(detail_panel_height, 1, curses.ACS_TTEE)

    selection_format = (curses.A_BOLD, CATEGORY_COLOR[selection.get(Field.CATEGORY)])

    # first entry:
    # <option> (<category> Option)

    option_label = ' (%s Option)' % selection.get(Field.CATEGORY)
    self.addstr(1, 2, selection.get(Field.OPTION) + option_label, *selection_format)

    # second entry:
    # Value: <value> ([default|custom], <type>, usage: <argument usage>)

    if detail_panel_height >= 3:
      value_attr = []
      value_attr.append('default' if selection.get(Field.IS_DEFAULT) else 'custom')
      value_attr.append(selection.get(Field.TYPE))
      value_attr.append('usage: %s' % (selection.get(Field.ARG_USAGE)))
      value_attr_label = ', '.join(value_attr)

      value_label_width = width - 12 - len(value_attr_label)
      value_label = str_tools.crop(selection.get(Field.VALUE), value_label_width)

      self.addstr(2, 2, 'Value: %s (%s)' % (value_label, value_attr_label), *selection_format)

    # remainder is filled with the man page description

    description_height = max(0, detail_panel_height - 3)
    description_content = 'Description: ' + selection.get(Field.DESCRIPTION)

    for i in range(description_height):
      # checks if we're done writing the description

      if not description_content:
        break

      # there's a leading indent after the first line

      if i > 0:
        description_content = '  ' + description_content

      # we only want to work with content up until the next newline

      if '\n' in description_content:
        line_content, description_content = description_content.split('\n', 1)
      else:
        line_content, description_content = description_content, ''

      if i != description_height - 1:
        # there's more lines to display

        msg, remainder = str_tools.crop(line_content, width - 3, 4, 4, str_tools.Ending.HYPHEN, True)
        description_content = remainder.strip() + description_content
      else:
        # this is the last line, end it with an ellipse

        msg = str_tools.crop(line_content, width - 3, 4, 4)

      self.addstr(3 + i, 2, msg, *selection_format)
Exemplo n.º 15
0
  def show_write_dialog(self):
    """
    Provies an interface to confirm if the configuration is saved and, if so,
    where.
    """

    # display a popup for saving the current configuration

    config_lines = tor_config.get_custom_options(True)
    popup, width, height = nyx.popups.init(len(config_lines) + 2)

    if not popup:
      return

    try:
      # displayed options (truncating the labels if there's limited room)

      if width >= 30:
        selection_options = ('Save', 'Save As...', 'Cancel')
      else:
        selection_options = ('Save', 'Save As', 'X')

      # checks if we can show options beside the last line of visible content

      is_option_line_separate = False
      last_index = min(height - 2, len(config_lines) - 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(config_lines[last_index])):
        popup.set_height(height + 1)
        popup.redraw(True)  # recreates the window instance
        new_height, _ = popup.get_preferred_size()

        if new_height > height:
          height = new_height
          is_option_line_separate = True

      selection = 2

      while True:
        # if the popup has been resized then recreate it (needed for the
        # proper border height)

        new_height, new_width = popup.get_preferred_size()

        if (height, width) != (new_height, new_width):
          height, width = new_height, new_width
          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)

        visible_config_lines = height - 3 if is_option_line_separate else height - 2

        for i in range(visible_config_lines):
          line = str_tools.crop(config_lines[i], width - 2)

          if ' ' in line:
            option, arg = line.split(' ', 1)
            popup.addstr(i + 1, 1, option, curses.A_BOLD, 'green')
            popup.addstr(i + 1, len(option) + 2, arg, curses.A_BOLD, 'cyan')
          else:
            popup.addstr(i + 1, 1, line, curses.A_BOLD, 'green')

        # draws selection options (drawn right to left)

        draw_x = width - 1

        for i in range(len(selection_options) - 1, -1, -1):
          option_label = selection_options[i]
          draw_x -= (len(option_label) + 2)

          # if we've run out of room then drop the option (this will only
          # occure on tiny displays)

          if draw_x < 1:
            break

          selection_format = curses.A_STANDOUT if i == selection else curses.A_NORMAL
          popup.addstr(height - 2, draw_x, '[')
          popup.addstr(height - 2, draw_x + 1, option_label, selection_format, curses.A_BOLD)
          popup.addstr(height - 2, draw_x + len(option_label) + 1, ']')

          draw_x -= 1  # space gap between the options

        popup.win.refresh()

        key = nyx.controller.get_controller().key_input()

        if key.match('left'):
          selection = max(0, selection - 1)
        elif key.match('right'):
          selection = min(len(selection_options) - 1, selection + 1)
        elif key.is_selection():
          break

      if selection in (0, 1):
        loaded_torrc, prompt_canceled = tor_config.get_torrc(), False

        try:
          config_location = loaded_torrc.get_config_location()
        except IOError:
          config_location = ''

        if selection == 1:
          # prompts user for a configuration location
          config_location = nyx.popups.input_prompt('Save to (esc to cancel): ', config_location)

          if not config_location:
            prompt_canceled = True

        if not prompt_canceled:
          try:
            tor_config.save_conf(config_location, config_lines)
            msg = 'Saved configuration to %s' % config_location
          except IOError as exc:
            msg = 'Unable to save configuration (%s)' % exc.strerror

          nyx.popups.show_msg(msg, 2)
    finally:
      nyx.popups.finalize()