Wire up all the new components

This commit is contained in:
Kovid Goyal 2016-10-24 10:17:02 +05:30
parent 6a23dbe1ec
commit 6480f9f156
4 changed files with 169 additions and 46 deletions

View File

@ -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
View 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()

View File

@ -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:

View File

@ -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