Exemple #1
0
 def get_default_size():
     config = TerminalConfig()
     font_name = config.get("Display", "font", "Consolas")
     font_size = config.getint("Display", "fontsize", 11)
     cursor = TerminalCursor(None, font_name, font_size)
     (col_size, row_size) = cursor.get_font_metrics()
     return (80 * col_size, 24 * row_size)
Exemple #2
0
 def __init__(self, channel, parent=None):
     '''channel should be a TerminalChannel object.'''
     QtGui.QWidget.__init__(self, parent)
     self.log = log.get_log(self)
     self.config = TerminalConfig()
     self.scroll_bar_width = self.config.getint("Display", 
                                                "scrollbarsize", 14)
     self.screen = ScreenBuffer(parent=self)
     self.scroll_bar = QtGui.QScrollBar(self)
     self.scroll_bar.setCursor(QtCore.Qt.ArrowCursor)
     self.scroll_bar.valueChanged.connect(self.scrollEvent)
     (width, height) = self.screen.get_pixel_size()
     self.resize(width + self.scroll_bar_width, height)
     self.channel = channel 
     self.channel.dataReceived.connect(self.write)
     self.channel.endOfFile.connect(self.close)
     self.sequencer = TerminalEscapeSequencer(self.screen, self.channel)
     self.dirty = False
     cursor = self.screen.get_cursor()
     (self.col_size, self.row_size) = cursor.get_font_metrics()
     self.setCursor(QtCore.Qt.IBeamCursor)
     self.clipboard = QtGui.QApplication.clipboard()
     self.clipboard.dataChanged.connect(self.clipboard_changed)
     self.word_select_mode = False
     self.queue = Queue()
     self.worker_thread = SequencerWorker(self.sequencer, self.screen, 
                                          self.queue)
     self.worker_thread.start()
Exemple #3
0
class SequencerWorker(QtCore.QThread):
    def __init__(self, sequencer, screen, queue):
        QtCore.QThread.__init__(self)
        #self.log = log.get_log(self)
        self.sequencer = sequencer
        self.screen = screen
        self.queue = queue
        self.config = TerminalConfig()
        self.focus_on_output = self.config.getboolean("Cursor",
                                                      "focusonoutput", True)
        self.running = False

    def run(self):
        self.running = True
        while self.running:
            try:
                data = self.queue.get(True, 1)
            except Empty:
                continue
            self.sequencer.process(data)
            self.screen.repaint_dirty_cells()
            if self.focus_on_output:
                cursor = self.screen.get_cursor()
                (row, col) = cursor.get_row_col()
                (width, height) = self.screen.get_size()
                base = self.screen.base
                if row >= base + height:
                    self.screen.scroll_down(row - (base + height) + 1)
            self.queue.task_done()

    def stop(self):
        self.running = False
Exemple #4
0
 def __init__(self):
     #self.log = log.get_log(self)
     self.config = TerminalConfig()
     font_name = self.config.get("Display", "font", "Consolas")
     font_size = self.config.getint("Display", "fontsize", 11)
     self.font = QtGui.QFont(font_name, font_size)
     self.reset()
Exemple #5
0
 def __init__(self, sequencer, screen, queue):
     QtCore.QThread.__init__(self)
     #self.log = log.get_log(self)
     self.sequencer = sequencer
     self.screen = screen
     self.queue = queue
     self.config = TerminalConfig()
     self.focus_on_output = self.config.getboolean("Cursor",
                                                   "focusonoutput", True)
     self.running = False
Exemple #6
0
 def __init__(self, addr, port, name, passwd):
     TerminalChannel.__init__(self)
     self.addr = addr
     self.port = port
     self.name = name
     self.passwd = passwd
     self.connected = False
     self.authentication_error = False
     self.client = paramiko.SSHClient()
     self.client.load_system_host_keys()
     self.client.set_missing_host_key_policy(paramiko.WarningPolicy())
     self.config = TerminalConfig()
Exemple #7
0
 def __init__(self, screen, channel):
     self.log = log.get_log(self)
     self.trace = TraceSequence(fall_through=True) #TODO change fall_through 
     self.screen = screen
     self.channel = channel
     self.__previous_sequence = ""
     self.config = TerminalConfig()
     self.encoding = self.config.get("Sequencer", "encoding", "utf-8")
     self.__sequences = []
     sequences = EscapeSequence.__subclasses__()
     for seq in sequences:
         inst = seq(screen, channel)
         self.__sequences.append(inst)
Exemple #8
0
 def __init__(self, width=80, height=24, parent=None):
     self.log = log.get_log(self)
     self.width = width      # in cells, not pixels
     self.height = height
     self.parent = parent
     self.config = TerminalConfig()
     self.font_name = self.config.get("Display", "font", "Consolas")
     self.font_size = self.config.getint("Display", "fontsize", 11)
     self.cursor = TerminalCursor(self, self.font_name, self.font_size)
     self.scrollback = self.config.getint("Display", "scrollback", 100)
     self.base = 0
     self.alternate_active = False
     self.create_buffer()
     self.create_alternate_buffer()
     self.setup_timer_events()
     (self.col_size, self.row_size) = self.cursor.get_font_metrics()
Exemple #9
0
 def get_default_size():
     config = TerminalConfig()
     scroll_bar_width = config.getint("Display", "scrollbarsize", 14)
     (width, height) = ScreenBuffer.get_default_size()
     width += scroll_bar_width
     return (width, height)
Exemple #10
0
class TerminalWidget(QtGui.QWidget):
    DEBUG_MARK = 1

    # signals
    titleChanged = QtCore.pyqtSignal(str)
    closing = QtCore.pyqtSignal()

    def __init__(self, channel, parent=None):
        '''channel should be a TerminalChannel object.'''
        QtGui.QWidget.__init__(self, parent)
        self.log = log.get_log(self)
        self.config = TerminalConfig()
        self.scroll_bar_width = self.config.getint("Display", 
                                                   "scrollbarsize", 14)
        self.screen = ScreenBuffer(parent=self)
        self.scroll_bar = QtGui.QScrollBar(self)
        self.scroll_bar.setCursor(QtCore.Qt.ArrowCursor)
        self.scroll_bar.valueChanged.connect(self.scrollEvent)
        (width, height) = self.screen.get_pixel_size()
        self.resize(width + self.scroll_bar_width, height)
        self.channel = channel 
        self.channel.dataReceived.connect(self.write)
        self.channel.endOfFile.connect(self.close)
        self.sequencer = TerminalEscapeSequencer(self.screen, self.channel)
        self.dirty = False
        cursor = self.screen.get_cursor()
        (self.col_size, self.row_size) = cursor.get_font_metrics()
        self.setCursor(QtCore.Qt.IBeamCursor)
        self.clipboard = QtGui.QApplication.clipboard()
        self.clipboard.dataChanged.connect(self.clipboard_changed)
        self.word_select_mode = False
        self.queue = Queue()
        self.worker_thread = SequencerWorker(self.sequencer, self.screen, 
                                             self.queue)
        self.worker_thread.start()

    @staticmethod
    def get_default_size():
        config = TerminalConfig()
        scroll_bar_width = config.getint("Display", "scrollbarsize", 14)
        (width, height) = ScreenBuffer.get_default_size()
        width += scroll_bar_width
        return (width, height)

    def close(self):
        self.log.debug("End of file received")
        self.closing.emit()
        if hasattr(self, 'worker_thread') and self.worker_thread.isRunning():
            self.worker_thread.stop()
            self.worker_thread.wait(1500)
        QtGui.QWidget.close(self)

    def write(self, data):
        self.queue.put(data)

    def set_dirty(self):
        '''Means that the display needs to be completely repainted.'''
        if self.end_of_data_block:
            self.update()
            self.dirty = False
        else:
            self.dirty = True

    def event(self, event):
        if event.type() == QtCore.QEvent.KeyPress:
            if event.key() == QtCore.Qt.Key_Tab or \
               event.key() == QtCore.Qt.Key_Backtab:
                self.log.debug("Tab button pressed")
                event.accept()
                self.keyPressEvent(event)
                return True
        return QtGui.QWidget.event(self, event)

    def keyPressEvent(self, event):
        #TODO remove debugging stuff...
        processed = self.screen.process_keypress(event)
        if processed:
            self.log.debug("screen processed keypress: %s" % \
                           processed.replace('\x1b', '\\x1b'))
            self.channel.send_keypress(processed)
        elif event.key() == QtCore.Qt.Key_F5:
            self.repaint()
        elif event.key() == QtCore.Qt.Key_F6:
            self.screen.print_debug()
        elif event.key() == QtCore.Qt.Key_F7:
            self.log.debug("======== Mark %s ========" % self.DEBUG_MARK)
            self.DEBUG_MARK += 1
        elif event.key() == QtCore.Qt.Key_F8:   # insert a breakpoint
            if hasattr(self, "recorder"):
                self.recorder.write('\x1b\x1F')
        elif event.key() == QtCore.Qt.Key_V and \
             event.modifiers() == (QtCore.Qt.ShiftModifier | \
                                   QtCore.Qt.ControlModifier):
            self.channel.send_keypress(str(self.clipboard.text()))
        else:
            self.log.debug("Keypress: %s" % event.text())
            self.channel.send_keypress(event.text())
        self.screen.blink_cursor(False)     # stop blinking while keypress
        #self.scroll_to(-1)

    def mousePressEvent(self, event):
        self.mouse_selection_start = event.pos()
        self.screen.clear_selection()

    def mouseReleaseEvent(self, event):
        if self.mouse_selection_start != event.pos():
            self.clipboard.setText(self.screen.get_selection_text())
        self.word_select_mode = False

    def mouseDoubleClickEvent(self, event):
        self.mouse_select_start = event.pos()
        (top, left) = self.screen.get_cell_from_point(event.pos())
        (first, last) = self.screen.find_word(top, left)
        self.screen.set_selection_start(*first)
        self.screen.set_selection_to_cell(*last)
        self.clipboard.setText(self.screen.get_selection_text())
        self.word_select_mode = True

    def mouseMoveEvent(self, event):
        start = self.mouse_selection_start
        diff = start - event.pos()
        if abs(diff.x()) >= (self.col_size / 2) or \
           abs(diff.y()) >= (self.row_size / 2): 
            (top, left) = self.screen.get_cell_from_point(start)
            cell = self.screen.get_cell(top, left)
            if not cell.has_data or self.word_select_mode:
                num = 1 if diff.y() > 0 else 0
                (top, left) = self.screen.find_word(top, left)[num]
            self.screen.set_selection_start(top, left)
        (top, left) = self.screen.get_cell_from_point(event.pos())
        cell = self.screen.get_cell(top, left)
        if not cell.has_data or self.word_select_mode:
            num = 1 if diff.y() < 0 else 0
            (top, left) = self.screen.find_word(top, left)[num]
        self.screen.set_selection_to_cell(top, left)

    def clipboard_changed(self):
        if self.clipboard.ownsClipboard():
            return
        self.screen.clear_selection()

    def paintEvent(self, event):
        painter = QtGui.QPainter()
        if not painter.begin(self):
            self.log.warning("paintEvent...Unable to paint widget!!!")
            return
        try:
            cursor = self.screen.get_cursor()
            painter.fillRect(event.rect(), cursor.bgcolor)
            self.screen.draw(painter, event)
        except:
            self.log.exception()
            self.screen.print_debug()
        painter.end()

    def resizeEvent(self, event):
        width = self.width()
        height = self.height()
        self.scroll_bar.resize(self.scroll_bar_width, height)
        scroll_bar_width = self.scroll_bar.width()
        self.scroll_bar.move(width - scroll_bar_width, 0)
        self.scroll_bar.setRange(0, self.screen.base) 

        cols = (width - scroll_bar_width) / self.col_size
        rows = height / self.row_size
        #self.log.debug("Resizing to (%s, %s)" % (rows, cols))
        self.screen.resize(cols, rows)
        self.channel.resize(cols, rows)

        cursor = self.screen.get_cursor()
        (row, col) = cursor.get_row_col()
        bottom = self.screen.base + rows
        if row >= bottom:
            self.screen.base += (row - bottom + 1)
        #self.update()

    def wheelEvent(self, event):
        self.scroll_bar.wheelEvent(event)

    def scrollEvent(self, value):
        self.screen.base = value
        self.update()

    def set_scroll_value(self, maximum, value=None):
        self.log.debug("Setting scroll range to (0, %s)" % maximum)
        self.scroll_bar.setRange(0, maximum)
        if value is None:
            value = maximum
        self.log.debug("Setting scroll value to (%s)" % value)
        self.scroll_bar.setValue(value)

    def get_scroll_value(self):
        maximum = self.scroll_bar.maximum()
        value = self.scroll_bar.value()
        return (maximum, value)

    def setWindowTitle(self, title):
        self.titleChanged.emit(title)
        return QtGui.QWidget.setWindowTitle(self, title)
Exemple #11
0
class TerminalCell:
    def __init__(self):
        #self.log = log.get_log(self)
        self.config = TerminalConfig()
        font_name = self.config.get("Display", "font", "Consolas")
        font_size = self.config.getint("Display", "fontsize", 11)
        self.font = QtGui.QFont(font_name, font_size)
        self.reset()

    def __str__(self):
        return self.ch or ""

    def __unicode__(self):
        return unicode(self.ch)

    def draw(self, painter, position, inverse=False):
        bgcolor = self.bgcolor if not inverse else self.fgcolor
        fgcolor = self.fgcolor if not inverse else self.bgcolor
        painter.fillRect(position, bgcolor)
        if self.ch:
            painter.setFont(self.font)
            painter.setPen(fgcolor)
            painter.drawText(position, QtCore.Qt.AlignLeft, self.ch)

    def draw_background(self, painter, position):
        painter.fillRect(position, self.bgcolor)

    def draw_text(self, painter, position, text):
        painter.setFont(self.font)
        painter.setPen(self.fgcolor)
        painter.drawText(position, QtCore.Qt.AlignLeft, text)

    def background_matches(self, cell):
        return cell.bgcolor == self.bgcolor

    def foreground_matches(self, cell):
        return cell.fgcolor == self.fgcolor and \
               cell.font == self.font
               #cell.font.bold() == self.font.bold() and \
               #cell.underline == self.underline

    def reset(self):
        #self.font = QtGui.QFont('Consolas', 11)
        self.fgcolor = QtGui.QColor(255, 255, 255)
        self.bgcolor = QtGui.QColor(0, 0, 0)
        self.ch = ''
        self.font.setBold(False)
        self.underline = False
        self.dirty = False
        self.selected = False
        self.has_data = False

    def set_font(self, font):
        self.font = QtGui.QFont(font)

    def set_fgcolor(self, color):
        self.fgcolor = color

    def set_bgcolor(self, color):
        self.bgcolor = color

    def get_fgcolor(self):
        return self.fgcolor

    def get_bgcolor(self):
        return self.bgcolor

    def set_underline(self, underline=True):
        self.underline = underline

    def set_bold(self, bold=True):
        self.font.setBold(True)

    def set_inverse(self):
        (self.fgcolor, self.bgcolor) = (self.bgcolor, self.fgcolor)

    def set_character(self, ch):
        self.ch = ch
        self.has_data = True

    def get_character(self):
        return self.ch

    def set_dirty(self, dirty=True):
        self.dirty = dirty

    def is_dirty(self):
        return self.dirty

    def toggle_selection(self):
        self.selected = not self.selected
        self.set_inverse()
Exemple #12
0
class ScreenBuffer:
    def __init__(self, width=80, height=24, parent=None):
        self.log = log.get_log(self)
        self.width = width      # in cells, not pixels
        self.height = height
        self.parent = parent
        self.config = TerminalConfig()
        self.font_name = self.config.get("Display", "font", "Consolas")
        self.font_size = self.config.getint("Display", "fontsize", 11)
        self.cursor = TerminalCursor(self, self.font_name, self.font_size)
        self.scrollback = self.config.getint("Display", "scrollback", 100)
        self.base = 0
        self.alternate_active = False
        self.create_buffer()
        self.create_alternate_buffer()
        self.setup_timer_events()
        (self.col_size, self.row_size) = self.cursor.get_font_metrics()

    @staticmethod
    def get_default_size():
        config = TerminalConfig()
        font_name = config.get("Display", "font", "Consolas")
        font_size = config.getint("Display", "fontsize", 11)
        cursor = TerminalCursor(None, font_name, font_size)
        (col_size, row_size) = cursor.get_font_metrics()
        return (80 * col_size, 24 * row_size)

    def setup_timer_events(self):
        self.blink_cursor_timer = QtCore.QTimer()
        self.blink_cursor_timer.timeout.connect(self.blink_cursor_cb)
        self.blink_cursor_active = True
        self.draw_cursor = True
        self.blink_speed = self.config.getint("Cursor", "blinkms", 600)
        self.blink_cursor_timer.start(self.blink_speed)

    def blink_cursor_cb(self):
        if not self.blink_cursor_active:
            return
        self.draw_cursor = not self.draw_cursor
        position = self.cursor.position()
        self.parent.update(position)

    def blink_cursor(self, blink=True):
        self.blink_cursor_active = blink
        self.draw_cursor = True
        position = self.cursor.position()
        self.parent.update(position)
        self.reset_blink_timer()

    def show_cursor(self, show=True):
        self.blink_cursor_active = show
        self.draw_cursor = show
        position = self.cursor.position()
        self.parent.update(position)
        self.reset_blink_timer()

    def reset_blink_timer(self):
        self.blink_cursor_timer.start(self.blink_speed)

    def get_widget(self):
        return self.parent

    def create_buffer(self):
        self.buffer = []
        for row in xrange(0, self.height + self.scrollback):
            self.buffer.append(TerminalRow(self.width, self))
        self.log.debug("Buffer size = %s" % len(self.buffer))

    def create_alternate_buffer(self):
        self.alternate = []
        for row in xrange(0, self.height):
            self.alternate.append(TerminalRow(self.width, self))
        self.set_buffer_scroll_range(0, self.height)

    def insert_row(self, num=1):
        buf = self.get_buffer()
        (row, col) = self.cursor.get_row_col()
        new_rows = [TerminalRow(self.width, self) for x in xrange(0, num)]
        buf.insert(row, '')
        buf[row:row + 1] = new_rows
        self.log.debug("Inserted %s row(s): %s" % (num, len(buf)))

        # delete rows that are greater than the scroll_bottom
        scroll_top = self.get_scroll_top()
        scroll_bottom = self.get_scroll_bottom()
        del buf[scroll_bottom:scroll_bottom + num]
        self.parent.update()

    def insert_cell(self, row, col):
        buf = self.get_buffer()
        buf[row].insert(col, TerminalCell())
        if len(buf[row]) > self.width:
            del buf[row][self.width - 1:]

    def delete_row(self, num=1):
        buf = self.get_buffer()
        (row, col) = self.cursor.get_row_col()
        del buf[row:row + num]
        self.log.debug("Deleted %s row(s): %s" % (num, len(buf)))

        # insert new blank rows
        scroll_bottom = self.get_scroll_bottom() - num
        new_rows = [TerminalRow(self.width, self) for x in xrange(0, num)]
        buf.insert(scroll_bottom, '')
        buf[scroll_bottom:scroll_bottom + 1] = new_rows
        self.parent.update()

    def get_buffer(self):
        if self.alternate_active:
            return self.alternate
        return self.buffer

    def is_alternate_buffer(self):
        return self.alternate_active

    def resize(self, width, height):
        '''Width and height in cells, not pixels. May change this...'''
        #FIXME creating new rows/cells when the height/width changes is
        # very expensive...investigate other methods of resizing the buffers
        # without having to create new objects each time...
        self.log.debug("Resizing screen to %s, %s" % (width, height))
        if height > self.height:
            diff = height - self.height
            for cnt in xrange(0, diff):
                self.buffer.append(TerminalRow(width, self))
            self.log.debug("Resized buffer size = %s" % len(self.buffer))

        if width > self.width:
            self.log.debug("Increasing width of screen buffer to %s." % width)
            for row in xrange(0, height + self.scrollback):
                self.buffer[row].expand(width)

        if height < len(self.alternate):
            diff = len(self.alternate) - height 
            del self.alternate[-diff:]
            self.log.debug("Deleted %s rows from alt buffer, len = %s" % \
                              (diff, len(self.alternate)))
        elif height > len(self.alternate):
            diff = height - len(self.alternate)
            for cnt in xrange(0, diff):
                self.alternate.append(TerminalRow(width, self))
            self.log.debug("Added %s rows to alt buffer, len = %s" % \
                             (diff, len(self.alternate)))

        if width < len(self.alternate[0]):
            diff = len(self.alternate[0]) - width
            for row in xrange(0, len(self.alternate)):
                del self.alternate[row][-diff:]
                self.alternate[row].width = width
            self.log.debug("Deleted %s cols from alt buffer, len = %s" % \
                              (diff, len(self.alternate[0])))
        elif width > len(self.alternate[0]):
            diff = width - len(self.alternate[0])
            for row in xrange(0, len(self.alternate)):
                self.alternate[row].expand(width)
            self.log.debug("Added %s cols to alt buffer, len = %s" % \
                             (diff, len(self.alternate[0])))

        self.width = width
        self.height = height
        self.buffer_scroll_top = 0
        self.buffer_scroll_bottom = height

    def get_cursor(self):
        return self.cursor

    def save_cursor(self):
        self.saved_cursor = self.cursor     # DECSC
        self.cursor = TerminalCursor(self, self.font_name, self.font_size)

    def restore_cursor(self):
        if not hasattr(self, 'saved_cursor'):
            self.log.warning("Trying to restore unsaved cursor!!!")
            return
        cell = self.cursor.get_cell()
        cell.set_dirty()
        self.cursor = self.saved_cursor
        del self.saved_cursor       # DECRC
        cell = self.cursor.get_cell()
        cell.set_dirty()

    def get_size(self):
        return (self.width, self.height)

    def get_pixel_size(self):
        return (self.width * self.col_size, self.height * self.row_size)

    def get_cells_from_rect(self, rect):
        (top, left) = (rect.top(), rect.left())
        (bottom, right) = (rect.bottom(), rect.right())
        x = left / self.col_size
        y = (top / self.row_size)
        y += self.base
        dx = (right / self.col_size) + 1
        dy = (bottom / self.row_size) + 1
        dy += self.base
        buf = self.get_buffer()
        if dy > len(buf):
            dy = len(buf)
        return (y, x, dy, dx)

    def get_cell_from_point(self, point):
        x = point.x()
        y = point.y()
        ret = (self.base + int(y / self.row_size), int(x / self.col_size))
        if ret[0] < 0:
            ret = (0, ret[1])
        if ret[1] < 0:
            ret = (ret[0], 0)
        return ret

    def create_rect_from_cell(self, row, col):
        top = (row - self.base) * self.row_size
        left = col * self.col_size
        return QtCore.QRect(left, top, self.col_size, self.row_size)

    def draw(self, painter, event):
        (top, left, bottom, right) = self.get_cells_from_rect(event.rect())
        self.log.debug("Redrawing (%s,%s) to (%s,%s)" % (top, left, 
                                                         bottom, right))
        row_range = range(top, bottom)
        row_range.reverse()
        buf = self.get_buffer()
        for row in row_range:
            buf[row].draw(painter, row)

        cursor_pos = self.cursor.position()
        if self.draw_cursor and cursor_pos.intersects(event.rect()):
            self.cursor.draw(painter)

    def is_rect_adjacent(r1, r2):
        if 0 <= r1.top() - r2.bottom() <= 1:
            return True
        if 0 <= r2.top() - r1.bottom() <= 1:
            return True
        if 0 <= r1.left() - r2.right() <= 1:
            return True
        if 0 <= r2.left() - r1.right() <= 1:
            return True
        return False
    is_rect_adjacent = staticmethod(is_rect_adjacent)

    def repaint_dirty_cells(self):
        rect = None
        top = self.base
        bottom = top + self.height
        buf_size = self.get_buffer_size()
        if bottom > buf_size:
            bottom = buf_size
        buf = self.get_buffer()
        for row in xrange(top, bottom):
            for col in xrange(0, self.width):
                try:
                    cell = buf[row][col]
                except:
                    self.log.exception()
                    self.log.error("row = %s, col = %s" % (row, col))
                    return
                if not cell.dirty:
                    continue
                cell.dirty = False
                if rect is None:
                    rect = self.create_rect_from_cell(row, col)
                else:
                    new_rect = self.create_rect_from_cell(row, col)
                    if new_rect.intersects(rect) or \
                       ScreenBuffer.is_rect_adjacent(rect, new_rect):
                        rect = rect.unite(new_rect)
                    else:
                        self.parent.update(rect)
                        rect = new_rect
        if rect is not None:
            self.parent.update(rect)

    def set_window_title(self, title):
        if self.parent is None:
            return
        self.parent.setWindowTitle(title)

    def get_cell(self, row, col):
        '''Retrieves a TerminalCell object from this screen buffer.'''
        buf = self.get_buffer()
        try:
            return buf[row][col]
        except IndexError as e:
            self.log.error("IndexError (%s,%s)" % (row, col))
            raise e

    def scroll(self, direction=ScrollDirection.DOWN, times=1):
        if direction == ScrollDirection.DOWN:
            self.scroll_down(times)
        elif direction == ScrollDirection.UP:
            self.scroll_up(times)
        else:
            self.log.error("Unknown scroll direction")

    def scroll_up(self, times=1):
        if self.alternate_active:
            scroll_top = self.get_scroll_top()
            scroll_bottom = self.get_scroll_bottom()
            buf = self.get_buffer()
            first = scroll_top
            last = scroll_bottom - 1

            rows = [TerminalRow(self.width, self) for x in range(0, times)]
            del buf[last:last + times]
            buf.insert(first, '')
            buf[first:first + 1] = rows

            self.parent.update()
            return

        self.base -= times
        if self.base < 0:
            self.base = 0
        self.log.debug("Scrolling screen buffer, base = %s, row = %s" % \
                       (self.base, self.cursor.row))
        self.parent.update()
        self.parent.set_scroll_value(self.base)

    def scroll_down(self, times=1):
        if self.alternate_active:
            scroll_top = self.get_scroll_top()
            scroll_bottom = self.get_scroll_bottom()
            buf = self.get_buffer()
            first = scroll_top - 1
            if first < 0:
                first = 0
            last = scroll_bottom - 1
            if last > len(buf):
                last = len(buf) - 1

            rows = [TerminalRow(self.width, self) for x in xrange(0, times)]
            del buf[first:first + times]
            buf.insert(last, '')
            buf[last:last + 1] = rows

            #repaint_buf = buf[first:last + 1]
            self.parent.update()
            return

        self.base += times
        self.log.debug("Scrolling screen buffer, base = %s, row = %s" % \
                       (self.base, self.cursor.row))
        if (self.base - times) >= self.scrollback:
            self.log.debug("Scrollback exceeded...rolling over buffer.")
            self.base -= times
            del self.buffer[0:times]
            rows = [TerminalRow(self.width, self) for x in xrange(0, times)]
            self.buffer.extend(rows)
        self.parent.set_scroll_value(self.base)
        self.parent.update()

    def set_buffer_scroll_range(self, top, bottom):
        '''Do not use this to set scroll ranges for the widget. 
           This is used by the sequencer to manipulate text in the buffer.
           It does not affect the graphical scroll bar at all.
           top, bottom should not be zero-offset. 
        '''
        self.buffer_scroll_top = top
        self.buffer_scroll_bottom = bottom

    def clear_screen(self):
        if self.alternate_active:
            for row in self.alternate:
                row.reset()
            return
        self.log.debug("Clear Screen")
        #self.scroll_bar.setRange(0, self.base)
        #self.scroll_bar.setValue(self.base)
        (row, col) = self.cursor.get_row_col()
        times = row - self.base
        if self.base >= self.scrollback:
            self.scroll_down(times)
            self.log.debug("Setting cursor to (%s, %s)" % (row, col))
            self.cursor.set_row_col(self.base, col)
        else:
            self.scroll_down(times)
        #self.parent.set_scroll_value(self.base)

    def get_base_row(self):
        return self.base

    def get_buffer_size(self):
        if self.alternate_active:
            return len(self.alternate)
        # buffer size may actually be larger than height + scrollback...
        return self.height + self.scrollback

    def get_scroll_bottom(self):
        if self.alternate_active:
            if hasattr(self, "buffer_scroll_bottom"):
                return self.buffer_scroll_bottom
        screen_bottom = self.base + self.height
        buf_size = self.get_buffer_size()
        if screen_bottom > buf_size:
            return buf_size
        return buf_size

    def get_scroll_top(self):
        if self.alternate_active:
            if hasattr(self, "buffer_scroll_top"):
                return self.buffer_scroll_top
        return self.base

    def set_alternate_buffer(self, alternate=True):
        self.log.debug("Set alternate buffer: %s" % alternate)
        self.alternate_active = alternate
        if alternate:
            self.saved_scroll_values = self.parent.get_scroll_value()
            self.saved_base = self.base
            self.base = 0
        elif hasattr(self, 'saved_scroll_values'):
            self.parent.set_scroll_value(*self.saved_scroll_values)
            del self.saved_scroll_values
            self.base = self.saved_base
            del self.saved_base
        self.parent.update()

    def print_debug(self):
        # this is an expensive function, so we skip it if we are not logging 
        # debug anyway
        if self.log.get_level() > log.Log.DEBUG:
            return
        debug = ""
        buff = self.buffer if not self.alternate_active else self.alternate
        bottom = len(buff)
        for row in xrange(0, bottom):
            debug += u"%03d: " % row
            for col in xrange(0, self.width):
                debug += unicode(buff[row][col]) or u' '
            debug += u"\n"
        self.log.debug("Screen buffer contents:\n%s" % debug)

    def set_cursor_keys(self, application=True):
        self.application_cursor_keys = application
        self.log.debug("Set application cursor keys: %s" % application)

    def process_keypress(self, event):
        sequence = self.__process_key(event)
        if sequence:
            return sequence
        elif hasattr(self, 'application_cursor_keys') and \
           self.application_cursor_keys:
            return self.__process_application_cursor_keys(event)
        else:
            return self.__process_normal_cursor_keys(event)

    def __process_key(self, event):
        if event.key() == QtCore.Qt.Key_PageUp:
            return '\x1b[5~'
        elif event.key() == QtCore.Qt.Key_PageDown:
            return '\x1b[6~'
        return False

    def __process_normal_cursor_keys(self, event):
        if event.key() == QtCore.Qt.Key_Up:
            return '\x1b[A'
        elif event.key() == QtCore.Qt.Key_Down:
            return '\x1b[B'
        elif event.key() == QtCore.Qt.Key_Right:
            return '\x1b[C'
        elif event.key() == QtCore.Qt.Key_Left:
            return '\x1b[D'
        elif event.key() == QtCore.Qt.Key_Home:
            return '\x1b[H'
        elif event.key() == QtCore.Qt.Key_End:
            return '\x1b[F'
        return False
 
    def __process_application_cursor_keys(self, event):
        if event.key() == QtCore.Qt.Key_Up:
            return '\x1bOA'
        elif event.key() == QtCore.Qt.Key_Down:
            return '\x1bOB'
        elif event.key() == QtCore.Qt.Key_Right:
            return '\x1bOC'
        elif event.key() == QtCore.Qt.Key_Left:
            return '\x1bOD'
        elif event.key() == QtCore.Qt.Key_Home:
            return '\x1bOH'
        elif event.key() == QtCore.Qt.Key_End:
            return '\x1bOF'
        return False

    def clear_selection(self):
        if hasattr(self, 'selection_start'):
            del self.selection_start
        buf = self.get_buffer()
        for row in xrange(0, len(buf)):
            for col in xrange(0, len(buf[row])):
                cell = buf[row][col]
                if cell.selected:
                    cell.toggle_selection()
                    rect = self.create_rect_from_cell(row, col)
                    self.parent.update(rect)

    def set_selection_start(self, top, left):
        self.clear_selection()
        cell = self.get_cell(top, left)
        cell.toggle_selection()
        rect = self.create_rect_from_cell(top, left)
        self.parent.update(rect)
        self.selection_start = (top, left)

    def set_selection_to_cell(self, top, left):
        if not hasattr(self, 'selection_start'):
            return
        self.cursor.save_row_col()
        self.cursor.set_row_col(*self.selection_start)
        while self.cursor.get_row_col() != (top, left):
            cell = self.cursor.get_cell()
            if not cell.selected:
                cell.toggle_selection()
                self.parent.update(self.cursor.position())
            if (top == self.selection_start[0] and \
               left >= self.selection_start[1]) or \
               top > self.selection_start[0]:
                self.cursor.advance_column(scroll=False)
            else:
                self.cursor.previous_column(scroll=False)
        cell = self.cursor.get_cell()
        if not cell.selected:
            cell.toggle_selection()
            self.parent.update(self.cursor.position())
        self.cursor.restore_row_col()

    def get_selection_text(self):
        text = ""
        buf = self.get_buffer()
        cell = None
        for row in xrange(0, len(buf)):
            for col in xrange(0, len(buf[row])):
                cell = buf[row][col]
                if cell.selected:
                    text += cell.ch
            if cell and cell.selected and not cell.has_data:
                text += '\n'
        return text

    def find_word(self, top, left):
        word_separators = [' ', ':', '.', ',', '[', ']', '(', ')', '<', 
                           '>', '!', '`']
        cell = self.get_cell(top, left)
        first = (top, left)
        last = (top, left)
        if cell.ch in word_separators:
            return (first, last)

        (row, col) = (top, left)
        is_eol = not self.get_cell(row, col).has_data
        while first > (self.base, 0):
            col -= 1
            if col < 0:
                col = self.width - 1
                row -= 1
            if row < self.base:
                row = self.base
                col = 0
                break
            cell = self.get_cell(row, col)
            if not is_eol and not cell.has_data:
                col = 0
                row += 1
                break
            if cell.ch in word_separators:
                col += 1
                break
            while not cell.has_data and col > 0:
                is_eol = True
                col -= 1
                cell = self.get_cell(row, col)
                if cell.has_data:
                    col += 1
            if is_eol:
                break
        first = (row, col)

        (row, col) = (top, left)
        is_eol = not self.get_cell(row, col).has_data
        while last < (self.base + self.height - 1, self.width - 1):
            col += 1
            cell = self.get_cell(row, col)
            if cell.ch in word_separators:
                col -= 1
                break
            if not cell.has_data and not is_eol:
                col -= 1
                break
            while not cell.has_data and col < self.width - 1:
                col += 1
                cell = self.get_cell(row, col)
            if not cell.has_data:
                break
        last = (row, col)
        return (first, last)
Exemple #13
0
class SSHConnection(TerminalChannel):
    def __init__(self, addr, port, name, passwd):
        TerminalChannel.__init__(self)
        self.addr = addr
        self.port = port
        self.name = name
        self.passwd = passwd
        self.connected = False
        self.authentication_error = False
        self.client = paramiko.SSHClient()
        self.client.load_system_host_keys()
        self.client.set_missing_host_key_policy(paramiko.WarningPolicy())
        self.config = TerminalConfig()

    def connect(self):
        try:
            self.client.connect(self.addr, self.port, self.name, self.passwd)
            self.connected = True
        except paramiko.AuthenticationException:
            self.connected = False
            self.authentication_error = True
        except Exception:
            self.log.exception()
            self.connected = False

    def is_connected(self):
        return self.connected
        
    def __del__(self):
        if hasattr(self, 'channel'):
            self.channel.close()
        if hasattr(self, 'client'):
            self.client.close()

    def resize(self, width, height):
        if not self.connected:
            self.log.error("Trying to resize, but not yet connected.")
            return
        self.channel.resize_pty(width, height)

    def send_keypress(self, key):
        if not self.connected:
            self.log.error("Trying to send keypress, but not yet connected.")
            return
        try:
            #self.log.debug("Sending %s" % key)
            self.channel.send(key)
        except EOFError:
            pass

    class SSHConnectionThread(QtCore.QThread):
        def __init__(self, parent, sock, term):
            QtCore.QThread.__init__(self)
            self.log = log.get_log(self)
            self.parent = parent
            self.sock = sock
            self.term = term

        def run(self):
            self.log.debug("Running connection thread")
            while True:
                data = self.sock.recv(4096)
                if not data:
                    self.log.info("*** EOF ***")
                    self.parent.endOfFile.emit()
                    break
                self.log.debug("Received: %s" % data.replace('\x1b', 
                               '\\x1b'))
                self.parent.dataReceived.emit(data)

    def start_shell(self, term):
        if not self.connected:
            self.log.error("Trying to start shell, but not yet connected.")
            return
        self.log.debug("Starting connection thread")
        term_name = self.config.get("Sequencer", "type", "xterm")
        self.channel = self.client.invoke_shell(term=term_name)
        self.connection_thread = SSHConnection.SSHConnectionThread(self, 
                                                        self.channel, term)
        self.connection_thread.start()
Exemple #14
0
class TerminalEscapeSequencer:
    def __init__(self, screen, channel):
        self.log = log.get_log(self)
        self.trace = TraceSequence(fall_through=True) #TODO change fall_through 
        self.screen = screen
        self.channel = channel
        self.__previous_sequence = ""
        self.config = TerminalConfig()
        self.encoding = self.config.get("Sequencer", "encoding", "utf-8")
        self.__sequences = []
        sequences = EscapeSequence.__subclasses__()
        for seq in sequences:
            inst = seq(screen, channel)
            self.__sequences.append(inst)

    def process(self, data):
        data = unicode(data, encoding=self.encoding)
        if self.__previous_sequence:
            data = self.__previous_sequence + data
            self.__previous_sequence = ""
        idx = 0
        self.log.debug("Processing input len = %s" % len(data))
        while idx < len(data):
            try:
                idx += self._process_text(data[idx:])
            except EncounteredEscapeException as e:
                idx += e.index
                self.log.debug("Found escape at %s" % idx)
                try:
                    idx += self._process_escape(data[idx:])
                except UnsupportedEscapeException as ee:
                    self.log.error(str(ee))
                    idx += ee.index

    def process_until_escape(self, data):
        #data = unicode(data, encoding=self.encoding)
        prev_len = len(self.__previous_sequence)
        if self.__previous_sequence:
            data = self.__previous_sequence + data
            self.log.debug("Prepending incomplete sequence: %s" % \
                           data.replace('\x1b', '\\x1b'))
            self.__previous_sequence = ""
        idx = 0
        try:
            start = idx
            idx += self._process_text(data[idx:])
            self.trace.end("Wrote '%s'" % data[start:idx])
        except EncounteredEscapeException as e:
            idx += e.index
            self.trace.end("Wrote '%s'" % data[start:idx])
            try:
                idx += self._process_escape(data[idx:])
            except UnsupportedEscapeException as ee:
                self.log.error(str(ee))
                idx += ee.index
            e.index = idx - prev_len
            raise e
        return idx

    def _process_escape(self, data):
        self.log.debug("ESC ", data.replace('\x1b', '\\x1b'))
        processed = False
        for escape in self.__sequences:
            m = re.match(escape.MATCH, data)
            if not m:
                self.log.debug("no match %s" % escape.MATCH)
                continue
            self.log.debug("matched %s" % escape.MATCH)
            idx = m.end()
            processed = True
            self.log.debug("Processing escape with: %s" % \
                           escape.__class__.__name__)
            try:
                idx += escape.process(data[idx:], match=m)
            except UnsupportedEscapeException as e:
                self.log.exception()
                idx += e.index       # end of unsupported sequence
            except IncompleteEscapeException:
                self.log.debug("incomplete sequence: %s" % \
                               data.replace('\x1b', '\\x1b'))
                self.__previous_sequence = data
                idx += len(data)     # end of input
            break
        if data == "\x1b":
            self.log.debug("Escape at end of data buffer: %s" % \
                           data.replace('\x1b', '\\x1b'))
            self.__previous_sequence = data
            idx = len(data)
        elif not processed:
            next_escape = data[1:].find('\x1b')
            if next_escape < 0:
                next_escape = len(data)
            else:
                next_escape += 1
            raise UnsupportedEscapeException(next_escape, data[:next_escape])
        return idx

    def _process_text(self, data):
        self.log.debug("TXT")
        cursor = self.screen.get_cursor()
        idx = 0
        for ch in data:
            idx += 1
            if ch == '\x1b':
                raise EncounteredEscapeException(idx - 1)
            if ch == '\n':
                self.log.debug("LF")
                try:
                    cursor.advance_row()
                except ScrollScreenException as e:
                    self.screen.scroll(e.direction)
                continue
            elif ch == '\r':
                self.log.debug("CR")
                cursor.reset_col()
                continue
            elif ch == '\x07':
                self.log.debug("BEL")
                continue
            elif ch == '\x08':
                cursor.left()
                continue
            try:
                cursor.write(ch)
            except ScrollScreenException as e:
                self.screen.scroll(e.direction)
        return idx