Implement changing cursor shape

This commit is contained in:
Kovid Goyal
2016-10-21 06:23:05 +05:30
parent 38dfe12d4e
commit a4bef64de6
6 changed files with 93 additions and 32 deletions

View File

@@ -23,10 +23,25 @@ def to_qcolor(x):
def to_font_size(x):
return max(6, float(x))
def to_cursor_shape(x):
shapes = 'block underline beam'
x = x.lower()
if x not in shapes.split():
raise ValueError('Invalid cursor shape: {} allowed values are {}'.format(x, shapes))
return x
def to_bool(x):
return x.lower() in 'y yes true'.split()
type_map = {
'scrollback_lines': int,
'font_size': to_font_size,
'cursor_opacity': float,
'cursor_shape': to_cursor_shape,
'cursor_blink': to_bool,
}
for name in 'foreground foreground_bold background cursor'.split():
type_map[name] = to_qcolor
@@ -37,7 +52,9 @@ term xterm-kitty
foreground #dddddd
foreground_bold #ffffff
cursor #eeeeee
cursor_opacity 0.8
cursor_opacity 1.0
cursor_shape block
cursor_blink no
background #000000
font_family monospace
font_size 10.0

View File

@@ -27,12 +27,14 @@ get_zeroes.current_size = None
class Cursor:
__slots__ = ("x", "y", "hidden", 'fg', 'bg', 'bold', 'italic', 'reverse', 'strikethrough', 'decoration', 'decoration_fg')
__slots__ = ("x", "y", 'shape', 'blink', "hidden", 'fg', 'bg', 'bold', 'italic', 'reverse', 'strikethrough', 'decoration', 'decoration_fg',)
def __init__(self, x: int=0, y: int=0):
self.x = x
self.y = y
self.hidden = False
self.shape = None
self.blink = None
self.reset_display_attrs()
def reset_display_attrs(self):

View File

@@ -217,14 +217,19 @@ class Screen(QObject):
self.update_screen()
self.select_graphic_rendition(7) # +reverse.
# Make the cursor visible.
if mo.DECTCEM in modes and self.cursor.hidden:
self.cursor.hidden = False
# Show/hide the cursor.
previous, self.cursor.hidden = self.cursor.hidden, mo.DECTCEM not in self.mode
if previous != self.cursor.hidden:
self.cursor_changed(self.cursor)
@property
def in_bracketed_paste_mode(self):
return mo.BRACKETED_PASTE in self.mode
@property
def enable_focus_tracking(self):
return mo.FOCUS_TRACKING in self.mode
def reset_mode(self, *modes, private=False):
"""Resets (disables) a given list of modes.
@@ -255,9 +260,9 @@ class Screen(QObject):
self.update_screen()
self.select_graphic_rendition(27) # -reverse.
# Hide the cursor.
if mo.DECTCEM in modes and not self.cursor.hidden:
self.cursor.hidden = True
# Show/hide the cursor.
previous, self.cursor.hidden = self.cursor.hidden, mo.DECTCEM not in self.mode
if previous != self.cursor.hidden:
self.cursor_changed(self.cursor)
def define_charset(self, code, mode):
@@ -940,6 +945,20 @@ class Screen(QObject):
y -= self.margins.top
self.write_process_input("\x1b[{0};{1}R".format(y, x).encode('ascii'))
def set_cursor_shape(self, mode, secondary=None):
if secondary == ' ':
shape = blink = None
if mode > 0:
blink = bool(mode % 2)
shape = 'block' if mode < 3 else 'underline' if mode < 5 else 'beam' if mode < 7 else None
if shape != self.cursor.shape or blink != self.cursor.blink:
self.cursor.shape, self.cursor.blink = shape, blink
self.cursor_changed(self.cursor)
elif secondary == '"': # DECSCA
pass
else: # DECLL
pass
def numeric_keypad_mode(self):
pass # TODO: Implement this

View File

@@ -179,47 +179,53 @@ class TerminalWidget(QWidget):
r = ev.region()
p = QPainter(self)
try:
self.paint_cursor(p, r)
except Exception:
import traceback
traceback.print_exc()
for lnum, cnum in self.dirty_cells(r):
try:
self.paint_cell(p, cnum, lnum)
except Exception:
pass
if not self.cursor.hidden:
x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, len(self.line_positions), len(self.cell_positions))
cr = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height)
if r.intersects(cr):
self.paint_cell(p, x, y, True)
p.end()
def paint_cursor(self, painter, region):
if self.cursor.hidden:
return
x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, len(self.line_positions), len(self.cell_positions))
def paint_cursor(self, painter, x, y):
r = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height)
if not region.intersects(r):
return
self.last_drew_cursor_at = x, y
line = self.screen.line(y)
colors = line.basic_cell_data(y)[2]
if colors & HAS_BG_MASK:
bg = as_color(colors >> COL_SHIFT, bg_color_table())
if bg is not None:
painter.fillRect(r, QColor(*bg))
cc = self.cursor_color
def width(w=2, vert=True):
dpi = self.logicalDpiX() if vert else self.logicalDpiY()
return int(w * dpi / 72)
if self.hasFocus():
painter.fillRect(r, self.cursor_color)
cs = self.cursor.shape or self.opts.cursor_shape
if cs == 'block':
painter.fillRect(r, cc)
elif cs == 'beam':
w = width(1.5)
painter.fillRect(r.left(), r.top(), w, self.cell_height, cc)
elif cs == 'underline':
y = r.top() + self.font_metrics.underlinePos() + self.baseline_offset
w = width(vert=False)
painter.fillRect(r.left(), min(y, r.bottom() - w), self.cell_width, w, cc)
else:
painter.setPen(QPen(self.cursor_color))
painter.setPen(QPen(cc))
painter.drawRect(r)
def paint_cell(self, painter: QPainter, col: int, row: int) -> None:
def paint_cell(self, painter: QPainter, col: int, row: int, draw_cursor: bool=False) -> None:
line = self.screen.line(row)
ch, attrs, colors = line.basic_cell_data(col)
x, y = self.cell_positions[col], self.line_positions[row]
if colors & HAS_BG_MASK and (col != self.last_drew_cursor_at[0] or row != self.last_drew_cursor_at[1]):
if colors & HAS_BG_MASK:
bg = as_color(colors >> COL_SHIFT, bg_color_table())
if bg is not None:
r = QRect(x, y, self.cell_width, self.cell_height)
painter.fillRect(r, QColor(*bg))
if draw_cursor:
self.paint_cursor(painter, col, row)
if ch == 0 or ch == 32:
# An empty cell
pass
@@ -247,9 +253,19 @@ class TerminalWidget(QWidget):
text = c.text(c.Selection)
if text:
text = text.encode('utf-8')
if self.screen.in_bracketed_paste_mode():
if self.screen.in_bracketed_paste_mode:
text = mo.BRACKETED_PASTE_START + text + mo.BRACKETED_PASTE_END
self.send_data_to_child.emit(text)
ev.accept()
return
return QWidget.mousePressEvent(self, ev)
def focusInEvent(self, ev):
if self.screen.enable_focus_tracking:
self.send_data_to_child.emit(b'\x1b[I')
return QWidget.focusInEvent(self, ev)
def focusOutEvent(self, ev):
if self.screen.enable_focus_tracking:
self.send_data_to_child.emit(b'\x1b[O')
return QWidget.focusOutEvent(self, ev)

View File

@@ -157,3 +157,9 @@ DECSTBM = b"r"
#: *Horizontal position adjust*: Same as :data:`CHA`.
HPA = b"'"
# Misc sequences
#: Change cursor shape/blink
DECSCUSR = b'q'

View File

@@ -119,7 +119,8 @@ class Stream(object):
esc.SGR: "select_graphic_rendition",
esc.DSR: "report_device_status",
esc.DECSTBM: "set_margins",
esc.HPA: "cursor_to_column"
esc.HPA: "cursor_to_column",
esc.DECSCUSR: 'set_cursor_shape',
}
#: A set of all events dispatched by the stream.