Compare commits

...

48 Commits

Author SHA1 Message Date
Kovid Goyal
b6679ff7b5 version 0.3.0 2017-08-23 07:25:37 +05:30
Kovid Goyal
de80003103 macOS: Show the current window title in the global menu bar 2017-08-22 22:46:46 +05:30
Kovid Goyal
2b0596c7f9 Add a basic global menubar on macOS
Fixes #111
2017-08-22 20:59:56 +05:30
Kovid Goyal
3d15a1d786 Fix textures not being rendered in a render call that also uploads textures
The active texture should not be unbound in the upload texture functions
2017-08-22 18:34:25 +05:30
Kovid Goyal
1fcb865202 ... 2017-08-22 18:19:46 +05:30
Kovid Goyal
1c56de5605 Change default usage mode to GL_STREAM_DRAW 2017-08-22 18:10:28 +05:30
Kovid Goyal
adbce71fb4 Remove unused code 2017-08-22 18:04:40 +05:30
Kovid Goyal
00e3ea8c08 Remove unused code 2017-08-22 17:32:40 +05:30
Kovid Goyal
36432c8616 API to conveniently download data from a vertex buffer 2017-08-22 12:51:30 +05:30
Kovid Goyal
03e0b9de04 Use vertex buffers instead of texture buffers
Apparently, integer samplers for textFetch() are broken on some macOS
Intel drivers. Fixes #101
2017-08-22 12:08:35 +05:30
Kovid Goyal
4364163ceb Use a single buffer for multiple vertex arrays 2017-08-22 07:59:51 +05:30
Kovid Goyal
ffd9ec653d Allow programs to be used with multiple vertex array objects 2017-08-22 00:24:45 +05:30
Kovid Goyal
ee3c3e4cb4 Binding for DeleteVertexArray 2017-08-22 00:18:03 +05:30
Kovid Goyal
277c46908d Remove unused code 2017-08-21 23:55:39 +05:30
Kovid Goyal
8e27c80e1f A spot of refactoring 2017-08-21 23:26:30 +05:30
Kovid Goyal
afa767c3a8 Support vertex attrib divisors 2017-08-21 23:24:05 +05:30
Kovid Goyal
67ec04fba3 Also use the buffer manager for vertex arrays 2017-08-21 23:10:25 +05:30
Kovid Goyal
f35bf7f1ba Move buffer management into its own class 2017-08-21 22:53:13 +05:30
Kovid Goyal
4fdb55e260 Ensure window with hidden chrome is resizable on macOS 2017-08-21 21:06:34 +05:30
Kovid Goyal
be06669e8f Ensure we dont exceed GL_MAX_TEXTURE_BUFFER_SIZE 2017-08-21 20:53:02 +05:30
Kovid Goyal
313253cd95 Add glFlush() and glFinish() bindings 2017-08-21 19:55:28 +05:30
Kovid Goyal
8c16be2ccd Remove unused code 2017-08-21 19:38:52 +05:30
Kovid Goyal
e1b276786b Fix #112 2017-08-21 19:10:06 +05:30
Kovid Goyal
05f5a05c20 Ensure sprite map size tracking is always correct 2017-08-21 17:39:30 +05:30
Kovid Goyal
7e79aac275 Fix incorrect tracking of previous buffer sizes to avoid re-allocs 2017-08-21 17:12:46 +05:30
Kovid Goyal
0a21819e16 DRYer 2017-08-21 16:53:04 +05:30
Kovid Goyal
fe3e51a00d Move the GLSL shaders into their own files 2017-08-21 16:39:34 +05:30
Kovid Goyal
21874339f1 Remove un-needed binding of the sprite_map buffer 2017-08-21 15:51:55 +05:30
Kovid Goyal
4f22fcdaac Code to verify buffer uploads are working 2017-08-21 15:30:50 +05:30
Kovid Goyal
0424d665d5 Function to read data from OpenGL buffers 2017-08-21 15:29:05 +05:30
Kovid Goyal
0bbea6812e Avoid unnecessary reallocs for cell data
Only re-allocate when the buffer size changes
2017-08-21 14:56:29 +05:30
Kovid Goyal
101a50b0ff Do not use GL_ARB_texture_buffer_object_rgb32
This is not available on macOS using Intel graphics cards
2017-08-21 12:46:09 +05:30
Kovid Goyal
a9f3a698d2 Also check for required OpenGL extensions on OS X 2017-08-21 11:21:57 +05:30
Kovid Goyal
5117a2c17a version 0.2.8 2017-08-05 08:21:20 +05:30
Kovid Goyal
ebce1d7c61 Fix incorrect handling of escape codes to change default foreground and background colors. Fixes #104 2017-08-04 21:57:22 +05:30
Kovid Goyal
e4668c1aff Replay set_dynamic_color and set_color_table_color 2017-08-04 20:47:59 +05:30
Kovid Goyal
2519c49c02 Fix incorrect escape codes generated when using the obsolete "normal" mouse protocol.
Fixes #105
2017-08-04 20:09:18 +05:30
Kovid Goyal
9e512ff58a Ensure queue_action is only called after the Tab object is fully initialized
Fix #103
2017-08-04 07:52:25 +05:30
Kovid Goyal
ed50595aca Add a --detach option
Allows kitty to detach itself from the controlling terminal. Useful
when launching kitty from a GUI environment with broken stdout/stderr or
when launching from a terminal that you want to close later without
affecting the launched kitty instance.
2017-08-03 22:35:47 +05:30
Kovid Goyal
9d62d087e0 Redirect xsel std streams to /dev/null
Prevents hanging if the streams are blocked. Fixes #102
2017-08-03 21:55:09 +05:30
Kovid Goyal
6b933e33f5 If the saved initial window size fails, retry creating the window with a standard size. Fixes #98 2017-07-30 08:32:51 +05:30
Kovid Goyal
a86931f401 Use the new glfw API to set WM_CLASS if available 2017-07-27 18:11:23 +05:30
Kovid Goyal
fc4e1595e8 Print a warning about unknown config keys 2017-07-23 22:59:30 +05:30
Kovid Goyal
b36c3f3425 Clean up config merging 2017-07-23 22:45:57 +05:30
Kovid Goyal
e427fd1233 Fix #95 2017-07-23 21:36:43 +05:30
Kovid Goyal
dd3af45043 Implement a send_text action to allow using keyboard shortcuts to send arbitrary text
Fixes #94
2017-07-23 14:37:15 +05:30
Kovid Goyal
304d42d4c2 Fix #93
Ignore invalid language names returned by the coca framework on macOS
2017-07-16 22:49:05 +05:30
Kovid Goyal
abca9280e7 Fix #92 2017-07-15 08:22:40 +05:30
25 changed files with 873 additions and 376 deletions

View File

@@ -3,12 +3,14 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from ctypes import addressof from ctypes import addressof
from functools import partial
from itertools import chain from itertools import chain
from threading import Lock from threading import Lock
from functools import partial
from .constants import GLfloat, GLint, GLuint, viewport_size from .constants import GLfloat, GLint, GLuint, viewport_size
from .fast_data_types import GL_TRIANGLE_FAN, glMultiDrawArrays, glUniform3fv from .fast_data_types import (
GL_STATIC_DRAW, GL_TRIANGLE_FAN, glMultiDrawArrays, glUniform3fv
)
from .shaders import ShaderProgram from .shaders import ShaderProgram
from .utils import pt_to_px from .utils import pt_to_px
@@ -50,10 +52,10 @@ void main() {
final_color = vec4(color, 1); final_color = vec4(color, 1);
} }
''') ''')
self.add_vertex_array('rect') self.vao_id = self.add_vertex_arrays(self.vertex_array('rect'))
def send_data(self, data): def send_data(self, data):
self.send_vertex_data('rect', data) self.send_vertex_data(self.vao_id, data, usage=GL_STATIC_DRAW)
def set_colors(self, color_buf): def set_colors(self, color_buf):
glUniform3fv(self.uniform_location('colors'), 3, addressof(color_buf)) glUniform3fv(self.uniform_location('colors'), 3, addressof(color_buf))
@@ -149,7 +151,8 @@ class Borders:
program.send_data(self.rects) program.send_data(self.rects)
program.set_colors(self.color_buf) program.set_colors(self.color_buf)
self.is_dirty = False self.is_dirty = False
glMultiDrawArrays( with program.bound_vertex_array(program.vao_id):
GL_TRIANGLE_FAN, glMultiDrawArrays(
addressof(self.starts), GL_TRIANGLE_FAN,
addressof(self.counts), self.num_of_rects) addressof(self.starts),
addressof(self.counts), self.num_of_rects)

View File

@@ -2,38 +2,42 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os import inspect
import io import io
import os
import select import select
import signal import signal
import struct import struct
import inspect
from functools import wraps from functools import wraps
from gettext import gettext as _
from queue import Empty, Queue
from threading import Thread, current_thread from threading import Thread, current_thread
from time import monotonic from time import monotonic
from queue import Queue, Empty
from gettext import gettext as _
from .borders import BordersProgram
from .char_grid import load_shader_programs
from .config import MINIMUM_FONT_SIZE from .config import MINIMUM_FONT_SIZE
from .constants import ( from .constants import (
viewport_size, set_boss, wakeup, cell_size, MODIFIER_KEYS, MODIFIER_KEYS, cell_size, is_key_pressed, isosx, main_thread,
main_thread, mouse_button_pressed, mouse_cursor_pos mouse_button_pressed, mouse_cursor_pos, set_boss, viewport_size, wakeup
) )
from .fast_data_types import ( from .fast_data_types import (
glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GLFW_PRESS, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
GLFW_REPEAT, GLFW_MOUSE_BUTTON_1, glfw_post_empty_event, GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT,
GLFW_CURSOR_NORMAL, GLFW_CURSOR, GLFW_CURSOR_HIDDEN, drain_read drain_read, glBlendFunc, glfw_post_empty_event, glViewport
) )
from .fonts.render import set_font_family from .fonts.render import set_font_family
from .borders import BordersProgram from .keys import (
from .char_grid import cursor_shader, cell_shader get_sent_data, get_shortcut, interpret_key_event, interpret_text_event
from .constants import is_key_pressed )
from .keys import interpret_text_event, interpret_key_event, get_shortcut
from .session import create_session from .session import create_session
from .shaders import Sprites, ShaderProgram from .shaders import Sprites
from .tabs import TabManager, SpecialWindow from .tabs import SpecialWindow, TabManager
from .timers import Timers from .timers import Timers
from .utils import handle_unix_signals, safe_print, pipe2 from .utils import handle_unix_signals, pipe2, safe_print
if isosx:
from .fast_data_types import cocoa_update_title
def conditional_run(w, i): def conditional_run(w, i):
@@ -59,6 +63,7 @@ def callback(func):
pass pass
else: else:
self.queue_action(conditional_run, w, i) self.queue_action(conditional_run, w, i)
return f return f
@@ -80,7 +85,9 @@ class Boss(Thread):
self.screen_update_delay = opts.repaint_delay / 1000.0 self.screen_update_delay = opts.repaint_delay / 1000.0
self.signal_fd = handle_unix_signals() self.signal_fd = handle_unix_signals()
self.read_wakeup_fd, self.write_wakeup_fd = pipe2() self.read_wakeup_fd, self.write_wakeup_fd = pipe2()
self.read_dispatch_map = {self.signal_fd: self.signal_received, self.read_wakeup_fd: self.on_wakeup} self.read_dispatch_map = {
self.signal_fd: self.signal_received,
self.read_wakeup_fd: self.on_wakeup}
self.all_writers = [] self.all_writers = []
self.timers = Timers() self.timers = Timers()
self.ui_timers = Timers() self.ui_timers = Timers()
@@ -100,8 +107,7 @@ class Boss(Thread):
glfw_window.window_focus_callback = self.on_focus glfw_window.window_focus_callback = self.on_focus
self.tab_manager = TabManager(opts, args, startup_session) self.tab_manager = TabManager(opts, args, startup_session)
self.sprites = Sprites() self.sprites = Sprites()
self.cell_program = ShaderProgram(*cell_shader) self.cell_program, self.cursor_program = load_shader_programs()
self.cursor_program = ShaderProgram(*cursor_shader)
self.borders_program = BordersProgram() self.borders_program = BordersProgram()
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
self.sprites.do_layout(cell_size.width, cell_size.height) self.sprites.do_layout(cell_size.width, cell_size.height)
@@ -205,8 +211,10 @@ class Boss(Thread):
def loop(self): def loop(self):
while not self.shutting_down: while not self.shutting_down:
all_readers = list(self.read_dispatch_map) all_readers = list(self.read_dispatch_map)
all_writers = [w.child_fd for w in self.iterwindows() if w.write_buf] all_writers = [
readers, writers, _ = select.select(all_readers, all_writers, [], self.timers.timeout()) w.child_fd for w in self.iterwindows() if w.write_buf]
readers, writers, _ = select.select(
all_readers, all_writers, [], self.timers.timeout())
for r in readers: for r in readers:
self.read_dispatch_map[r]() self.read_dispatch_map[r]()
for w in writers: for w in writers:
@@ -214,7 +222,8 @@ class Boss(Thread):
self.timers() self.timers()
for w in self.iterwindows(): for w in self.iterwindows():
if w.screen.is_dirty(): if w.screen.is_dirty():
self.timers.add_if_missing(self.screen_update_delay, w.update_screen) self.timers.add_if_missing(
self.screen_update_delay, w.update_screen)
@callback @callback
def on_window_resize(self, window, w, h): def on_window_resize(self, window, w, h):
@@ -231,10 +240,16 @@ class Boss(Thread):
glfw_post_empty_event() glfw_post_empty_event()
def increase_font_size(self): def increase_font_size(self):
self.change_font_size(min(self.opts.font_size * 5, self.current_font_size + self.opts.font_size_delta)) self.change_font_size(
min(
self.opts.font_size * 5, self.current_font_size +
self.opts.font_size_delta))
def decrease_font_size(self): def decrease_font_size(self):
self.change_font_size(max(MINIMUM_FONT_SIZE, self.current_font_size - self.opts.font_size_delta)) self.change_font_size(
max(
MINIMUM_FONT_SIZE, self.current_font_size -
self.opts.font_size_delta))
def restore_font_size(self): def restore_font_size(self):
self.change_font_size(self.opts.font_size) self.change_font_size(self.opts.font_size)
@@ -308,7 +323,9 @@ class Boss(Thread):
return return
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS: if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS:
window.scroll_end() window.scroll_end()
data = interpret_key_event(key, scancode, mods, window, action) data = get_sent_data(
self.opts.send_text_map, key, scancode, mods, window, action
) or interpret_key_event(key, scancode, mods, window, action)
if data: if data:
window.write_to_child(data) window.write_to_child(data)
@@ -326,7 +343,9 @@ class Boss(Thread):
else: else:
tab = self.active_tab tab = self.active_tab
if tab is not None: if tab is not None:
tab.new_special_window(SpecialWindow(self.opts.scrollback_pager, data, _('History'))) tab.new_special_window(
SpecialWindow(
self.opts.scrollback_pager, data, _('History')))
def window_for_pos(self, x, y): def window_for_pos(self, x, y):
tab = self.active_tab tab = self.active_tab
@@ -365,7 +384,8 @@ class Boss(Thread):
@callback @callback
def on_mouse_move(self, window, xpos, ypos): def on_mouse_move(self, window, xpos, ypos):
mouse_cursor_pos[:2] = xpos, ypos = int(xpos * viewport_size.x_ratio), int(ypos * viewport_size.y_ratio) mouse_cursor_pos[:2] = xpos, ypos = int(
xpos * viewport_size.x_ratio), int(ypos * viewport_size.y_ratio)
self.show_mouse_cursor() self.show_mouse_cursor()
w = self.window_for_pos(xpos, ypos) w = self.window_for_pos(xpos, ypos)
if w is not None: if w is not None:
@@ -387,7 +407,8 @@ class Boss(Thread):
def show_mouse_cursor(self): def show_mouse_cursor(self):
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_NORMAL) self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_NORMAL)
if self.opts.mouse_hide_wait > 0: if self.opts.mouse_hide_wait > 0:
self.ui_timers.add(self.opts.mouse_hide_wait, self.hide_mouse_cursor) self.ui_timers.add(
self.opts.mouse_hide_wait, self.hide_mouse_cursor)
def hide_mouse_cursor(self): def hide_mouse_cursor(self):
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_HIDDEN) self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_HIDDEN)
@@ -399,12 +420,14 @@ class Boss(Thread):
try: try:
self.glfw_window.request_window_attention() self.glfw_window.request_window_attention()
except AttributeError: except AttributeError:
pass # needs glfw 3.3 pass # needs glfw 3.3
def start_cursor_blink(self): def start_cursor_blink(self):
self.cursor_blinking = True self.cursor_blinking = True
if self.opts.cursor_stop_blinking_after > 0: if self.opts.cursor_stop_blinking_after > 0:
self.ui_timers.add(self.opts.cursor_stop_blinking_after, self.stop_cursor_blinking) self.ui_timers.add(
self.opts.cursor_stop_blinking_after,
self.stop_cursor_blinking)
def stop_cursor_blinking(self): def stop_cursor_blinking(self):
self.cursor_blinking = False self.cursor_blinking = False
@@ -420,15 +443,22 @@ class Boss(Thread):
if tab.title != self.glfw_window_title: if tab.title != self.glfw_window_title:
self.glfw_window_title = tab.title self.glfw_window_title = tab.title
self.glfw_window.set_title(self.glfw_window_title) self.glfw_window.set_title(self.glfw_window_title)
if isosx:
cocoa_update_title(self.glfw_window_title)
with self.sprites: with self.sprites:
self.sprites.render_dirty_cells() self.sprites.render_dirty_cells()
tab.render() tab.render()
render_data = {window: window.char_grid.prepare_for_render(self.sprites) for window in tab.visible_windows() if not window.needs_layout} render_data = {
window:
window.char_grid.prepare_for_render(self.cell_program)
for window in tab.visible_windows()
if not window.needs_layout}
with self.cell_program: with self.cell_program:
self.tab_manager.render(self.cell_program, self.sprites) self.tab_manager.render(self.cell_program, self.sprites)
for window, rd in render_data.items(): for window, rd in render_data.items():
if rd is not None: if rd is not None:
window.render_cells(rd, self.cell_program, self.sprites) window.render_cells(
rd, self.cell_program, self.sprites)
active = self.active_window active = self.active_window
rd = render_data.get(active) rd = render_data.get(active)
if rd is not None: if rd is not None:
@@ -439,15 +469,16 @@ class Boss(Thread):
d = int(self.opts.cursor_blink_interval * 1000) d = int(self.opts.cursor_blink_interval * 1000)
n = t // d n = t // d
draw_cursor = n % 2 == 0 draw_cursor = n % 2 == 0
self.ui_timers.add_if_missing(((n + 1) * d / 1000) - now, glfw_post_empty_event) self.ui_timers.add_if_missing(
((n + 1) * d / 1000) - now, glfw_post_empty_event)
if draw_cursor: if draw_cursor:
with self.cursor_program: with self.cursor_program:
active.char_grid.render_cursor(rd, self.cursor_program, self.window_is_focused) active.char_grid.render_cursor(
rd, self.cursor_program,
self.window_is_focused)
def gui_close_window(self, window): def gui_close_window(self, window):
if window.char_grid.buffer_id is not None: window.char_grid.destroy(self.cell_program)
self.sprites.destroy_sprite_map(window.char_grid.buffer_id)
window.char_grid.buffer_id = None
for tab in self.tab_manager: for tab in self.tab_manager:
if window in tab: if window in tab:
break break
@@ -499,5 +530,8 @@ class Boss(Thread):
self.queue_action(self.tab_manager.move_tab, -1) self.queue_action(self.tab_manager.move_tab, -1)
def display_scrollback_in_new_tab(self, data): def display_scrollback_in_new_tab(self, data):
self.tab_manager.new_tab(special_window=SpecialWindow(self.opts.scrollback_pager, data, _('History'))) self.tab_manager.new_tab(
special_window=SpecialWindow(
self.opts.scrollback_pager, data, _('History')))
# }}} # }}}

26
kitty/cell_fragment.glsl Normal file
View File

@@ -0,0 +1,26 @@
uniform sampler2DArray sprites;
in vec3 sprite_pos;
in vec3 underline_pos;
in vec3 strike_pos;
in vec3 foreground;
in vec3 background;
in vec3 decoration_fg;
out vec4 final_color;
vec3 blend(float alpha, vec3 over, vec3 under) {
return over + (1 - alpha) * under;
}
void main() {
float text_alpha = texture(sprites, sprite_pos).r;
float underline_alpha = texture(sprites, underline_pos).r;
float strike_alpha = texture(sprites, strike_pos).r;
vec3 underline = underline_alpha * decoration_fg;
vec3 strike = strike_alpha * foreground;
vec3 fg = text_alpha * foreground;
vec3 decoration = blend(underline_alpha, underline, strike);
vec3 combined_fg = blend(text_alpha, fg, decoration);
float combined_alpha = max(max(underline_alpha, strike_alpha), text_alpha);
final_color = vec4(blend(combined_alpha, combined_fg, background), 1);
}

59
kitty/cell_vertex.glsl Normal file
View File

@@ -0,0 +1,59 @@
uniform uvec2 dimensions; // xnum, ynum
uniform vec4 steps; // xstart, ystart, dx, dy
uniform vec2 sprite_layout; // dx, dy
uniform ivec2 color_indices; // which color to use as fg and which as bg
in uvec3 sprite_coords;
in uvec3 colors;
out vec3 sprite_pos;
out vec3 underline_pos;
out vec3 strike_pos;
out vec3 foreground;
out vec3 background;
out vec3 decoration_fg;
const uvec2 pos_map[] = uvec2[4](
uvec2(1, 0), // right, top
uvec2(1, 1), // right, bottom
uvec2(0, 1), // left, bottom
uvec2(0, 0) // left, top
);
const uint BYTE_MASK = uint(255);
const uint ZERO = uint(0);
const uint SMASK = uint(3);
vec3 to_color(uint c) {
uint r, g, b;
r = (c >> 16) & BYTE_MASK;
g = (c >> 8) & BYTE_MASK;
b = c & BYTE_MASK;
return vec3(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0);
}
vec3 to_sprite_pos(uvec2 pos, uint x, uint y, uint z) {
vec2 s_xpos = vec2(x, float(x) + 1.0) * sprite_layout.x;
vec2 s_ypos = vec2(y, float(y) + 1.0) * sprite_layout.y;
return vec3(s_xpos[pos.x], s_ypos[pos.y], z);
}
void main() {
uint instance_id = uint(gl_InstanceID);
uint r = instance_id / dimensions[0];
uint c = instance_id - r * dimensions[0];
float left = steps[0] + c * steps[2];
float top = steps[1] - r * steps[3];
vec2 xpos = vec2(left, left + steps[2]);
vec2 ypos = vec2(top, top - steps[3]);
uvec2 pos = pos_map[gl_VertexID];
gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1);
sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z);
uint fg = colors[color_indices[0]];
uint bg = colors[color_indices[1]];
uint decoration = colors[2];
foreground = to_color(fg);
background = to_color(bg);
decoration_fg = to_color(decoration);
underline_pos = to_sprite_pos(pos, (decoration >> 24) & SMASK, ZERO, ZERO);
strike_pos = to_sprite_pos(pos, (decoration >> 26) & SMASK, ZERO, ZERO);
}

View File

@@ -4,9 +4,9 @@
import re import re
import sys import sys
from enum import Enum
from collections import namedtuple from collections import namedtuple
from ctypes import addressof, memmove, sizeof from ctypes import addressof, memmove, sizeof
from enum import Enum
from threading import Lock from threading import Lock
from .config import build_ansi_color_table, defaults from .config import build_ansi_color_table, defaults
@@ -15,11 +15,12 @@ from .constants import (
) )
from .fast_data_types import ( from .fast_data_types import (
CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DATA_CELL_SIZE, GL_BLEND, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DATA_CELL_SIZE, GL_BLEND,
GL_LINE_LOOP, GL_TRIANGLE_FAN, ColorProfile, glDisable, glDrawArrays, GL_LINE_LOOP, GL_TRIANGLE_FAN, GL_UNSIGNED_INT, ColorProfile, glDisable,
glDrawArraysInstanced, glEnable, glUniform1i, glUniform2f, glUniform2ui, glDrawArrays, glDrawArraysInstanced, glEnable, glUniform1i, glUniform2f,
glUniform4f glUniform2i, glUniform2ui, glUniform4f
) )
from .rgb import to_color from .rgb import to_color
from .shaders import ShaderProgram, load_shaders
from .utils import ( from .utils import (
color_as_int, color_from_int, get_logical_dpi, open_url, safe_print, color_as_int, color_from_int, get_logical_dpi, open_url, safe_print,
set_primary_selection set_primary_selection
@@ -32,133 +33,22 @@ class DynamicColor(Enum):
default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6) default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6)
if DATA_CELL_SIZE % 3: class CellProgram(ShaderProgram):
raise ValueError('Incorrect data cell size, must be a multiple of 3')
def create_sprite_map(self):
stride = DATA_CELL_SIZE * sizeof(GLuint)
size = DATA_CELL_SIZE // 2
return self.add_vertex_arrays(
self.vertex_array('sprite_coords', size=size, dtype=GL_UNSIGNED_INT, stride=stride, divisor=1),
self.vertex_array('colors', size=size, dtype=GL_UNSIGNED_INT, stride=stride, offset=stride // 2, divisor=1),
)
# cell shader {{{ def load_shader_programs():
cell = CellProgram(*load_shaders('cell'))
cell_shader = ( cursor = ShaderProgram(*load_shaders('cursor'))
'''\ cursor.vao_id = cursor.add_vertex_arrays()
uniform uvec2 dimensions; // xnum, ynum return cell, cursor
uniform vec4 steps; // xstart, ystart, dx, dy
uniform vec2 sprite_layout; // dx, dy
uniform usamplerBuffer sprite_map; // gl_InstanceID -> x, y, z
uniform uvec2 color_indices; // which color to use as fg and which as bg
out vec3 sprite_pos;
out vec3 underline_pos;
out vec3 strike_pos;
out vec3 foreground;
out vec3 background;
out vec3 decoration_fg;
const uvec2 pos_map[] = uvec2[4](
uvec2(1, 0), // right, top
uvec2(1, 1), // right, bottom
uvec2(0, 1), // left, bottom
uvec2(0, 0) // left, top
);
const uint BYTE_MASK = uint(255);
const uint ZERO = uint(0);
const uint SMASK = uint(3);
vec3 to_color(uint c) {
uint r, g, b;
r = (c >> 16) & BYTE_MASK;
g = (c >> 8) & BYTE_MASK;
b = c & BYTE_MASK;
return vec3(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0);
}
vec3 to_sprite_pos(uvec2 pos, uint x, uint y, uint z) {
vec2 s_xpos = vec2(x, float(x) + 1.0) * sprite_layout[0];
vec2 s_ypos = vec2(y, float(y) + 1.0) * sprite_layout[1];
return vec3(s_xpos[pos[0]], s_ypos[pos[1]], z);
}
void main() {
uint instance_id = uint(gl_InstanceID);
uint r = instance_id / dimensions[0];
uint c = instance_id - r * dimensions[0];
float left = steps[0] + c * steps[2];
float top = steps[1] - r * steps[3];
vec2 xpos = vec2(left, left + steps[2]);
vec2 ypos = vec2(top, top - steps[3]);
uvec2 pos = pos_map[gl_VertexID];
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
int sprite_id = gl_InstanceID * STRIDE;
uvec4 spos = texelFetch(sprite_map, sprite_id);
uvec4 colors = texelFetch(sprite_map, sprite_id + 1);
sprite_pos = to_sprite_pos(pos, spos[0], spos[1], spos[2]);
foreground = to_color(colors[color_indices[0]]);
background = to_color(colors[color_indices[1]]);
uint decoration = colors[2];
decoration_fg = to_color(decoration);
underline_pos = to_sprite_pos(pos, (decoration >> 24) & SMASK, ZERO, ZERO);
strike_pos = to_sprite_pos(pos, (decoration >> 26) & SMASK, ZERO, ZERO);
}
'''.replace('STRIDE', str(DATA_CELL_SIZE // 3)),
'''\
uniform sampler2DArray sprites;
in vec3 sprite_pos;
in vec3 underline_pos;
in vec3 strike_pos;
in vec3 foreground;
in vec3 background;
in vec3 decoration_fg;
out vec4 final_color;
vec3 blend(float alpha, vec3 over, vec3 under) {
return over + (1 - alpha) * under;
}
void main() {
float text_alpha = texture(sprites, sprite_pos).r;
float underline_alpha = texture(sprites, underline_pos).r;
float strike_alpha = texture(sprites, strike_pos).r;
vec3 underline = underline_alpha * decoration_fg;
vec3 strike = strike_alpha * foreground;
vec3 fg = text_alpha * foreground;
vec3 decoration = blend(underline_alpha, underline, strike);
vec3 combined_fg = blend(text_alpha, fg, decoration);
float combined_alpha = max(max(underline_alpha, strike_alpha), text_alpha);
final_color = vec4(blend(combined_alpha, combined_fg, background), 1);
}
''')
# }}}
# cursor shader {{{
cursor_shader = (
'''\
uniform vec2 xpos;
uniform vec2 ypos;
const uvec2 pos_map[] = uvec2[4](
uvec2(1, 0), // right, top
uvec2(1, 1), // right, bottom
uvec2(0, 1), // left, bottom
uvec2(0, 0) // left, top
);
void main() {
uvec2 pos = pos_map[gl_VertexID];
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
}
''',
'''\
uniform vec4 color;
out vec4 final_color;
void main() {
final_color = color;
}
''')
# }}}
class Selection: # {{{ class Selection: # {{{
@@ -230,16 +120,15 @@ def calculate_gl_geometry(window_geometry):
return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy)
def render_cells(buffer_id, sg, cell_program, sprites, invert_colors=False): def render_cells(vao_id, sg, cell_program, sprites, invert_colors=False):
sprites.bind_sprite_map(buffer_id)
ul = cell_program.uniform_location ul = cell_program.uniform_location
glUniform2ui(ul('dimensions'), sg.xnum, sg.ynum) glUniform2ui(ul('dimensions'), sg.xnum, sg.ynum)
glUniform2ui(ul('color_indices'), 1 if invert_colors else 0, 0 if invert_colors else 1) glUniform2i(ul('color_indices'), 1 if invert_colors else 0, 0 if invert_colors else 1)
glUniform4f(ul('steps'), sg.xstart, sg.ystart, sg.dx, sg.dy) glUniform4f(ul('steps'), sg.xstart, sg.ystart, sg.dx, sg.dy)
glUniform1i(ul('sprites'), sprites.sampler_num) glUniform1i(ul('sprites'), sprites.sampler_num)
glUniform1i(ul('sprite_map'), sprites.buffer_sampler_num)
glUniform2f(ul('sprite_layout'), *(sprites.layout)) glUniform2f(ul('sprite_layout'), *(sprites.layout))
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, sg.xnum * sg.ynum) with cell_program.bound_vertex_array(vao_id):
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, sg.xnum * sg.ynum)
class CharGrid: class CharGrid:
@@ -248,7 +137,7 @@ class CharGrid:
def __init__(self, screen, opts): def __init__(self, screen, opts):
self.buffer_lock = Lock() self.buffer_lock = Lock()
self.buffer_id = None self.vao_id = None
self.current_selection = Selection() self.current_selection = Selection()
self.last_rendered_selection = self.current_selection.limits(0, screen.lines, screen.columns) self.last_rendered_selection = self.current_selection.limits(0, screen.lines, screen.columns)
self.render_buf_is_dirty = True self.render_buf_is_dirty = True
@@ -276,6 +165,11 @@ class CharGrid:
safe_print('Invalid characters in select_by_word_characters, ignoring', file=sys.stderr) safe_print('Invalid characters in select_by_word_characters, ignoring', file=sys.stderr)
self.word_pat = re.compile(r'[\w{}]'.format(escape(defaults.select_by_word_characters)), re.UNICODE) self.word_pat = re.compile(r'[\w{}]'.format(escape(defaults.select_by_word_characters)), re.UNICODE)
def destroy(self, cell_program):
if self.vao_id is not None:
cell_program.remove_vertex_array(self.vao_id)
self.vao_id = None
def update_position(self, window_geometry): def update_position(self, window_geometry):
self.screen_geometry = calculate_gl_geometry(window_geometry) self.screen_geometry = calculate_gl_geometry(window_geometry)
@@ -297,7 +191,7 @@ class CharGrid:
if raw is None: if raw is None:
return 0 return 0
val = to_color(raw) val = to_color(raw)
return None if val is None else (color_as_int(val) << 8) | 1 return None if val is None else (color_as_int(val) << 8) | 2
for which, val in changes.items(): for which, val in changes.items():
val = item(val) val = item(val)
@@ -322,10 +216,9 @@ class CharGrid:
sprites = get_boss().sprites sprites = get_boss().sprites
is_dirty = self.screen.is_dirty() is_dirty = self.screen.is_dirty()
with sprites.lock: with sprites.lock:
fg = self.screen.default_fg fg, bg = self.screen.default_fg, self.screen.default_bg
fg = fg >> 8 if fg & 1 else self.default_fg fg = fg >> 8 if fg & 2 else self.default_fg
bg = self.screen.default_bg bg = bg >> 8 if bg & 2 else self.default_bg
bg = bg >> 8 if bg & 1 else self.default_bg
cursor_changed, history_line_added_count = self.screen.update_cell_data( cursor_changed, history_line_added_count = self.screen.update_cell_data(
sprites.backend, self.color_profile, addressof(self.main_sprite_map), fg, bg, force_full_refresh) sprites.backend, self.color_profile, addressof(self.main_sprite_map), fg, bg, force_full_refresh)
if self.scrolled_by: if self.scrolled_by:
@@ -461,13 +354,13 @@ class CharGrid:
s = sel or self.current_selection s = sel or self.current_selection
return s.text(self.screen.linebuf, self.screen.historybuf) return s.text(self.screen.linebuf, self.screen.historybuf)
def prepare_for_render(self, sprites): def prepare_for_render(self, cell_program):
with self.buffer_lock: with self.buffer_lock:
sg = self.render_data sg = self.render_data
if sg is None: if sg is None:
return return
if self.buffer_id is None: if self.vao_id is None:
self.buffer_id = sprites.add_sprite_map() self.vao_id = cell_program.create_sprite_map()
buf = self.render_buf buf = self.render_buf
start, end = sel = self.current_selection.limits(self.scrolled_by, self.screen.lines, self.screen.columns) start, end = sel = self.current_selection.limits(self.scrolled_by, self.screen.lines, self.screen.columns)
if start != end: if start != end:
@@ -475,18 +368,18 @@ class CharGrid:
if self.render_buf_is_dirty or sel != self.last_rendered_selection: if self.render_buf_is_dirty or sel != self.last_rendered_selection:
memmove(buf, self.render_buf, sizeof(type(buf))) memmove(buf, self.render_buf, sizeof(type(buf)))
fg = self.screen.highlight_fg fg = self.screen.highlight_fg
fg = fg >> 8 if fg & 1 else self.highlight_fg fg = fg >> 8 if fg & 2 else self.highlight_fg
bg = self.screen.highlight_bg bg = self.screen.highlight_bg
bg = bg >> 8 if bg & 1 else self.highlight_bg bg = bg >> 8 if bg & 2 else self.highlight_bg
self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], fg, bg) self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], fg, bg)
if self.render_buf_is_dirty or self.last_rendered_selection != sel: if self.render_buf_is_dirty or self.last_rendered_selection != sel:
sprites.set_sprite_map(self.buffer_id, buf) cell_program.send_vertex_data(self.vao_id, buf)
self.render_buf_is_dirty = False self.render_buf_is_dirty = False
self.last_rendered_selection = sel self.last_rendered_selection = sel
return sg return sg
def render_cells(self, sg, cell_program, sprites, invert_colors=False): def render_cells(self, sg, cell_program, sprites, invert_colors=False):
render_cells(self.buffer_id, sg, cell_program, sprites, invert_colors=invert_colors) render_cells(self.vao_id, sg, cell_program, sprites, invert_colors=invert_colors)
def render_cursor(self, sg, cursor_program, is_focused): def render_cursor(self, sg, cursor_program, is_focused):
cursor = self.current_cursor cursor = self.current_cursor
@@ -503,7 +396,7 @@ class CharGrid:
left = sg.xstart + cursor.x * sg.dx left = sg.xstart + cursor.x * sg.dx
top = sg.ystart - cursor.y * sg.dy top = sg.ystart - cursor.y * sg.dy
cc = self.screen.cursor_color cc = self.screen.cursor_color
col = color_from_int(cc >> 8) if cc & 1 else self.opts.cursor col = color_from_int(cc >> 8) if cc & 2 else self.opts.cursor
shape = cursor.shape or self.default_cursor.shape shape = cursor.shape or self.default_cursor.shape
alpha = self.opts.cursor_opacity alpha = self.opts.cursor_opacity
if alpha < 1.0 and shape == CURSOR_BLOCK: if alpha < 1.0 and shape == CURSOR_BLOCK:
@@ -516,5 +409,6 @@ class CharGrid:
glUniform4f(ul('color'), col[0] / 255.0, col[1] / 255.0, col[2] / 255.0, alpha) glUniform4f(ul('color'), col[0] / 255.0, col[1] / 255.0, col[2] / 255.0, alpha)
glUniform2f(ul('xpos'), left, right) glUniform2f(ul('xpos'), left, right)
glUniform2f(ul('ypos'), top, bottom) glUniform2f(ul('ypos'), top, bottom)
glDrawArrays(GL_TRIANGLE_FAN if is_focused else GL_LINE_LOOP, 0, 4) with cursor_program.bound_vertex_array(cursor_program.vao_id):
glDrawArrays(GL_TRIANGLE_FAN if is_focused else GL_LINE_LOOP, 0, 4)
glDisable(GL_BLEND) glDisable(GL_BLEND)

View File

@@ -125,14 +125,14 @@ def write_osc(code, string=''):
write(OSC + str(code) + string + '\x07') write(OSC + str(code) + string + '\x07')
set_dynamic_color = write_osc set_dynamic_color = set_color_table_color = write_osc
def replay(raw): def replay(raw):
for line in raw.splitlines(): for line in raw.splitlines():
if line.strip(): if line.strip():
cmd, rest = line.partition(' ')[::2] cmd, rest = line.partition(' ')[::2]
if cmd in {'draw', 'set_title', 'set_icon'}: if cmd in {'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color'}:
globals()[cmd](rest) globals()[cmd](rest)
else: else:
rest = map(int, rest.split()) if rest else () rest = map(int, rest.split()) if rest else ()

View File

@@ -9,17 +9,150 @@
#include "data-types.h" #include "data-types.h"
#include <Cocoa/Cocoa.h> #include <Cocoa/Cocoa.h>
#ifndef NSWindowStyleMaskTitled #include <AvailabilityMacros.h>
#define NSWindowStyleMaskTitled NSTitledWindowMask // Needed for _NSGetProgname
#include <crt_externs.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask
#endif #endif
@interface MenuDispatcher : NSObject
@end
@implementation MenuDispatcher
@end
static NSObject* menu_dispatcher = NULL;
static NSMenuItem* title_menu = NULL;
static NSString*
find_app_name(void) {
size_t i;
NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
// Keys to search for as potential application names
NSString* name_keys[] =
{
@"CFBundleDisplayName",
@"CFBundleName",
@"CFBundleExecutable",
};
for (i = 0; i < sizeof(name_keys) / sizeof(name_keys[0]); i++)
{
id name = [infoDictionary objectForKey:name_keys[i]];
if (name &&
[name isKindOfClass:[NSString class]] &&
![name isEqualToString:@""])
{
return name;
}
}
char** progname = _NSGetProgname();
if (progname && *progname)
return [NSString stringWithUTF8String:*progname];
// Really shouldn't get here
return @"kitty";
}
PyObject* PyObject*
cocoa_hide_titlebar(PyObject UNUSED *self, PyObject *window_id) { cocoa_create_global_menu(PyObject UNUSED *_self) {
if (menu_dispatcher != NULL) { Py_RETURN_NONE; }
NSString* app_name = find_app_name();
menu_dispatcher = [[MenuDispatcher alloc] init];
NSMenu* bar = [[NSMenu alloc] init];
[NSApp setMainMenu:bar];
NSMenuItem* appMenuItem =
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
NSMenu* appMenu = [[NSMenu alloc] init];
[appMenuItem setSubmenu:appMenu];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", app_name]
action:@selector(orderFrontStandardAboutPanel:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name]
action:@selector(hide:)
keyEquivalent:@"h"];
[[appMenu addItemWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"]
setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask];
[appMenu addItemWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
NSMenu* servicesMenu = [[NSMenu alloc] init];
[NSApp setServicesMenu:servicesMenu];
[[appMenu addItemWithTitle:@"Services"
action:NULL
keyEquivalent:@""] setSubmenu:servicesMenu];
[servicesMenu release];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name]
action:@selector(terminate:)
keyEquivalent:@"q"];
[appMenu release];
NSMenuItem* windowMenuItem =
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
[NSApp setWindowsMenu:windowMenu];
[windowMenuItem setSubmenu:windowMenu];
[windowMenu addItemWithTitle:@"Minimize"
action:@selector(performMiniaturize:)
keyEquivalent:@"m"];
[windowMenu addItemWithTitle:@"Zoom"
action:@selector(performZoom:)
keyEquivalent:@""];
[windowMenu addItem:[NSMenuItem separatorItem]];
[windowMenu addItemWithTitle:@"Bring All to Front"
action:@selector(arrangeInFront:)
keyEquivalent:@""];
[windowMenu addItem:[NSMenuItem separatorItem]];
[[windowMenu addItemWithTitle:@"Enter Full Screen"
action:@selector(toggleFullScreen:)
keyEquivalent:@"f"]
setKeyEquivalentModifierMask:NSControlKeyMask | NSCommandKeyMask];
[windowMenu release];
[bar release];
Py_RETURN_NONE;
}
PyObject*
cocoa_update_title(PyObject UNUSED *self, PyObject *pytitle) {
NSString *title = [[NSString alloc] initWithUTF8String:PyUnicode_AsUTF8(pytitle)];
NSMenu *bar = [NSApp mainMenu];
if (title_menu != NULL) {
[bar removeItem:title_menu];
}
title_menu = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
NSMenu *m = [[NSMenu alloc] initWithTitle:[NSString stringWithFormat:@" :: %@", title]];
[title_menu setSubmenu:m];
[m release];
[title release];
Py_RETURN_NONE;
}
PyObject*
cocoa_make_window_resizable(PyObject UNUSED *self, PyObject *window_id) {
NSWindow *window = (NSWindow*)PyLong_AsVoidPtr(window_id); NSWindow *window = (NSWindow*)PyLong_AsVoidPtr(window_id);
@try { @try {
[window setStyleMask: [window setStyleMask:
[window styleMask] & ~NSWindowStyleMaskTitled]; [window styleMask] | NSWindowStyleMaskResizable];
} @catch (NSException *e) { } @catch (NSException *e) {
return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]); return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
} }

View File

@@ -2,6 +2,7 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import ast
import json import json
import os import os
import re import re
@@ -84,16 +85,22 @@ named_keys = {
} }
def parse_key(val, keymap): def parse_shortcut(sc):
sc, action = val.partition(' ')[::2]
action = action.strip()
sc = sc.strip()
if not sc or not action:
return
parts = sc.split('+') parts = sc.split('+')
mods = parse_mods(parts[:-1]) mods = parse_mods(parts[:-1])
key = parts[-1].upper() key = parts[-1].upper()
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None) key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
if key is not None:
return mods, key
return None, None
def parse_key(val, keymap):
sc, action = val.partition(' ')[::2]
sc, action = sc.strip(), action.strip()
if not sc or not action:
return
mods, key = parse_shortcut(sc)
if key is None: if key is None:
safe_print( safe_print(
'Shortcut: {} has an unknown key, ignoring'.format(val), 'Shortcut: {} has an unknown key, ignoring'.format(val),
@@ -137,6 +144,39 @@ def parse_symbol_map(val):
return symbol_map return symbol_map
def parse_send_text(val):
parts = val.split(' ')
def abort(msg):
safe_print(
'Send text: {} is invalid ({}), ignoring'.format(val, msg), file=sys.stderr
)
return {}
if len(parts) < 3:
return abort('Incomplete')
text = ' '.join(parts[2:])
mode, sc = parts[:2]
mods, key = parse_shortcut(sc.strip())
if key is None:
return abort('Invalid shortcut')
text = ast.literal_eval("'''" + text + "'''").encode('utf-8')
if not text:
return abort('Empty text')
if mode in ('all', '*'):
modes = parse_send_text.all_modes
else:
modes = frozenset(mode.split(',')).intersection(parse_send_text.all_modes)
if not modes:
return abort('Invalid keyboard modes')
return {mode: {(mods, key): text} for mode in modes}
parse_send_text.all_modes = frozenset({'normal', 'application', 'kitty'})
def to_open_url_modifiers(val): def to_open_url_modifiers(val):
return parse_mods(val.split('+')) return parse_mods(val.split('+'))
@@ -198,8 +238,10 @@ for a in ('active', 'inactive'):
type_map['%s_tab_%s' % (a, b)] = lambda x: to_color(x, validate=True) type_map['%s_tab_%s' % (a, b)] = lambda x: to_color(x, validate=True)
def parse_config(lines): def parse_config(lines, check_keys=True):
ans = {'keymap': {}, 'symbol_map': {}} ans = {'keymap': {}, 'symbol_map': {}, 'send_text_map': {'kitty': {}, 'normal': {}, 'application': {}}}
if check_keys:
all_keys = defaults._asdict()
for line in lines: for line in lines:
line = line.strip() line = line.strip()
if not line or line.startswith('#'): if not line or line.startswith('#'):
@@ -213,6 +255,15 @@ def parse_config(lines):
if key == 'symbol_map': if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val)) ans['symbol_map'].update(parse_symbol_map(val))
continue continue
if key == 'send_text':
stvals = parse_send_text(val)
for k, v in ans['send_text_map'].items():
v.update(stvals.get(k, {}))
continue
if check_keys:
if key not in all_keys:
safe_print('Ignoring unknown config key: {}'.format(key), file=sys.stderr)
continue
tm = type_map.get(key) tm = type_map.get(key)
if tm is not None: if tm is not None:
val = tm(val) val = tm(val)
@@ -223,38 +274,42 @@ def parse_config(lines):
with open( with open(
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf') os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf')
) as f: ) as f:
defaults = parse_config(f.readlines()) defaults = parse_config(f.readlines(), check_keys=False)
Options = namedtuple('Defaults', ','.join(defaults.keys())) Options = namedtuple('Defaults', ','.join(defaults.keys()))
defaults = Options(**defaults) defaults = Options(**defaults)
actions = frozenset(defaults.keymap.values()) actions = frozenset(defaults.keymap.values())
def update_dict(a, b): def merge_keymaps(defaults, newvals):
a.update(b) ans = defaults.copy()
return a for k, v in newvals.items():
if v in {'noop', 'no-op', 'no_op'}:
ans.pop(k, None)
continue
if v in actions:
ans[k] = v
return ans
def merge_dicts(vals, defaults): def merge_dicts(defaults, newvals):
return { ans = defaults.copy()
k: update_dict(v, vals.get(k, {})) ans.update(newvals)
if isinstance(v, dict) else vals.get(k, v) return ans
for k, v in defaults.items()
}
def merge_configs(ans, vals): def merge_configs(defaults, vals):
vals['keymap'] = { ans = {}
k: v for k, v in defaults.items():
for k, v in vals.get('keymap', {}).items() if v in actions if isinstance(v, dict):
} newvals = vals.get(k, {})
remove_keys = { if k == 'keymap':
k ans['keymap'] = merge_keymaps(v, newvals)
for k, v in vals.get('keymap', {}).items() elif k == 'send_text_map':
if v in ('noop', 'no-op', 'no_op') ans[k] = {m: merge_dicts(mm, newvals.get(m, {})) for m, mm in v.items()}
} else:
ans = merge_dicts(vals, ans) ans[k] = merge_dicts(v, newvals)
for k in remove_keys: else:
ans['keymap'].pop(k, None) ans[k] = vals.get(k, v)
return ans return ans

View File

@@ -15,7 +15,7 @@ from .fast_data_types import (
GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER) GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER)
appname = 'kitty' appname = 'kitty'
version = (0, 2, 7) version = (0, 3, 0)
str_version = '.'.join(map(str, version)) str_version = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
isosx = 'darwin' in _plat isosx = 'darwin' in _plat

View File

@@ -0,0 +1,6 @@
uniform vec4 color;
out vec4 final_color;
void main() {
final_color = color;
}

16
kitty/cursor_vertex.glsl Normal file
View File

@@ -0,0 +1,16 @@
uniform vec2 xpos;
uniform vec2 ypos;
const uvec2 pos_map[] = uvec2[4](
uvec2(1, 0), // right, top
uvec2(1, 1), // right, bottom
uvec2(0, 1), // left, bottom
uvec2(0, 0) // left, top
);
void main() {
uvec2 pos = pos_map[gl_VertexID];
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
}

View File

@@ -31,6 +31,17 @@ change_wcwidth_wrap(PyObject UNUSED *self, PyObject *use9) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject*
redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
char *devnull = NULL;
if (!PyArg_ParseTuple(args, "s", &devnull)) return NULL;
if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
if (freopen(devnull, "w", stderr) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
GL_METHODS GL_METHODS
{"drain_read", (PyCFunction)drain_read, METH_O, ""}, {"drain_read", (PyCFunction)drain_read, METH_O, ""},
@@ -38,6 +49,7 @@ static PyMethodDef module_methods[] = {
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""}, {"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
{"read_bytes", (PyCFunction)read_bytes, METH_VARARGS, ""}, {"read_bytes", (PyCFunction)read_bytes, METH_VARARGS, ""},
{"read_bytes_dump", (PyCFunction)read_bytes_dump, METH_VARARGS, ""}, {"read_bytes_dump", (PyCFunction)read_bytes_dump, METH_VARARGS, ""},
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""}, {"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
#ifndef __APPLE__ #ifndef __APPLE__

View File

@@ -35,8 +35,7 @@ typedef unsigned int index_type;
#define CELL_FIELD_COUNT 5 #define CELL_FIELD_COUNT 5
#define CELL_SIZE (CELL_FIELD_COUNT * 4) #define CELL_SIZE (CELL_FIELD_COUNT * 4)
// The data cell size must be a multiple of 3 #define DATA_CELL_SIZE 6
#define DATA_CELL_SIZE 2 * 3
#define CHAR_MASK 0xFFFFFF #define CHAR_MASK 0xFFFFFF
#define ATTRS_SHIFT 24 #define ATTRS_SHIFT 24

View File

@@ -84,6 +84,16 @@ Uniform2ui(PyObject UNUSED *self, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject*
Uniform2i(PyObject UNUSED *self, PyObject *args) {
int location;
int x, y;
if (!PyArg_ParseTuple(args, "iii", &location, &x, &y)) return NULL;
glUniform2i(location, x, y);
CHECK_ERROR;
Py_RETURN_NONE;
}
static PyObject* static PyObject*
Uniform1i(PyObject UNUSED *self, PyObject *args) { Uniform1i(PyObject UNUSED *self, PyObject *args) {
int location; int location;
@@ -147,7 +157,6 @@ _glewInit(PyObject UNUSED *self) {
return NULL; \ return NULL; \
} }
ARB_TEST(texture_storage); ARB_TEST(texture_storage);
ARB_TEST(texture_buffer_object_rgb32);
#undef ARB_TEST #undef ARB_TEST
#endif #endif
Py_RETURN_NONE; Py_RETURN_NONE;
@@ -528,29 +537,37 @@ GetTexImage(PyObject UNUSED *self, PyObject *args) {
} }
static PyObject* static PyObject*
NamedBufferData(PyObject UNUSED *self, PyObject *args) { GetBufferSubData(PyObject UNUSED *self, PyObject *args) {
int usage; int buftype;
unsigned long size, target; unsigned long size, target, offset;
PyObject *address; PyObject *address;
if (!PyArg_ParseTuple(args, "kkO!i", &target, &size, &PyLong_Type, &address, &usage)) return NULL; if (!PyArg_ParseTuple(args, "ikkkO!", &buftype, &target, &size, &offset, &PyLong_Type, &address)) return NULL;
void *data = PyLong_AsVoidPtr(address); void *data = PyLong_AsVoidPtr(address);
if (data == NULL) { PyErr_SetString(PyExc_TypeError, "Not a valid data pointer"); return NULL; } if (data == NULL) { PyErr_SetString(PyExc_TypeError, "Not a valid data pointer"); return NULL; }
glBindBuffer(buftype, target);
Py_BEGIN_ALLOW_THREADS; Py_BEGIN_ALLOW_THREADS;
#ifdef glNamedBufferData glGetBufferSubData(buftype, offset, size, data);
#ifdef __APPLE__
if(false) {
#else
if(GLEW_VERSION_4_5) {
#endif
glNamedBufferData(target, size, data, usage);
} else {
glBindBuffer(GL_TEXTURE_BUFFER, target); glBufferData(GL_TEXTURE_BUFFER, size, data, usage); glBindBuffer(GL_TEXTURE_BUFFER, 0);
}
#else
glBindBuffer(GL_TEXTURE_BUFFER, target); glBufferData(GL_TEXTURE_BUFFER, size, data, usage); glBindBuffer(GL_TEXTURE_BUFFER, 0);
#endif
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
CHECK_ERROR; CHECK_ERROR;
glBindBuffer(buftype, 0);
Py_RETURN_NONE;
}
static PyObject*
replace_or_create_buffer(PyObject UNUSED *self, PyObject *args) {
int usage, buftype;
unsigned long size, prev_sz, target;
PyObject *address;
if (!PyArg_ParseTuple(args, "kkkO!ii", &target, &size, &prev_sz, &PyLong_Type, &address, &usage, &buftype)) return NULL;
void *data = PyLong_AsVoidPtr(address);
if (data == NULL) { PyErr_SetString(PyExc_TypeError, "Not a valid data pointer"); return NULL; }
glBindBuffer(buftype, target);
Py_BEGIN_ALLOW_THREADS;
if (prev_sz == 0 || prev_sz != size) glBufferData(buftype, size, data, usage);
else glBufferSubData(buftype, 0, size, data);
Py_END_ALLOW_THREADS;
CHECK_ERROR;
glBindBuffer(buftype, 0);
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@@ -606,6 +623,14 @@ DeleteBuffer(PyObject UNUSED *self, PyObject *val) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject*
DeleteVertexArray(PyObject UNUSED *self, PyObject *val) {
GLuint tex_id = (GLuint)PyLong_AsUnsignedLong(val);
glDeleteVertexArrays(1, &tex_id);
CHECK_ERROR;
Py_RETURN_NONE;
}
static PyObject* static PyObject*
BlendFunc(PyObject UNUSED *self, PyObject *args) { BlendFunc(PyObject UNUSED *self, PyObject *args) {
int s, d; int s, d;
@@ -641,17 +666,70 @@ EnableVertexAttribArray(PyObject UNUSED *self, PyObject *val) {
static PyObject* static PyObject*
VertexAttribPointer(PyObject UNUSED *self, PyObject *args) { VertexAttribPointer(PyObject UNUSED *self, PyObject *args) {
unsigned int index, stride; unsigned int index;
int type=GL_FLOAT, normalized, size; int type, normalized, size, stride;
void *offset; unsigned long offset;
PyObject *l; if (!PyArg_ParseTuple(args, "Iiipik", &index, &size, &type, &normalized, &stride, &offset)) return NULL;
if (!PyArg_ParseTuple(args, "IiipIO!", &index, &size, &type, &normalized, &stride, &PyLong_Type, &l)) return NULL; switch(type) {
offset = PyLong_AsVoidPtr(l); case GL_BYTE:
glVertexAttribPointer(index, size, type, normalized, stride, offset); case GL_UNSIGNED_BYTE:
case GL_SHORT:
case GL_UNSIGNED_SHORT:
case GL_INT:
case GL_UNSIGNED_INT:
glVertexAttribIPointer(index, size, type, stride, (GLvoid*)offset);
break;
default:
glVertexAttribPointer(index, size, type, normalized ? GL_TRUE : GL_FALSE, stride, (GLvoid*)offset);
break;
}
CHECK_ERROR; CHECK_ERROR;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject*
VertexAttribDivisor(PyObject UNUSED *self, PyObject *args) {
unsigned int index, divisor;
if (!PyArg_ParseTuple(args, "II", &index, &divisor)) return NULL;
glVertexAttribDivisor(index, divisor);
CHECK_ERROR;
Py_RETURN_NONE;
}
static PyObject*
Flush(PyObject UNUSED *self) {
glFinish();
Py_RETURN_NONE;
}
static PyObject*
Finish(PyObject UNUSED *self) {
glFinish();
Py_RETURN_NONE;
}
static PyObject*
check_for_extensions(PyObject UNUSED *self) {
GLint n = 0, i, left = 2;
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
bool texture_storage = false;
#define CHECK(name) if (!name) { \
if (strstr((const char*)ext, "GL_ARB_" #name) == (const char *)ext) { left--; name = true; } \
}
for (i = 0; i < n; i++) {
const GLubyte *ext = glGetStringi(GL_EXTENSIONS, i);
CHECK(texture_storage);
if (left < 1) break;
}
#undef CHECK
if (left > 0) {
#define CHECK(name) if (!name) { PyErr_Format(PyExc_RuntimeError, "The OpenGL driver on this system is missing the required extension: GL_ARB_%s", #name); return NULL; }
CHECK(texture_storage);
#undef CHECK
}
Py_RETURN_NONE;
}
int add_module_gl_constants(PyObject *module) { int add_module_gl_constants(PyObject *module) {
#define GLC(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return 0; } #define GLC(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return 0; }
GLC(GL_VERSION); GLC(GL_VERSION);
@@ -667,17 +745,17 @@ int add_module_gl_constants(PyObject *module) {
GLC(GL_COMPILE_STATUS); GLC(GL_COMPILE_STATUS);
GLC(GL_LINK_STATUS); GLC(GL_LINK_STATUS);
GLC(GL_TEXTURE0); GLC(GL_TEXTURE1); GLC(GL_TEXTURE2); GLC(GL_TEXTURE3); GLC(GL_TEXTURE4); GLC(GL_TEXTURE5); GLC(GL_TEXTURE6); GLC(GL_TEXTURE7); GLC(GL_TEXTURE8); GLC(GL_TEXTURE0); GLC(GL_TEXTURE1); GLC(GL_TEXTURE2); GLC(GL_TEXTURE3); GLC(GL_TEXTURE4); GLC(GL_TEXTURE5); GLC(GL_TEXTURE6); GLC(GL_TEXTURE7); GLC(GL_TEXTURE8);
GLC(GL_MAX_ARRAY_TEXTURE_LAYERS); GLC(GL_MAX_ARRAY_TEXTURE_LAYERS); GLC(GL_TEXTURE_BINDING_BUFFER); GLC(GL_MAX_TEXTURE_BUFFER_SIZE);
GLC(GL_MAX_TEXTURE_SIZE); GLC(GL_MAX_TEXTURE_SIZE);
GLC(GL_TEXTURE_2D_ARRAY); GLC(GL_TEXTURE_2D_ARRAY);
GLC(GL_LINEAR); GLC(GL_CLAMP_TO_EDGE); GLC(GL_NEAREST); GLC(GL_LINEAR); GLC(GL_CLAMP_TO_EDGE); GLC(GL_NEAREST);
GLC(GL_TEXTURE_MIN_FILTER); GLC(GL_TEXTURE_MAG_FILTER); GLC(GL_TEXTURE_MIN_FILTER); GLC(GL_TEXTURE_MAG_FILTER);
GLC(GL_TEXTURE_WRAP_S); GLC(GL_TEXTURE_WRAP_T); GLC(GL_TEXTURE_WRAP_S); GLC(GL_TEXTURE_WRAP_T);
GLC(GL_UNPACK_ALIGNMENT); GLC(GL_UNPACK_ALIGNMENT);
GLC(GL_R8); GLC(GL_RED); GLC(GL_UNSIGNED_BYTE); GLC(GL_RGB32UI); GLC(GL_RGBA); GLC(GL_R8); GLC(GL_RED); GLC(GL_UNSIGNED_BYTE); GLC(GL_R32UI); GLC(GL_RGB32UI); GLC(GL_RGBA);
GLC(GL_TEXTURE_BUFFER); GLC(GL_STATIC_DRAW); GLC(GL_STREAM_DRAW); GLC(GL_TEXTURE_BUFFER); GLC(GL_STATIC_DRAW); GLC(GL_STREAM_DRAW);
GLC(GL_SRC_ALPHA); GLC(GL_ONE_MINUS_SRC_ALPHA); GLC(GL_SRC_ALPHA); GLC(GL_ONE_MINUS_SRC_ALPHA);
GLC(GL_BLEND); GLC(GL_FLOAT); GLC(GL_ARRAY_BUFFER); GLC(GL_BLEND); GLC(GL_FLOAT); GLC(GL_UNSIGNED_INT); GLC(GL_ARRAY_BUFFER);
return 1; return 1;
} }
@@ -685,13 +763,16 @@ int add_module_gl_constants(PyObject *module) {
#define GL_METHODS \ #define GL_METHODS \
{"enable_automatic_opengl_error_checking", (PyCFunction)enable_automatic_error_checking, METH_O, NULL}, \ {"enable_automatic_opengl_error_checking", (PyCFunction)enable_automatic_error_checking, METH_O, NULL}, \
{"copy_image_sub_data", (PyCFunction)copy_image_sub_data, METH_VARARGS, NULL}, \ {"copy_image_sub_data", (PyCFunction)copy_image_sub_data, METH_VARARGS, NULL}, \
{"replace_or_create_buffer", (PyCFunction)replace_or_create_buffer, METH_VARARGS, NULL}, \
{"glewInit", (PyCFunction)_glewInit, METH_NOARGS, NULL}, \ {"glewInit", (PyCFunction)_glewInit, METH_NOARGS, NULL}, \
{"check_for_extensions", (PyCFunction)check_for_extensions, METH_NOARGS, NULL}, \
METH(Viewport, METH_VARARGS) \ METH(Viewport, METH_VARARGS) \
METH(CheckError, METH_NOARGS) \ METH(CheckError, METH_NOARGS) \
METH(ClearColor, METH_VARARGS) \ METH(ClearColor, METH_VARARGS) \
METH(GetProgramiv, METH_VARARGS) \ METH(GetProgramiv, METH_VARARGS) \
METH(GetShaderiv, METH_VARARGS) \ METH(GetShaderiv, METH_VARARGS) \
METH(Uniform2ui, METH_VARARGS) \ METH(Uniform2ui, METH_VARARGS) \
METH(Uniform2i, METH_VARARGS) \
METH(Uniform1i, METH_VARARGS) \ METH(Uniform1i, METH_VARARGS) \
METH(Uniform2f, METH_VARARGS) \ METH(Uniform2f, METH_VARARGS) \
METH(Uniform4f, METH_VARARGS) \ METH(Uniform4f, METH_VARARGS) \
@@ -702,6 +783,7 @@ int add_module_gl_constants(PyObject *module) {
METH(CompileShader, METH_O) \ METH(CompileShader, METH_O) \
METH(DeleteTexture, METH_O) \ METH(DeleteTexture, METH_O) \
METH(DeleteBuffer, METH_O) \ METH(DeleteBuffer, METH_O) \
METH(DeleteVertexArray, METH_O) \
METH(GetString, METH_O) \ METH(GetString, METH_O) \
METH(GetIntegerv, METH_O) \ METH(GetIntegerv, METH_O) \
METH(Clear, METH_O) \ METH(Clear, METH_O) \
@@ -718,6 +800,7 @@ int add_module_gl_constants(PyObject *module) {
METH(Disable, METH_O) \ METH(Disable, METH_O) \
METH(EnableVertexAttribArray, METH_O) \ METH(EnableVertexAttribArray, METH_O) \
METH(VertexAttribPointer, METH_VARARGS) \ METH(VertexAttribPointer, METH_VARARGS) \
METH(VertexAttribDivisor, METH_VARARGS) \
METH(GetProgramInfoLog, METH_O) \ METH(GetProgramInfoLog, METH_O) \
METH(GetShaderInfoLog, METH_O) \ METH(GetShaderInfoLog, METH_O) \
METH(ActiveTexture, METH_O) \ METH(ActiveTexture, METH_O) \
@@ -735,6 +818,8 @@ int add_module_gl_constants(PyObject *module) {
METH(CopyImageSubData, METH_VARARGS) \ METH(CopyImageSubData, METH_VARARGS) \
METH(TexSubImage3D, METH_VARARGS) \ METH(TexSubImage3D, METH_VARARGS) \
METH(GetTexImage, METH_VARARGS) \ METH(GetTexImage, METH_VARARGS) \
METH(NamedBufferData, METH_VARARGS) \ METH(GetBufferSubData, METH_VARARGS) \
METH(BlendFunc, METH_VARARGS) \ METH(BlendFunc, METH_VARARGS) \
METH(Finish, METH_NOARGS) \
METH(Flush, METH_NOARGS) \

View File

@@ -208,6 +208,18 @@ glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
return Py_BuildValue("s", glfwGetKeyName(key, scancode)); return Py_BuildValue("s", glfwGetKeyName(key, scancode));
} }
PyObject*
glfw_init_hint_string(PyObject UNUSED *self, PyObject *args) {
int hint_id;
char *hint;
if (!PyArg_ParseTuple(args, "is", &hint_id, &hint)) return NULL;
#ifdef glfwInitHintString
glfwInitHintString(hint_id, hint);
#endif
Py_RETURN_NONE;
}
// }}} // }}}
static void static void
@@ -448,6 +460,10 @@ init_glfw(PyObject *m) {
PyEval_InitThreads(); PyEval_InitThreads();
glfwSetErrorCallback(cb_error_callback); glfwSetErrorCallback(cb_error_callback);
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false; #define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false;
#ifdef GLFW_X11_WM_CLASS_NAME
ADDC(GLFW_X11_WM_CLASS_NAME)
ADDC(GLFW_X11_WM_CLASS_CLASS)
#endif
ADDC(GLFW_RELEASE); ADDC(GLFW_RELEASE);
ADDC(GLFW_PRESS); ADDC(GLFW_PRESS);
ADDC(GLFW_REPEAT); ADDC(GLFW_REPEAT);

View File

@@ -17,15 +17,20 @@ PyObject* glfw_wait_events(PyObject UNUSED *self, PyObject*);
PyObject* glfw_post_empty_event(PyObject UNUSED *self); PyObject* glfw_post_empty_event(PyObject UNUSED *self);
PyObject* glfw_get_physical_dpi(PyObject UNUSED *self); PyObject* glfw_get_physical_dpi(PyObject UNUSED *self);
PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args); PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
PyObject* glfw_init_hint_string(PyObject UNUSED *self, PyObject *args);
#ifdef __APPLE__ #ifdef __APPLE__
PyObject* cocoa_hide_titlebar(PyObject UNUSED *self, PyObject *window_id);
PyObject* cocoa_get_lang(PyObject UNUSED *self); PyObject* cocoa_get_lang(PyObject UNUSED *self);
#define COCOA_HIDE_TITLEBAR {"cocoa_hide_titlebar", (PyCFunction)cocoa_hide_titlebar, METH_O, ""}, PyObject* cocoa_make_window_resizable(PyObject UNUSED *self, PyObject *window_id);
#define COCOA_GET_LANG {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, PyObject* cocoa_create_global_menu(PyObject UNUSED *self);
PyObject* cocoa_update_title(PyObject UNUSED *self, PyObject *title);
#define COCOA_FUNCS \
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, \
{"cocoa_make_window_resizable", (PyCFunction)cocoa_make_window_resizable, METH_O, ""}, \
{"cocoa_create_global_menu", (PyCFunction)cocoa_create_global_menu, METH_NOARGS, ""}, \
{"cocoa_update_title", (PyCFunction)cocoa_update_title, METH_O, ""},
#else #else
#define COCOA_HIDE_TITLEBAR #define COCOA_FUNCS
#define COCOA_GET_LANG
#endif #endif
#define GLFW_FUNC_WRAPPERS \ #define GLFW_FUNC_WRAPPERS \
@@ -38,6 +43,5 @@ PyObject* cocoa_get_lang(PyObject UNUSED *self);
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, \ {"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, \
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, \ {"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, \
{"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, \ {"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, \
COCOA_HIDE_TITLEBAR \ {"glfw_init_hint_string", (PyCFunction)glfw_init_hint_string, METH_VARARGS, ""}, \
COCOA_GET_LANG COCOA_FUNCS

View File

@@ -105,6 +105,12 @@ def get_key_map(screen):
return cursor_key_mode_map[screen.cursor_key_mode] return cursor_key_mode_map[screen.cursor_key_mode]
def keyboard_mode_name(screen):
if screen.extended_keyboard:
return 'kitty'
return 'application' if screen.cursor_key_mode else 'normal'
valid_localized_key_names = { valid_localized_key_names = {
k: getattr(defines, 'GLFW_KEY_' + k) k: getattr(defines, 'GLFW_KEY_' + k)
for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
@@ -195,3 +201,11 @@ def interpret_text_event(codepoint, mods, window):
def get_shortcut(keymap, mods, key, scancode): def get_shortcut(keymap, mods, key, scancode):
key = get_localized_key(key, scancode) key = get_localized_key(key, scancode)
return keymap.get((mods & 0b1111, key)) return keymap.get((mods & 0b1111, key))
def get_sent_data(send_text_map, key, scancode, mods, window, action):
if action in (defines.GLFW_PRESS, defines.GLFW_REPEAT):
key = get_localized_key(key, scancode)
m = keyboard_mode_name(window.screen)
keymap = send_text_map[m]
return keymap.get((mods & 0b1111, key))

View File

@@ -231,6 +231,23 @@ map ctrl+shift+equal increase_font_size
map ctrl+shift+minus decrease_font_size map ctrl+shift+minus decrease_font_size
map ctrl+shift+backspace restore_font_size map ctrl+shift+backspace restore_font_size
# Sending arbitrary text on shortcut key presses
# You can tell kitty to send arbitrary (UTF-8) encoded text to
# the client program when pressing specified shortcut keys. For example:
# send_text all ctrl+alt+a Special text
# This will send "Special text" when you press the Ctrl+Alt+a key combination.
# The text to be sent is a python string literal so you can use escapes like
# \x1b to send control codes or \u21fb to send unicode characters (or you can
# just input the unicode characters directly as UTF-8 text). The first argument
# to send_text is the keyboard modes in which to activate the shortcut. The possible
# values are normal or application or kitty or a comma separated combination of them.
# The special keyword all means all modes. The modes normal and application refer to
# the DECCKM cursor key mode for terminals, and kitty refers to the special kitty
# extended keyboard protocol. Another example, that outputs a word and then moves the cursor
# to the start of the line (same as pressing the Home key):
# send_text normal ctrl+alt+a Word\x1b[H
# send_text application ctrl+alt+a Word\x1bOH
# Symbol mapping (special font for specified unicode code points). Map the # Symbol mapping (special font for specified unicode code points). Map the
# specified unicode codepoints to a particular font. Useful if you need special # specified unicode codepoints to a particular font. Useful if you need special
# rendering for some symbols, such as for Powerline. Avoids the need for # rendering for some symbols, such as for Powerline. Avoids the need for

View File

@@ -8,7 +8,6 @@ import os
import sys import sys
import tempfile import tempfile
from gettext import gettext as _ from gettext import gettext as _
from queue import Empty from queue import Empty
from .boss import Boss from .boss import Boss
@@ -20,16 +19,21 @@ from .constants import (
) )
from .fast_data_types import ( from .fast_data_types import (
GL_COLOR_BUFFER_BIT, GLFW_CONTEXT_VERSION_MAJOR, GL_COLOR_BUFFER_BIT, GLFW_CONTEXT_VERSION_MAJOR,
GLFW_CONTEXT_VERSION_MINOR, GLFW_OPENGL_CORE_PROFILE, GLFW_CONTEXT_VERSION_MINOR, GLFW_DECORATED, GLFW_OPENGL_CORE_PROFILE,
GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_PROFILE, GLFW_SAMPLES, GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_PROFILE, GLFW_SAMPLES,
GLFW_STENCIL_BITS, Window, change_wcwidth, GLFW_STENCIL_BITS, Window, change_wcwidth, check_for_extensions,
enable_automatic_opengl_error_checking, glClear, glClearColor, glewInit, enable_automatic_opengl_error_checking, glClear, glClearColor, glewInit,
glfw_init, glfw_set_error_callback, glfw_swap_interval, glfw_terminate, glfw_init, glfw_init_hint_string, glfw_set_error_callback,
glfw_wait_events, glfw_window_hint glfw_swap_interval, glfw_terminate, glfw_wait_events, glfw_window_hint
) )
from .layout import all_layouts from .layout import all_layouts
from .shaders import GL_VERSION from .shaders import GL_VERSION
from .utils import safe_print from .utils import detach, safe_print
try:
from .fast_data_types import GLFW_X11_WM_CLASS_NAME, GLFW_X11_WM_CLASS_CLASS
except ImportError:
GLFW_X11_WM_CLASS_NAME = GLFW_X11_WM_CLASS_CLASS = None
defconf = os.path.join(config_dir, 'kitty.conf') defconf = os.path.join(config_dir, 'kitty.conf')
@@ -95,6 +99,13 @@ def option_parser():
default=False, default=False,
help=_('Output commands received from child process to stdout') help=_('Output commands received from child process to stdout')
) )
if not isosx:
a(
'--detach',
action='store_true',
default=False,
help=_('Detach from the controlling terminal, if any')
)
a( a(
'--replay-commands', '--replay-commands',
default=None, default=None,
@@ -137,7 +148,9 @@ def option_parser():
return parser return parser
def setup_opengl(): def setup_opengl(opts):
if opts.macos_hide_titlebar:
glfw_window_hint(GLFW_DECORATED, False)
glfw_window_hint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSION[0]) glfw_window_hint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSION[0])
glfw_window_hint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSION[1]) glfw_window_hint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSION[1])
glfw_window_hint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) glfw_window_hint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
@@ -175,7 +188,7 @@ def dispatch_pending_calls(boss):
def run_app(opts, args): def run_app(opts, args):
setup_opengl() setup_opengl(opts)
load_cached_values() load_cached_values()
if 'window-size' in cached_values and opts.remember_window_size: if 'window-size' in cached_values and opts.remember_window_size:
ws = cached_values['window-size'] ws = cached_values['window-size']
@@ -188,13 +201,21 @@ def run_app(opts, args):
else: else:
viewport_size.width = opts.initial_window_width viewport_size.width = opts.initial_window_width
viewport_size.height = opts.initial_window_height viewport_size.height = opts.initial_window_height
window = Window(viewport_size.width, viewport_size.height, args.cls) try:
window = Window(viewport_size.width, viewport_size.height, args.cls)
except ValueError:
safe_print('Failed to create GLFW window with initial size:', viewport_size)
viewport_size.width = 640
viewport_size.height = 400
window = Window(viewport_size.width, viewport_size.height, args.cls)
window.set_title(appname) window.set_title(appname)
window.make_context_current() window.make_context_current()
if isosx: if isosx:
from .fast_data_types import cocoa_make_window_resizable, cocoa_create_global_menu
check_for_extensions()
cocoa_create_global_menu()
if opts.macos_hide_titlebar: if opts.macos_hide_titlebar:
from .fast_data_types import cocoa_hide_titlebar cocoa_make_window_resizable(window.cocoa_window_id())
cocoa_hide_titlebar(window.cocoa_window_id())
else: else:
with open(logo_data_file, 'rb') as f: with open(logo_data_file, 'rb') as f:
window.set_icon(f.read(), 256, 256) window.set_icon(f.read(), 256, 256)
@@ -241,11 +262,25 @@ def ensure_osx_locale():
def main(): def main():
if isosx: if isosx:
ensure_osx_locale() ensure_osx_locale()
locale.setlocale(locale.LC_ALL, '') try:
locale.setlocale(locale.LC_ALL, '')
except Exception:
if not isosx:
raise
print('Failed to set locale with LANG:', os.environ.get('LANG'), file=sys.stderr)
os.environ.pop('LANG')
try:
locale.setlocale(locale.LC_ALL, '')
except Exception:
print('Failed to set locale with no LANG, ignoring', file=sys.stderr)
if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES',
None) == '1' and getattr(sys, 'frozen', True): None) == '1' and getattr(sys, 'frozen', True):
os.chdir(os.path.expanduser('~')) os.chdir(os.path.expanduser('~'))
if not os.path.isdir(os.getcwd()):
os.chdir(os.path.expanduser('~'))
args = option_parser().parse_args() args = option_parser().parse_args()
if getattr(args, 'detach', False):
detach()
if args.cmd: if args.cmd:
exec(args.cmd) exec(args.cmd)
return return
@@ -259,6 +294,8 @@ def main():
change_wcwidth(not opts.use_system_wcwidth) change_wcwidth(not opts.use_system_wcwidth)
glfw_set_error_callback(on_glfw_error) glfw_set_error_callback(on_glfw_error)
enable_automatic_opengl_error_checking(args.debug_gl) enable_automatic_opengl_error_checking(args.debug_gl)
if GLFW_X11_WM_CLASS_CLASS is not None:
glfw_init_hint_string(GLFW_X11_WM_CLASS_CLASS, opts.cls)
if not glfw_init(): if not glfw_init():
raise SystemExit('GLFW initialization failed') raise SystemExit('GLFW initialization failed')
try: try:

View File

@@ -58,5 +58,5 @@ def encode_mouse_event(tracking_mode, tracking_protocol, button, action, mods, x
ans = bytes(ans) ans = bytes(ans)
else: else:
if x <= 223 and y <= 223: if x <= 223 and y <= 223:
ans = bytearray([0o33, ord('['), cb + 32, x + 32, y + 32]) ans = bytearray([0o33, ord('['), ord('M'), cb + 32, x + 32, y + 32])
return ans return ans

View File

@@ -7,6 +7,7 @@
#include "data-types.h" #include "data-types.h"
#include <structmember.h> #include <structmember.h>
#include <limits.h>
#include "unicode-data.h" #include "unicode-data.h"
#include "tracker.h" #include "tracker.h"
#include "modes.h" #include "modes.h"
@@ -1332,6 +1333,14 @@ static PyGetSetDef getsetters[] = {
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };
#if UINT_MAX == UINT32_MAX
#define T_COL T_UINT
#elif ULONG_MAX == UINT32_MAX
#define T_COL T_ULONG
#else
#error Neither int nor long is 4-bytes in size
#endif
static PyMemberDef members[] = { static PyMemberDef members[] = {
{"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"}, {"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"},
{"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"}, {"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"},
@@ -1341,11 +1350,11 @@ static PyMemberDef members[] = {
{"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"}, {"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"},
{"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"}, {"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"},
{"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"}, {"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"},
{"default_fg", T_ULONG, offsetof(Screen, default_fg), 0, "default_fg"}, {"default_fg", T_COL, offsetof(Screen, default_fg), 0, "default_fg"},
{"default_bg", T_ULONG, offsetof(Screen, default_bg), 0, "default_bg"}, {"default_bg", T_COL, offsetof(Screen, default_bg), 0, "default_bg"},
{"highlight_fg", T_ULONG, offsetof(Screen, highlight_fg), 0, "highlight_fg"}, {"highlight_fg", T_COL, offsetof(Screen, highlight_fg), 0, "highlight_fg"},
{"highlight_bg", T_ULONG, offsetof(Screen, highlight_bg), 0, "highlight_bg"}, {"highlight_bg", T_COL, offsetof(Screen, highlight_bg), 0, "highlight_bg"},
{"cursor_color", T_ULONG, offsetof(Screen, cursor_color), 0, "cursor_color"}, {"cursor_color", T_COL, offsetof(Screen, cursor_color), 0, "cursor_color"},
{NULL} {NULL}
}; };

View File

@@ -2,39 +2,106 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys import sys
from collections import namedtuple
from contextlib import contextmanager
from ctypes import addressof, sizeof from ctypes import addressof, sizeof
from functools import lru_cache from functools import lru_cache
from threading import Lock from threading import Lock
from .fonts.render import render_cell
from .fast_data_types import ( from .fast_data_types import (
glCreateProgram, glAttachShader, GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, BOLD, GL_ARRAY_BUFFER, GL_CLAMP_TO_EDGE, GL_COMPILE_STATUS, GL_FLOAT,
glLinkProgram, GL_TRUE, GL_LINK_STATUS, glGetProgramiv, GL_FRAGMENT_SHADER, GL_LINK_STATUS, GL_MAX_ARRAY_TEXTURE_LAYERS,
glGetProgramInfoLog, glDeleteShader, glDeleteProgram, glGenVertexArrays, GL_MAX_TEXTURE_SIZE, GL_NEAREST, GL_R8, GL_RED, GL_STREAM_DRAW,
glCreateShader, glShaderSource, glCompileShader, glGetShaderiv, GL_TEXTURE0, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER,
GL_COMPILE_STATUS, glGetShaderInfoLog, glGetUniformLocation, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_TRUE,
glGetAttribLocation, glUseProgram, glBindVertexArray, GL_TEXTURE0, GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_VERTEX_SHADER, ITALIC, SpriteMap,
GL_TEXTURE1, glGetIntegerv, GL_MAX_ARRAY_TEXTURE_LAYERS, glNamedBufferData, copy_image_sub_data, glActiveTexture, glAttachShader, glBindBuffer,
GL_MAX_TEXTURE_SIZE, glDeleteTexture, GL_TEXTURE_2D_ARRAY, glGenTextures, glBindTexture, glBindVertexArray, glCompileShader, glCopyImageSubData,
glBindTexture, glTexParameteri, GL_CLAMP_TO_EDGE, glDeleteBuffer, glCreateProgram, glCreateShader, glDeleteBuffer, glDeleteProgram,
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, glDeleteShader, glDeleteTexture, glDeleteVertexArray,
GL_NEAREST, GL_TEXTURE_WRAP_T, glGenBuffers, GL_R8, GL_RED, glEnableVertexAttribArray, glGenBuffers, glGenTextures, glGenVertexArrays,
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_STATIC_DRAW, GL_STREAM_DRAW, glGetAttribLocation, glGetBufferSubData, glGetIntegerv,
GL_TEXTURE_BUFFER, GL_RGB32UI, GL_FLOAT, GL_ARRAY_BUFFER, glBindBuffer, glGetProgramInfoLog, glGetProgramiv, glGetShaderInfoLog, glGetShaderiv,
glPixelStorei, glTexBuffer, glActiveTexture, glTexStorage3D, glGetUniformLocation, glLinkProgram, glPixelStorei, glShaderSource,
glCopyImageSubData, glTexSubImage3D, ITALIC, BOLD, SpriteMap, glTexParameteri, glTexStorage3D, glTexSubImage3D, glUseProgram,
glEnableVertexAttribArray, glVertexAttribPointer, copy_image_sub_data glVertexAttribDivisor, glVertexAttribPointer, replace_or_create_buffer
) )
from .fonts.render import render_cell
from .utils import safe_print from .utils import safe_print
GL_VERSION = (3, 3) GL_VERSION = (3, 3)
VERSION = GL_VERSION[0] * 100 + GL_VERSION[1] * 10 VERSION = GL_VERSION[0] * 100 + GL_VERSION[1] * 10
ITALIC_MASK = 1 << ITALIC ITALIC_MASK = 1 << ITALIC
BOLD_MASK = 1 << BOLD BOLD_MASK = 1 << BOLD
BASE = os.path.dirname(os.path.abspath(__file__))
VertexArray = namedtuple('VertexArray', 'name size dtype normalized stride offset divisor')
class Sprites: @lru_cache()
def load_shaders(name):
vert = open(os.path.join(BASE, '{}_vertex.glsl'.format(name))).read()
frag = open(os.path.join(BASE, '{}_fragment.glsl'.format(name))).read()
return vert, frag
class BufferManager: # {{{
def __init__(self):
self.sizes = {}
self.types = {}
self.ctypes_types = {}
self.name_count = 0
def create(self, for_use=GL_ARRAY_BUFFER):
buf_id = glGenBuffers(1)
self.types[buf_id] = for_use
self.sizes.pop(buf_id, None)
self.ctypes_types.pop(buf_id, None)
return buf_id
def delete(self, buf_id):
if buf_id in self.types:
glDeleteBuffer(buf_id)
self.sizes.pop(buf_id, None)
self.types.pop(buf_id)
self.ctypes_types.pop(buf_id, None)
def set_data(self, buf_id, data, usage=GL_STREAM_DRAW, verify=False):
prev_sz = self.sizes.get(buf_id, 0)
new_sz = sizeof(data)
replace_or_create_buffer(buf_id, new_sz, prev_sz, addressof(data), usage, self.types[buf_id])
self.sizes[buf_id] = new_sz
self.ctypes_types[buf_id] = type(data)
if verify:
verify_data = self.get_data(buf_id)
if list(data) != list(verify_data):
raise RuntimeError('OpenGL failed to upload to buffer')
def get_data(self, buf_id):
verify_data = self.ctypes_types[buf_id]()
glGetBufferSubData(self.types[buf_id], buf_id, self.sizes[buf_id], 0, addressof(verify_data))
return verify_data
def bind(self, buf_id):
glBindBuffer(self.types[buf_id], buf_id)
def unbind(self, buf_id):
glBindBuffer(self.types[buf_id], 0)
@contextmanager
def bound_buffer(self, buf_id):
self.bind(buf_id)
yield
self.unbind(buf_id)
buffer_manager = BufferManager()
# }}}
class Sprites: # {{{
''' Maintain sprite sheets of all rendered characters on the GPU as a texture ''' Maintain sprite sheets of all rendered characters on the GPU as a texture
array with each texture being a sprite sheet. ''' array with each texture being a sprite sheet. '''
@@ -43,15 +110,14 @@ class Sprites:
def __init__(self): def __init__(self):
self.xnum = self.ynum = 1 self.xnum = self.ynum = 1
self.sampler_num = 0
self.buffer_sampler_num = 1
self.first_cell_cache = {} self.first_cell_cache = {}
self.second_cell_cache = {} self.second_cell_cache = {}
self.x = self.y = self.z = 0 self.x = self.y = self.z = 0
self.texture_id = self.buffer_texture_id = None self.texture_id = None
self.last_num_of_layers = 1 self.last_num_of_layers = 1
self.last_ynum = -1 self.last_ynum = -1
self.texture_unit = GL_TEXTURE0 self.sampler_num = 0
self.texture_unit = GL_TEXTURE0 + self.sampler_num
self.backend = SpriteMap(glGetIntegerv(GL_MAX_TEXTURE_SIZE), glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS)) self.backend = SpriteMap(glGetIntegerv(GL_MAX_TEXTURE_SIZE), glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS))
self.lock = Lock() self.lock = Lock()
@@ -103,7 +169,6 @@ class Sprites:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1) glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
x, y = x * self.cell_width, y * self.cell_height x, y = x * self.cell_width, y * self.cell_height
glTexSubImage3D(tgt, 0, x, y, z, self.cell_width, self.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, addressof(buf)) glTexSubImage3D(tgt, 0, x, y, z, self.cell_width, self.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, addressof(buf))
glBindTexture(tgt, 0)
def realloc_texture(self): def realloc_texture(self):
tgt = GL_TEXTURE_2D_ARRAY tgt = GL_TEXTURE_2D_ARRAY
@@ -138,51 +203,28 @@ class Sprites:
self.last_num_of_layers = znum self.last_num_of_layers = znum
self.last_ynum = self.backend.ynum self.last_ynum = self.backend.ynum
self.texture_id = tex self.texture_id = tex
glBindTexture(tgt, 0)
def destroy(self): def destroy(self):
if self.texture_id is not None: if self.texture_id is not None:
glDeleteTexture(self.texture_id) glDeleteTexture(self.texture_id)
self.texture_id = None self.texture_id = None
if self.buffer_texture_id is not None:
glDeleteTexture(self.buffer_texture_id)
def ensure_state(self): def ensure_state(self):
if self.texture_id is None: if self.texture_id is None:
self.realloc_texture() self.realloc_texture()
self.buffer_texture_id = glGenTextures(1)
self.buffer_texture_unit = GL_TEXTURE1
def add_sprite_map(self):
return glGenBuffers(1)
def set_sprite_map(self, buf_id, data, usage=GL_STREAM_DRAW):
self.bind_sprite_map(buf_id)
glNamedBufferData(buf_id, sizeof(data), addressof(data), usage)
def bind_sprite_map(self, buf_id):
glBindBuffer(GL_TEXTURE_BUFFER, buf_id)
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32UI, buf_id)
def destroy_sprite_map(self, buf_id):
glDeleteBuffer(buf_id)
def __enter__(self): def __enter__(self):
self.ensure_state() self.ensure_state()
glActiveTexture(self.texture_unit) glActiveTexture(self.texture_unit)
glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture_id) glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture_id)
glActiveTexture(self.buffer_texture_unit)
glBindTexture(GL_TEXTURE_BUFFER, self.buffer_texture_id)
def __exit__(self, *a): def __exit__(self, *a):
glBindTexture(GL_TEXTURE_2D_ARRAY, 0) glBindTexture(GL_TEXTURE_2D_ARRAY, 0)
glBindTexture(GL_TEXTURE_BUFFER, 0)
glBindBuffer(GL_TEXTURE_BUFFER, 0) # }}}
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32UI, 0)
class ShaderProgram: class ShaderProgram: # {{{
""" Helper class for using GLSL shader programs """ """ Helper class for using GLSL shader programs """
def __init__(self, vertex, fragment): def __init__(self, vertex, fragment):
@@ -206,23 +248,43 @@ class ShaderProgram:
raise ValueError('Error linking shader program: \n%s' % info.decode('utf-8')) raise ValueError('Error linking shader program: \n%s' % info.decode('utf-8'))
glDeleteShader(vs_id) glDeleteShader(vs_id)
glDeleteShader(frag_id) glDeleteShader(frag_id)
self.vao_id = glGenVertexArrays(1) self.vertex_arrays = {}
self.buffers = {}
def add_vertex_array(self, name, size=3, dtype=GL_FLOAT, normalized=False, stride=0, offset=0): def vertex_array(self, name, size=3, dtype=GL_FLOAT, normalized=False, stride=0, offset=0, divisor=0):
glBindVertexArray(self.vao_id) return VertexArray(name, size, dtype, normalized, stride, offset, divisor)
if name not in self.buffers:
self.buffers[name] = glGenBuffers(1) def add_vertex_arrays(self, *arrays):
glBindBuffer(GL_ARRAY_BUFFER, self.buffers[name]) vao_id = glGenVertexArrays(1)
aid = self.attribute_location(name) self.vertex_arrays[vao_id] = buf_id = buffer_manager.create(for_use=GL_ARRAY_BUFFER)
glEnableVertexAttribArray(aid) with self.bound_vertex_array(vao_id), buffer_manager.bound_buffer(buf_id):
glVertexAttribPointer(aid, size, dtype, normalized, stride, offset) for x in arrays:
glBindBuffer(GL_ARRAY_BUFFER, 0) aid = self.attribute_location(x.name)
if aid > -1:
glEnableVertexAttribArray(aid)
glVertexAttribPointer(aid, x.size, x.dtype, x.normalized, x.stride, x.offset)
if x.divisor > 0:
glVertexAttribDivisor(aid, x.divisor)
return vao_id
@contextmanager
def bound_vertex_array(self, vao_id):
glBindVertexArray(vao_id)
yield
glBindVertexArray(0) glBindVertexArray(0)
def send_vertex_data(self, name, data, usage=GL_STATIC_DRAW): def remove_vertex_array(self, vao_id):
bufid = self.buffers[name] buf_id = self.vertex_arrays.pop(vao_id, None)
glNamedBufferData(bufid, sizeof(data), addressof(data), usage) if buf_id is not None:
glDeleteVertexArray(vao_id)
buffer_manager.delete(buf_id)
def send_vertex_data(self, vao_id, data, usage=GL_STREAM_DRAW):
bufid = self.vertex_arrays[vao_id]
buffer_manager.set_data(bufid, data, usage=usage)
def get_vertex_data(self, vao_id):
bufid = self.vertex_arrays[vao_id]
return buffer_manager.get_data(bufid)
def __hash__(self) -> int: def __hash__(self) -> int:
return self.program_id return self.program_id
@@ -260,8 +322,7 @@ class ShaderProgram:
def __enter__(self): def __enter__(self):
glUseProgram(self.program_id) glUseProgram(self.program_id)
glBindVertexArray(self.vao_id)
def __exit__(self, *args): def __exit__(self, *args):
glUseProgram(0) glUseProgram(0)
glBindVertexArray(0) # }}}

View File

@@ -3,18 +3,23 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from collections import deque from collections import deque
from ctypes import addressof
from functools import partial from functools import partial
from threading import Lock from threading import Lock
from ctypes import addressof
from .borders import Borders
from .char_grid import calculate_gl_geometry, render_cells
from .child import Child from .child import Child
from .config import build_ansi_color_table from .config import build_ansi_color_table
from .constants import get_boss, appname, shell_path, cell_size, queue_action, viewport_size, WindowGeometry, GLuint from .constants import (
from .fast_data_types import glfw_post_empty_event, Screen, DECAWM, DATA_CELL_SIZE, ColorProfile GLuint, WindowGeometry, appname, cell_size, get_boss, queue_action,
from .char_grid import calculate_gl_geometry, render_cells shell_path, viewport_size
from .layout import all_layouts, Rect )
from .fast_data_types import (
DATA_CELL_SIZE, DECAWM, ColorProfile, Screen, glfw_post_empty_event
)
from .layout import Rect, all_layouts
from .utils import color_as_int from .utils import color_as_int
from .borders import Borders
from .window import Window from .window import Window
@@ -32,9 +37,12 @@ class Tab:
self.borders = Borders(opts) self.borders = Borders(opts)
self.windows = deque() self.windows = deque()
self.active_window_idx = 0 self.active_window_idx = 0
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
setattr(self, which + '_window', partial(self.nth_window, num=i))
if session_tab is None: if session_tab is None:
self.cwd = args.directory self.cwd = args.directory
l = self.enabled_layouts[0] l = self.enabled_layouts[0]
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
if special_window is None: if special_window is None:
queue_action(self.new_window) queue_action(self.new_window)
else: else:
@@ -42,10 +50,8 @@ class Tab:
else: else:
self.cwd = session_tab.cwd or args.directory self.cwd = session_tab.cwd or args.directory
l = session_tab.layout l = session_tab.layout
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
queue_action(self.startup, session_tab) queue_action(self.startup, session_tab)
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
setattr(self, which + '_window', partial(self.nth_window, num=i))
def startup(self, session_tab): def startup(self, session_tab):
for cmd in session_tab.windows: for cmd in session_tab.windows:
@@ -201,7 +207,7 @@ class TabManager:
def __init__(self, opts, args, startup_session): def __init__(self, opts, args, startup_session):
self.opts, self.args = opts, args self.opts, self.args = opts, args
self.buffer_id = None self.vao_id = None
self.tabbar_lock = Lock() self.tabbar_lock = Lock()
self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs] self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs]
self.cell_ranges = [] self.cell_ranges = []
@@ -305,7 +311,7 @@ class TabManager:
if needs_resize: if needs_resize:
queue_action(get_boss().tabbar_visibility_changed) queue_action(get_boss().tabbar_visibility_changed)
def update_tab_bar_data(self, sprites): def update_tab_bar_data(self, sprites, cell_program):
s = self.tab_bar_screen s = self.tab_bar_screen
s.cursor.x = 0 s.cursor.x = 0
s.erase_in_line(2, False) s.erase_in_line(2, False)
@@ -335,9 +341,9 @@ class TabManager:
s.update_cell_data( s.update_cell_data(
sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True) sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True)
sprites.render_dirty_cells() sprites.render_dirty_cells()
if self.buffer_id is None: if self.vao_id is None:
self.buffer_id = sprites.add_sprite_map() self.vao_id = cell_program.create_sprite_map()
sprites.set_sprite_map(self.buffer_id, self.sprite_map) cell_program.send_vertex_data(self.vao_id, self.sprite_map)
def activate_tab_at(self, x): def activate_tab_at(self, x):
x = (x - self.window_geometry.left) // cell_size.width x = (x - self.window_geometry.left) // cell_size.width
@@ -357,5 +363,5 @@ class TabManager:
return return
with self.tabbar_lock: with self.tabbar_lock:
if self.tabbar_dirty: if self.tabbar_dirty:
self.update_tab_bar_data(sprites) self.update_tab_bar_data(sprites, cell_program)
render_cells(self.buffer_id, self.screen_geometry, cell_program, sprites) render_cells(self.vao_id, self.screen_geometry, cell_program, sprites)

View File

@@ -14,7 +14,7 @@ from functools import lru_cache
from time import monotonic from time import monotonic
from .constants import isosx from .constants import isosx
from .fast_data_types import glfw_get_physical_dpi, wcwidth as wcwidth_impl from .fast_data_types import glfw_get_physical_dpi, wcwidth as wcwidth_impl, redirect_std_streams
from .rgb import Color, to_color from .rgb import Color, to_color
@@ -181,7 +181,7 @@ def get_primary_selection():
return '' # There is no primary selection on OS X return '' # There is no primary selection on OS X
# glfw has no way to get the primary selection # glfw has no way to get the primary selection
# https://github.com/glfw/glfw/issues/894 # https://github.com/glfw/glfw/issues/894
return subprocess.check_output(['xsel', '-p']).decode('utf-8') return subprocess.check_output(['xsel', '-p'], stderr=open(os.devnull, 'wb'), stdin=open(os.devnull, 'rb')).decode('utf-8')
def base64_encode( def base64_encode(
@@ -203,7 +203,7 @@ def set_primary_selection(text):
return # There is no primary selection on OS X return # There is no primary selection on OS X
if isinstance(text, str): if isinstance(text, str):
text = text.encode('utf-8') text = text.encode('utf-8')
p = subprocess.Popen(['xsel', '-i', '-p'], stdin=subprocess.PIPE) p = subprocess.Popen(['xsel', '-i', '-p'], stdin=subprocess.PIPE, stdout=open(os.devnull, 'wb'), stderr=subprocess.STDOUT)
p.stdin.write(text), p.stdin.close() p.stdin.write(text), p.stdin.close()
p.wait() p.wait()
@@ -215,3 +215,14 @@ def open_url(url, program='default'):
cmd = shlex.split(program) cmd = shlex.split(program)
cmd.append(url) cmd.append(url)
subprocess.Popen(cmd).wait() subprocess.Popen(cmd).wait()
def detach(fork=True, setsid=True, redirect=True):
if fork:
# Detach from the controlling process.
if os.fork() != 0:
raise SystemExit(0)
if setsid:
os.setsid()
if redirect:
redirect_std_streams(os.devnull)

View File

@@ -319,7 +319,7 @@ def package(args, for_bundle=False): # {{{
def src_ignore(parent, entries): def src_ignore(parent, entries):
return [ return [
x for x in entries x for x in entries
if '.' in x and x.rpartition('.')[2] not in ('py', 'so', 'conf') if '.' in x and x.rpartition('.')[2] not in ('py', 'so', 'conf', 'glsl')
] ]
shutil.copytree('kitty', os.path.join(libdir, 'kitty'), ignore=src_ignore) shutil.copytree('kitty', os.path.join(libdir, 'kitty'), ignore=src_ignore)