Wire up all the new components
This commit is contained in:
parent
6a23dbe1ec
commit
6480f9f156
@ -11,6 +11,11 @@ from threading import Thread
|
||||
from queue import Queue, Empty
|
||||
|
||||
import glfw
|
||||
from pyte.streams import Stream, DebugStream
|
||||
|
||||
from .char_grid import CharGrid
|
||||
from .screen import Screen
|
||||
from .tracker import ChangeTracker
|
||||
from .utils import resize_pty, create_pty
|
||||
|
||||
|
||||
@ -27,24 +32,36 @@ class Boss(Thread):
|
||||
|
||||
daemon = True
|
||||
shutting_down = False
|
||||
pending_title_change = pending_icon_change = None
|
||||
pending_color_changes = {}
|
||||
|
||||
def __init__(self, window, opts, args):
|
||||
Thread.__init__(self, name='ChildMonitor')
|
||||
self.window = window
|
||||
self.write_queue = Queue()
|
||||
self.window, self.opts = window, opts
|
||||
self.action_queue = Queue()
|
||||
self.read_wakeup_fd, self.write_wakeup_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
|
||||
self.tracker = ChangeTracker(self.mark_dirtied)
|
||||
self.char_grid = CharGrid(opts)
|
||||
self.screen = Screen(self.opts, self.tracker, self)
|
||||
sclass = DebugStream if args.dump_commands else Stream
|
||||
self.stream = sclass(self.screen)
|
||||
self.write_buf = memoryview(b'')
|
||||
self.child_fd = create_pty()[0]
|
||||
self.signal_fd = handle_unix_signals()
|
||||
self.read_wakeup_fd, self.write_wakeup_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
|
||||
self.readers = [self.child_fd, self.signal_fd, self.read_wakeup_fd]
|
||||
self.writers = [self.child_fd]
|
||||
resize_pty(80, 24)
|
||||
|
||||
def on_window_resize(self, window, w, h):
|
||||
pass
|
||||
self.char_grid.on_resize(window, w, h)
|
||||
|
||||
def render(self):
|
||||
pass
|
||||
if self.pending_title_change is not None:
|
||||
glfw.glfwSetWindowTitle(self.window, self.pending_title_change)
|
||||
self.pending_title_change = None
|
||||
if self.pending_icon_change is not None:
|
||||
self.pending_icon_change = None # TODO: Implement this
|
||||
self.char_grid.render()
|
||||
|
||||
def wakeup(self):
|
||||
os.write(self.write_wakeup_fd, b'1')
|
||||
@ -54,14 +71,12 @@ class Boss(Thread):
|
||||
os.read(self.read_wakeup_fd, 1024)
|
||||
except (EnvironmentError, BlockingIOError):
|
||||
pass
|
||||
buf = b''
|
||||
while True:
|
||||
while not self.shutting_down:
|
||||
try:
|
||||
buf += self.write_queue.get_nowait()
|
||||
func, args = self.action_queue.get_nowait()
|
||||
except Empty:
|
||||
break
|
||||
if buf:
|
||||
self.write_buf = memoryview(self.write_buf.tobytes() + buf)
|
||||
getattr(self, func)(*args)
|
||||
|
||||
def run(self):
|
||||
while not self.shutting_down:
|
||||
@ -100,10 +115,10 @@ class Boss(Thread):
|
||||
return
|
||||
except EnvironmentError:
|
||||
data = b''
|
||||
if not data:
|
||||
# EOF
|
||||
if data:
|
||||
self.stream.feed(data)
|
||||
else: # EOF
|
||||
self.shutdown()
|
||||
return
|
||||
|
||||
def write_ready(self):
|
||||
if not self.shutting_down:
|
||||
@ -114,5 +129,35 @@ class Boss(Thread):
|
||||
self.write_buf = self.write_buf[n:]
|
||||
|
||||
def write_to_child(self, data):
|
||||
self.write_queue.put(data)
|
||||
if data:
|
||||
self.action_queue.put(('queue_write', data))
|
||||
self.wakeup()
|
||||
|
||||
def queue_write(self, data):
|
||||
self.write_buf = memoryview(self.write_buf.tobytes() + data)
|
||||
|
||||
def mark_dirtied(self):
|
||||
self.action_queue.put(('update_screen', ()))
|
||||
self.wakeup()
|
||||
|
||||
def update_screen(self):
|
||||
changes = self.tracker.consolidate_changes()
|
||||
self.char_grid.update_screen(changes)
|
||||
glfw.glfwPostEmptyEvent()
|
||||
|
||||
def title_changed(self, new_title):
|
||||
self.pending_title_change = new_title
|
||||
glfw.glfwPostEmptyEvent()
|
||||
|
||||
def icon_changed(self, new_icon):
|
||||
self.pending_icon_change = new_icon
|
||||
glfw.glfwPostEmptyEvent()
|
||||
|
||||
def change_default_color(self, which, value):
|
||||
self.pending_color_changes[which] = value
|
||||
self.action_queue.put(('change_colors', ()))
|
||||
|
||||
def change_colors(self):
|
||||
self.char_grid.change_colors(self.pending_color_changes)
|
||||
self.pending_color_changes = {}
|
||||
glfw.glfwPostEmptyEvent()
|
||||
|
||||
89
kitty/char_grid.py
Normal file
89
kitty/char_grid.py
Normal file
@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from threading import Lock
|
||||
|
||||
from .config import build_ansi_color_tables, to_color
|
||||
from .fonts import load_font_family
|
||||
|
||||
from OpenGL.arrays import ArrayDatatype
|
||||
from OpenGL.GL import (
|
||||
GL_ARRAY_BUFFER,
|
||||
GL_COLOR_BUFFER_BIT, GL_COMPILE_STATUS,
|
||||
GL_FALSE, GL_FLOAT, GL_FRAGMENT_SHADER,
|
||||
GL_LINK_STATUS, GL_RENDERER,
|
||||
GL_SHADING_LANGUAGE_VERSION,
|
||||
GL_STATIC_DRAW, GL_TEXTURE_2D, GL_TRIANGLES,
|
||||
GL_TRUE, GL_UNPACK_ALIGNMENT, GL_VENDOR, GL_VERSION,
|
||||
GL_VERTEX_SHADER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T,
|
||||
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER,
|
||||
GL_LINEAR, GL_RGB, GL_RGBA, GL_UNSIGNED_BYTE, GL_TEXTURE0,
|
||||
GL_REPEAT,
|
||||
glActiveTexture, glAttachShader,
|
||||
glBindBuffer, glBindTexture, glBindVertexArray,
|
||||
glBufferData, glClear, glClearColor,
|
||||
glCompileShader, glCreateProgram,
|
||||
glCreateShader, glDeleteProgram,
|
||||
glDeleteShader, glDrawArrays,
|
||||
glEnableVertexAttribArray, glGenBuffers, glGenTextures,
|
||||
glGenVertexArrays, glGetAttribLocation,
|
||||
glGetProgramInfoLog, glGetProgramiv,
|
||||
glGetShaderInfoLog, glGetShaderiv, glGetString,
|
||||
glGetUniformLocation, glLinkProgram, glPixelStorei,
|
||||
glShaderSource, glTexImage2D, glTexParameteri, glUniform1i, glUseProgram,
|
||||
glVertexAttribPointer, glViewport)
|
||||
|
||||
|
||||
class CharGrid:
|
||||
|
||||
def __init__(self, opts):
|
||||
self.apply_opts(opts)
|
||||
self.lock = Lock()
|
||||
|
||||
def apply_clear_color(self):
|
||||
bg = self.default_bg
|
||||
glClearColor(bg[0]/255, bg[1]/255, bg[2]/255, 1)
|
||||
|
||||
def apply_opts(self, opts):
|
||||
self.opts = opts
|
||||
build_ansi_color_tables(opts)
|
||||
self.opts = opts
|
||||
self.default_bg = self.original_bg = opts.background
|
||||
self.default_fg = self.original_fg = opts.foreground
|
||||
self.base_font_family = load_font_family(opts.font_family)
|
||||
self.font_size = int(opts.font_size * 64)
|
||||
self.apply_clear_color()
|
||||
|
||||
def on_resize(self, window, w, h):
|
||||
glViewport(0, 0, w, h)
|
||||
self.do_layout(w, h)
|
||||
|
||||
def do_layout(self, w, h):
|
||||
pass
|
||||
|
||||
def redraw(self):
|
||||
pass
|
||||
|
||||
def render(self):
|
||||
with self.lock:
|
||||
glClear(GL_COLOR_BUFFER_BIT)
|
||||
|
||||
def change_colors(self, changes):
|
||||
dirtied = False
|
||||
for which, val in changes.items():
|
||||
if which in ('fg', 'bg'):
|
||||
if not val:
|
||||
setattr(self, 'default_' + which, getattr(self, 'original_' + which))
|
||||
dirtied = True
|
||||
else:
|
||||
val = to_color(val)
|
||||
if val is not None:
|
||||
setattr(self, 'default_' + which, val)
|
||||
dirtied = True
|
||||
if dirtied:
|
||||
self.apply_clear_color()
|
||||
self.redraw()
|
||||
|
||||
def update_screen(self, changes):
|
||||
self.redraw()
|
||||
@ -8,8 +8,6 @@ import unicodedata
|
||||
from collections import deque, namedtuple
|
||||
from typing import Sequence
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
|
||||
from pyte import charsets as cs, graphics as g, modes as mo
|
||||
from .data_types import Line, Cursor, rewrap_lines
|
||||
from .utils import wcwidth, is_simple_string, sanitize_title
|
||||
@ -40,23 +38,21 @@ def wrap_cursor_position(x, y, lines, columns):
|
||||
return x, y
|
||||
|
||||
|
||||
class Screen(QObject):
|
||||
class Screen:
|
||||
"""
|
||||
See standard ECMA-48, Section 6.1.1 http://www.ecma-international.org/publications/standards/Ecma-048.htm
|
||||
for a description of the presentational component, implemented by ``Screen``.
|
||||
"""
|
||||
|
||||
title_changed = pyqtSignal(object)
|
||||
icon_changed = pyqtSignal(object)
|
||||
write_to_child = pyqtSignal(object)
|
||||
change_default_color = pyqtSignal(object, object)
|
||||
tracker_callbacks = 'cursor_changed cursor_position_changed update_screen update_line_range update_cell_range line_added_to_history'.split()
|
||||
callbacks = 'title_changed icon_changed write_to_child change_default_color'.split()
|
||||
_notify_cursor_position = True
|
||||
|
||||
def __init__(self, opts, tracker, columns: int=80, lines: int=24, parent=None):
|
||||
QObject.__init__(self, parent)
|
||||
self.write_process_input = self.write_to_child.emit
|
||||
for attr in 'cursor_changed cursor_position_changed update_screen update_line_range update_cell_range line_added_to_history'.split():
|
||||
def __init__(self, opts, tracker, callbacks, columns: int=80, lines: int=24):
|
||||
for attr in self.tracker_callbacks:
|
||||
setattr(self, attr, getattr(tracker, attr))
|
||||
for attr in self.callbacks:
|
||||
setattr(self, attr, getattr(callbacks, attr))
|
||||
self.main_savepoints, self.alt_savepoints = deque(), deque()
|
||||
self.savepoints = self.main_savepoints
|
||||
self.columns = columns
|
||||
@ -134,8 +130,8 @@ class Screen(QObject):
|
||||
self.cursor = Cursor(0, 0)
|
||||
self.cursor_changed(self.cursor)
|
||||
self.cursor_position()
|
||||
self.change_default_color.emit('fg', None)
|
||||
self.change_default_color.emit('bg', None)
|
||||
self.change_default_color('fg', None)
|
||||
self.change_default_color('bg', None)
|
||||
if notify:
|
||||
self.update_screen()
|
||||
|
||||
@ -435,14 +431,14 @@ class Screen(QObject):
|
||||
|
||||
.. note:: This is an XTerm extension supported by the Linux terminal.
|
||||
"""
|
||||
self.title_changed.emit(sanitize_title(self._decode(param)))
|
||||
self.title_changed(sanitize_title(self._decode(param)))
|
||||
|
||||
def set_icon_name(self, param):
|
||||
"""Sets icon name.
|
||||
|
||||
.. note:: This is an XTerm extension supported by the Linux terminal.
|
||||
"""
|
||||
self.icon_changed.emit(sanitize_title(self._decode(param)))
|
||||
self.icon_changed(sanitize_title(self._decode(param)))
|
||||
|
||||
def carriage_return(self):
|
||||
"""Move the cursor to the beginning of the current line."""
|
||||
@ -951,13 +947,13 @@ class Screen(QObject):
|
||||
# If you implement xterm keycode querying
|
||||
# http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions
|
||||
# you can enable this.
|
||||
self.write_process_input(b'\x1b[>1;4600;0c')
|
||||
self.write_to_child(b'\x1b[>1;4600;0c')
|
||||
else:
|
||||
# xterm gives: [[?64;1;2;6;9;15;18;21;22c
|
||||
# use the simpler vte response, since we dont support
|
||||
# windowing/horizontal scrolling etc.
|
||||
# [[?64;1;2;6;9;15;18;21;22c
|
||||
self.write_process_input(b"\x1b[?62c")
|
||||
self.write_to_child(b"\x1b[?62c")
|
||||
|
||||
def report_device_status(self, mode):
|
||||
"""Reports terminal status or cursor position.
|
||||
@ -968,7 +964,7 @@ class Screen(QObject):
|
||||
.. versionadded:: 0.5.0
|
||||
"""
|
||||
if mode == 5: # Request for terminal status.
|
||||
self.write_process_input(b"\x1b[0n")
|
||||
self.write_to_child(b"\x1b[0n")
|
||||
elif mode == 6: # Request for cursor position.
|
||||
x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, self.lines, self.columns)
|
||||
x, y = x + 1, y + 1
|
||||
@ -976,7 +972,7 @@ class Screen(QObject):
|
||||
# "Origin mode (DECOM) selects line numbering."
|
||||
if mo.DECOM in self.mode:
|
||||
y -= self.margins.top
|
||||
self.write_process_input("\x1b[{0};{1}R".format(y, x).encode('ascii'))
|
||||
self.write_to_child("\x1b[{0};{1}R".format(y, x).encode('ascii'))
|
||||
|
||||
def set_cursor_shape(self, mode, secondary=None):
|
||||
if secondary == ' ':
|
||||
@ -1002,9 +998,9 @@ class Screen(QObject):
|
||||
def handle_val(val, param=None):
|
||||
val %= 100
|
||||
if val == 10: # foreground
|
||||
self.change_default_color.emit('fg', param)
|
||||
self.change_default_color('fg', param)
|
||||
elif val == 11: # background
|
||||
self.change_default_color.emit('bg', param)
|
||||
self.change_default_color('bg', param)
|
||||
elif val == 12: # cursor color
|
||||
old, self.cursor.color = self.cursor.color, param
|
||||
if old != self.cursor.color:
|
||||
|
||||
@ -6,8 +6,6 @@ from collections import defaultdict
|
||||
from operator import itemgetter
|
||||
from typing import Set, Tuple, Iterator
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, Qt
|
||||
|
||||
from .data_types import Cursor
|
||||
|
||||
|
||||
@ -27,15 +25,11 @@ def merge_ranges(ranges: Set[Tuple[int]]) -> Iterator[Tuple[int]]:
|
||||
yield low, high # end the final run
|
||||
|
||||
|
||||
class ChangeTracker(QObject):
|
||||
class ChangeTracker:
|
||||
|
||||
dirtied = pyqtSignal(object)
|
||||
mark_dirtied = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QObject.__init__(self, parent)
|
||||
def __init__(self, mark_dirtied):
|
||||
self.reset()
|
||||
self.mark_dirtied.connect(self.consolidate_changes, type=Qt.QueuedConnection)
|
||||
self.mark_dirtied = mark_dirtied
|
||||
|
||||
def reset(self):
|
||||
self._dirty = False
|
||||
@ -48,7 +42,7 @@ class ChangeTracker(QObject):
|
||||
def dirty(self):
|
||||
if not self._dirty:
|
||||
self._dirty = True
|
||||
self.mark_dirtied.emit()
|
||||
self.mark_dirtied()
|
||||
|
||||
def cursor_changed(self, cursor: Cursor) -> None:
|
||||
self.changed_cursor = cursor
|
||||
@ -83,5 +77,4 @@ class ChangeTracker(QObject):
|
||||
changes = {'screen': self.screen_changed, 'cursor': self.changed_cursor, 'lines': self.changed_lines,
|
||||
'cells': cc, 'history_line_added_count': self.history_line_added_count}
|
||||
self.reset()
|
||||
self.dirtied.emit(changes)
|
||||
return changes
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user