mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-13 03:59:23 +02:00
Implement changing cursor shape
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user