mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-27 18:51:41 +02:00
Implement changing cursor shape
This commit is contained in:
@@ -23,10 +23,25 @@ def to_qcolor(x):
|
|||||||
def to_font_size(x):
|
def to_font_size(x):
|
||||||
return max(6, float(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 = {
|
type_map = {
|
||||||
'scrollback_lines': int,
|
'scrollback_lines': int,
|
||||||
'font_size': to_font_size,
|
'font_size': to_font_size,
|
||||||
'cursor_opacity': float,
|
'cursor_opacity': float,
|
||||||
|
'cursor_shape': to_cursor_shape,
|
||||||
|
'cursor_blink': to_bool,
|
||||||
}
|
}
|
||||||
for name in 'foreground foreground_bold background cursor'.split():
|
for name in 'foreground foreground_bold background cursor'.split():
|
||||||
type_map[name] = to_qcolor
|
type_map[name] = to_qcolor
|
||||||
@@ -37,7 +52,9 @@ term xterm-kitty
|
|||||||
foreground #dddddd
|
foreground #dddddd
|
||||||
foreground_bold #ffffff
|
foreground_bold #ffffff
|
||||||
cursor #eeeeee
|
cursor #eeeeee
|
||||||
cursor_opacity 0.8
|
cursor_opacity 1.0
|
||||||
|
cursor_shape block
|
||||||
|
cursor_blink no
|
||||||
background #000000
|
background #000000
|
||||||
font_family monospace
|
font_family monospace
|
||||||
font_size 10.0
|
font_size 10.0
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ get_zeroes.current_size = None
|
|||||||
|
|
||||||
class Cursor:
|
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):
|
def __init__(self, x: int=0, y: int=0):
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
self.hidden = False
|
self.hidden = False
|
||||||
|
self.shape = None
|
||||||
|
self.blink = None
|
||||||
self.reset_display_attrs()
|
self.reset_display_attrs()
|
||||||
|
|
||||||
def reset_display_attrs(self):
|
def reset_display_attrs(self):
|
||||||
|
|||||||
@@ -217,14 +217,19 @@ class Screen(QObject):
|
|||||||
self.update_screen()
|
self.update_screen()
|
||||||
self.select_graphic_rendition(7) # +reverse.
|
self.select_graphic_rendition(7) # +reverse.
|
||||||
|
|
||||||
# Make the cursor visible.
|
# Show/hide the cursor.
|
||||||
if mo.DECTCEM in modes and self.cursor.hidden:
|
previous, self.cursor.hidden = self.cursor.hidden, mo.DECTCEM not in self.mode
|
||||||
self.cursor.hidden = False
|
if previous != self.cursor.hidden:
|
||||||
self.cursor_changed(self.cursor)
|
self.cursor_changed(self.cursor)
|
||||||
|
|
||||||
|
@property
|
||||||
def in_bracketed_paste_mode(self):
|
def in_bracketed_paste_mode(self):
|
||||||
return mo.BRACKETED_PASTE in self.mode
|
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):
|
def reset_mode(self, *modes, private=False):
|
||||||
"""Resets (disables) a given list of modes.
|
"""Resets (disables) a given list of modes.
|
||||||
|
|
||||||
@@ -255,9 +260,9 @@ class Screen(QObject):
|
|||||||
self.update_screen()
|
self.update_screen()
|
||||||
self.select_graphic_rendition(27) # -reverse.
|
self.select_graphic_rendition(27) # -reverse.
|
||||||
|
|
||||||
# Hide the cursor.
|
# Show/hide the cursor.
|
||||||
if mo.DECTCEM in modes and not self.cursor.hidden:
|
previous, self.cursor.hidden = self.cursor.hidden, mo.DECTCEM not in self.mode
|
||||||
self.cursor.hidden = True
|
if previous != self.cursor.hidden:
|
||||||
self.cursor_changed(self.cursor)
|
self.cursor_changed(self.cursor)
|
||||||
|
|
||||||
def define_charset(self, code, mode):
|
def define_charset(self, code, mode):
|
||||||
@@ -940,6 +945,20 @@ class Screen(QObject):
|
|||||||
y -= self.margins.top
|
y -= self.margins.top
|
||||||
self.write_process_input("\x1b[{0};{1}R".format(y, x).encode('ascii'))
|
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):
|
def numeric_keypad_mode(self):
|
||||||
pass # TODO: Implement this
|
pass # TODO: Implement this
|
||||||
|
|
||||||
|
|||||||
@@ -179,47 +179,53 @@ class TerminalWidget(QWidget):
|
|||||||
r = ev.region()
|
r = ev.region()
|
||||||
p = QPainter(self)
|
p = QPainter(self)
|
||||||
|
|
||||||
try:
|
|
||||||
self.paint_cursor(p, r)
|
|
||||||
except Exception:
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
for lnum, cnum in self.dirty_cells(r):
|
for lnum, cnum in self.dirty_cells(r):
|
||||||
try:
|
try:
|
||||||
self.paint_cell(p, cnum, lnum)
|
self.paint_cell(p, cnum, lnum)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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):
|
def paint_cursor(self, painter, x, y):
|
||||||
if self.cursor.hidden:
|
|
||||||
return
|
|
||||||
x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, len(self.line_positions), len(self.cell_positions))
|
|
||||||
r = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height)
|
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
|
self.last_drew_cursor_at = x, y
|
||||||
line = self.screen.line(y)
|
cc = self.cursor_color
|
||||||
colors = line.basic_cell_data(y)[2]
|
|
||||||
if colors & HAS_BG_MASK:
|
def width(w=2, vert=True):
|
||||||
bg = as_color(colors >> COL_SHIFT, bg_color_table())
|
dpi = self.logicalDpiX() if vert else self.logicalDpiY()
|
||||||
if bg is not None:
|
return int(w * dpi / 72)
|
||||||
painter.fillRect(r, QColor(*bg))
|
|
||||||
if self.hasFocus():
|
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:
|
else:
|
||||||
painter.setPen(QPen(self.cursor_color))
|
painter.setPen(QPen(cc))
|
||||||
painter.drawRect(r)
|
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)
|
line = self.screen.line(row)
|
||||||
ch, attrs, colors = line.basic_cell_data(col)
|
ch, attrs, colors = line.basic_cell_data(col)
|
||||||
x, y = self.cell_positions[col], self.line_positions[row]
|
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())
|
bg = as_color(colors >> COL_SHIFT, bg_color_table())
|
||||||
if bg is not None:
|
if bg is not None:
|
||||||
r = QRect(x, y, self.cell_width, self.cell_height)
|
r = QRect(x, y, self.cell_width, self.cell_height)
|
||||||
painter.fillRect(r, QColor(*bg))
|
painter.fillRect(r, QColor(*bg))
|
||||||
|
if draw_cursor:
|
||||||
|
self.paint_cursor(painter, col, row)
|
||||||
if ch == 0 or ch == 32:
|
if ch == 0 or ch == 32:
|
||||||
# An empty cell
|
# An empty cell
|
||||||
pass
|
pass
|
||||||
@@ -247,9 +253,19 @@ class TerminalWidget(QWidget):
|
|||||||
text = c.text(c.Selection)
|
text = c.text(c.Selection)
|
||||||
if text:
|
if text:
|
||||||
text = text.encode('utf-8')
|
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
|
text = mo.BRACKETED_PASTE_START + text + mo.BRACKETED_PASTE_END
|
||||||
self.send_data_to_child.emit(text)
|
self.send_data_to_child.emit(text)
|
||||||
ev.accept()
|
ev.accept()
|
||||||
return
|
return
|
||||||
return QWidget.mousePressEvent(self, ev)
|
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`.
|
#: *Horizontal position adjust*: Same as :data:`CHA`.
|
||||||
HPA = b"'"
|
HPA = b"'"
|
||||||
|
|
||||||
|
|
||||||
|
# Misc sequences
|
||||||
|
|
||||||
|
#: Change cursor shape/blink
|
||||||
|
DECSCUSR = b'q'
|
||||||
|
|||||||
@@ -119,7 +119,8 @@ class Stream(object):
|
|||||||
esc.SGR: "select_graphic_rendition",
|
esc.SGR: "select_graphic_rendition",
|
||||||
esc.DSR: "report_device_status",
|
esc.DSR: "report_device_status",
|
||||||
esc.DECSTBM: "set_margins",
|
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.
|
#: A set of all events dispatched by the stream.
|
||||||
|
|||||||
Reference in New Issue
Block a user