diff --git a/kitty/boss.py b/kitty/boss.py index 5773b23c9..92f40502a 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -35,14 +35,14 @@ class Boss(Thread): pending_title_change = pending_icon_change = None pending_color_changes = {} - def __init__(self, window, opts, args): + def __init__(self, window, window_width, window_height, opts, args): Thread.__init__(self, name='ChildMonitor') 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) + self.char_grid = CharGrid(self.screen, opts, window_width, window_height) sclass = DebugStream if args.dump_commands else Stream self.stream = sclass(self.screen) self.write_buf = memoryview(b'') @@ -53,7 +53,23 @@ class Boss(Thread): resize_pty(80, 24) def on_window_resize(self, window, w, h): - self.char_grid.on_resize(window, w, h) + self.queue_action(self.resize_screen, w, h) + + def resize_screen(self, w, h): + self.char_grid.resize_screen(w, h) + + def apply_opts(self, opts): + self.opts = opts + self.queue_action(self.apply_opts_to_screen) + + def apply_opts_to_screen(self): + self.screen.apply_opts(self.opts) + self.char_grid.apply_opts(self.opts) + self.char_grid.dirty_everything() + + def queue_action(self, func, *args): + self.action_queue.put((func, args)) + self.wakeup() def render(self): if self.pending_title_change is not None: @@ -76,7 +92,7 @@ class Boss(Thread): func, args = self.action_queue.get_nowait() except Empty: break - getattr(self, func)(*args) + func(*args) def run(self): while not self.shutting_down: @@ -130,15 +146,13 @@ class Boss(Thread): def write_to_child(self, data): if data: - self.action_queue.put(('queue_write', data)) - self.wakeup() + self.queue_action(self.queue_write, data) 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() + self.queue_action(self.update_screen) def update_screen(self): changes = self.tracker.consolidate_changes() @@ -155,7 +169,7 @@ class Boss(Thread): def change_default_color(self, which, value): self.pending_color_changes[which] = value - self.action_queue.put(('change_colors', ())) + self.queue_action(self.change_colors) def change_colors(self): self.char_grid.change_colors(self.pending_color_changes) diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 0b5e3e2ff..997e84287 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -7,66 +7,62 @@ from threading import Lock from .config import build_ansi_color_tables, to_color from .fonts import set_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) +import OpenGL.GL as gl class CharGrid: - def __init__(self, opts): + def __init__(self, screen, opts, window_width, window_height): + self.width, self.height = window_width, window_height + self.screen = screen self.apply_opts(opts) - self.lock = Lock() + self.dirty_everything() + self.default_bg, self.default_fg = self.original_bg, self.original_fg + self.resize_lock = Lock() + self.apply_resize_to_screen(self.width, self.height) - def apply_clear_color(self): - bg = self.default_bg - glClearColor(bg[0]/255, bg[1]/255, bg[2]/255, 1) + def dirty_everything(self): + self.cell_resize_pending = True + self.clear_color_changed = True + self.resize_pending = self.width, self.height 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.original_bg = opts.background + self.original_fg = opts.foreground self.cell_width, self.cell_height = set_font_family(opts.font_family, opts.font_size) - self.apply_clear_color() - def on_resize(self, window, w, h): - glViewport(0, 0, w, h) - self.do_layout(w, h) + def apply_resize_to_screen(self, w, h): + cells_per_line = w // self.cell_width + lines_per_screen = h // self.cell_height + self.screen.resize(lines_per_screen, cells_per_line) + + def resize_screen(self, w, h): + ' Screen was resized by the user (called in non-UI thread) ' + with self.resize_lock: + self.apply_resize_to_screen(w, h) + self.resize_pending = w, h def do_layout(self, w, h): - pass - - def redraw(self): - pass + self.width, self.height = w, h + self.cells_per_line = w // self.cell_width + self.lines_per_screen = h // self.cell_height + if self.cell_resize_pending: + self.cell_resize_pending = False def render(self): - with self.lock: - glClear(GL_COLOR_BUFFER_BIT) + with self.resize_lock: + if self.resize_pending: + self.do_layout(*self.resize_pending) + gl.glViewport(0, 0, self.width, self.height) + self.resize_pending = None + if self.clear_color_changed: + bg = self.default_bg + self.clear_color_changed = False + gl.glClearColor(bg[0]/255, bg[1]/255, bg[2]/255, 1) + gl.glClear(gl.GL_COLOR_BUFFER_BIT) def change_colors(self, changes): dirtied = False @@ -81,8 +77,9 @@ class CharGrid: setattr(self, 'default_' + which, val) dirtied = True if dirtied: - self.apply_clear_color() - self.redraw() + self.clear_color_changed = True + self.update_screen() - def update_screen(self, changes): - self.redraw() + def update_screen(self, changes=None): + if changes is None: + changes = {'screen': True} diff --git a/kitty/constants.py b/kitty/constants.py index 3d7a85c58..53afa7a0c 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -3,7 +3,7 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal import os -from PyQt5.QtCore import QStandardPaths +import threading appname = 'kitty' version = (0, 1, 0) @@ -15,10 +15,7 @@ def _get_config_dir(): if 'KITTY_CONFIG_DIRECTORY' in os.environ: return os.path.abspath(os.path.expanduser(os.environ['VISE_CONFIG_DIRECTORY'])) - candidate = QStandardPaths.writableLocation(QStandardPaths.ConfigLocation) - if not candidate: - raise RuntimeError( - 'Failed to find path for application config directory') + candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or '~/.config')) ans = os.path.join(candidate, appname) try: os.makedirs(ans) @@ -29,3 +26,4 @@ config_dir = _get_config_dir() del _get_config_dir terminfo_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'terminfo') +main_thread = threading.current_thread() diff --git a/kitty/main.py b/kitty/main.py index 29e81e4eb..887d7db33 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -43,15 +43,16 @@ def setup_opengl(): def run_app(opts, args): setup_opengl() + window_width = window_height = 1024 window = glfw.glfwCreateWindow( - 1024, 1024, args.cls.encode('utf-8'), None, None) + window_width, window_height, args.cls.encode('utf-8'), None, None) if not window: raise SystemExit("glfwCreateWindow failed") glfw.glfwSetWindowTitle(window, appname.encode('utf-8')) try: glfw.glfwMakeContextCurrent(window) glfw.glfwSwapInterval(1) - boss = Boss(window, opts, args) + boss = Boss(window, window_width, window_height, opts, args) glfw.glfwSetFramebufferSizeCallback(window, boss.on_window_resize) boss.start()