Compare commits

..

68 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
Kovid Goyal
9a103f3979 version 0.2.7 2017-07-14 10:46:35 +05:30
Kovid Goyal
270dde7020 Use erase in line instead as it is more efficient 2017-07-13 19:32:18 +05:30
Kovid Goyal
11de18e737 Fix #91 2017-07-13 19:24:26 +05:30
Kovid Goyal
62db44c71e macOS: Ensure the LANG environment variable is set
Fixes #90
2017-06-25 20:44:16 +05:30
Kovid Goyal
3d5c65eaea Fix #89 2017-06-21 10:19:45 +05:30
Kovid Goyal
ea298f95f2 Another place we can use exist_ok 2017-06-09 00:00:02 +05:30
Kovid Goyal
2f21e0e341 Merge branch 'patch-2' of https://github.com/mimi1vx/kitty 2017-06-08 23:59:14 +05:30
Ondřej Súkup
ae62d36a4a Remove unneeded try except block
os.makedirs has from python-3.2 option `exist_ok`
2017-06-08 20:25:34 +02:00
Kovid Goyal
76e3101d9b Hide all symbols 2017-06-07 11:19:53 +05:30
Kovid Goyal
c3442545a8 Add a note about changing mouse wheel scroll direction 2017-06-06 00:06:43 +05:30
Kovid Goyal
3a9b0faa06 Be more positive ;) 2017-06-06 00:03:43 +05:30
Kovid Goyal
4989b1f8bb ... 2017-06-05 23:58:36 +05:30
Kovid Goyal
348fe4ada4 Option for window padding
Fixes #85
2017-06-05 23:57:17 +05:30
Kovid Goyal
bbc6b2d86a Option to size window margin (blank area outside window borders)
Defaults to zero
2017-06-05 22:27:47 +05:30
Kovid Goyal
b4d4ed718f version 0.2.6 2017-06-03 09:58:38 +05:30
Kovid Goyal
836724709e Add an option to hide the window title bar on macOS
Fixes #84
2017-06-03 09:57:29 +05:30
Kovid Goyal
96d2567815 Fix compilation with gcc >= 7
Requires explicit fallthrough comments in switch statements
2017-06-03 08:59:09 +05:30
Kovid Goyal
419f43ceed Fix deprecation warning on macOS 10.12 2017-06-03 08:58:11 +05:30
Kovid Goyal
47851ebb1b Fix conversion of type 2 terminal colors not working because of missing break 2017-06-03 08:51:00 +05:30
Kovid Goyal
24a4fbd987 Add a function to hide the title bar on OS X 2017-06-03 08:45:27 +05:30
28 changed files with 1084 additions and 415 deletions

View File

@@ -3,13 +3,16 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from ctypes import addressof
from functools import partial
from itertools import chain
from threading import Lock
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 .utils import get_dpi
from .utils import pt_to_px
def as_color(c):
@@ -31,7 +34,8 @@ def as_rect(left, top, right, bottom, color=0):
class BordersProgram(ShaderProgram):
def __init__(self):
ShaderProgram.__init__(self, '''\
ShaderProgram.__init__(
self, '''\
uniform vec3 colors[3];
in vec3 rect;
out vec3 color;
@@ -48,43 +52,82 @@ void main() {
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):
self.send_vertex_data('rect', data)
self.send_vertex_data(self.vao_id, data, usage=GL_STATIC_DRAW)
def set_colors(self, color_buf):
glUniform3fv(self.uniform_location('colors'), 3, addressof(color_buf))
def border_maker(rects):
' Create a function that will add all the rectangles for drawing a border to rects '
def r(l, t, b, r, color):
rects.extend(as_rect(l, t, b, r, color))
def vertical_edge(color, width, top, bottom, left):
r(left, top, left + width, bottom, color)
def horizontal_edge(color, height, left, right, top):
r(left, top, right, top + height, color)
def edge(func, color, sz, a, b):
return partial(func, color, sz, a, b)
def border(color, sz, left, top, right, bottom):
horz = edge(horizontal_edge, color, sz, left, right)
horz(top), horz(bottom - sz) # top, bottom edges
vert = edge(vertical_edge, color, sz, top, bottom)
vert(left), vert(right - sz) # left, right edges
return border
class Borders:
def __init__(self, opts):
self.is_dirty = False
self.lock = Lock()
self.can_render = False
dpix, dpiy = get_dpi()['logical']
dpi = (dpix + dpiy) / 2
self.border_width = round(opts.window_border_width * dpi / 72)
self.border_width = pt_to_px(opts.window_border_width)
self.padding_width = pt_to_px(opts.window_padding_width)
self.color_buf = (GLfloat * 9)(
*as_color(opts.background),
*as_color(opts.active_border_color),
*as_color(opts.inactive_border_color)
)
*as_color(opts.background), *as_color(opts.active_border_color),
*as_color(opts.inactive_border_color))
def __call__(self, windows, active_window, current_layout, extra_blank_rects, draw_window_borders=True):
def __call__(
self,
windows,
active_window,
current_layout,
extra_blank_rects,
draw_window_borders=True
):
rects = []
for br in chain(current_layout.blank_rects, extra_blank_rects):
rects.extend(as_rect(*br))
if draw_window_borders and self.border_width > 0:
bw = self.border_width
bw, pw = self.border_width, self.padding_width
fw = bw + pw
border = border_maker(rects)
if fw > 0:
for w in windows:
g = w.geometry
color = 1 if w is active_window else 2
rects.extend(as_rect(g.left - bw, g.top - bw, g.left, g.bottom + bw, color))
rects.extend(as_rect(g.left - bw, g.top - bw, g.right + bw, g.top, color))
rects.extend(as_rect(g.right, g.top - bw, g.right + bw, g.bottom + bw, color))
rects.extend(as_rect(g.left - bw, g.bottom, g.right + bw, g.bottom + bw, color))
if bw > 0 and draw_window_borders:
# Draw the border rectangles
color = 1 if w is active_window else 2
border(
color, bw, g.left - fw, g.top - fw, g.right + fw,
g.bottom + fw)
if pw > 0:
# Draw the background rectangles over the padding region
color = 0
border(
color, pw, g.left - pw, g.top - pw, g.right + pw,
g.bottom + pw)
with self.lock:
self.num_of_rects = len(rects) // 12
self.rects = (GLfloat * len(rects))()
@@ -108,4 +151,8 @@ class Borders:
program.send_data(self.rects)
program.set_colors(self.color_buf)
self.is_dirty = False
glMultiDrawArrays(GL_TRIANGLE_FAN, addressof(self.starts), addressof(self.counts), self.num_of_rects)
with program.bound_vertex_array(program.vao_id):
glMultiDrawArrays(
GL_TRIANGLE_FAN,
addressof(self.starts),
addressof(self.counts), self.num_of_rects)

View File

@@ -2,38 +2,42 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
import inspect
import io
import os
import select
import signal
import struct
import inspect
from functools import wraps
from gettext import gettext as _
from queue import Empty, Queue
from threading import Thread, current_thread
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 .constants import (
viewport_size, set_boss, wakeup, cell_size, MODIFIER_KEYS,
main_thread, mouse_button_pressed, mouse_cursor_pos
MODIFIER_KEYS, cell_size, is_key_pressed, isosx, main_thread,
mouse_button_pressed, mouse_cursor_pos, set_boss, viewport_size, wakeup
)
from .fast_data_types import (
glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GLFW_PRESS,
GLFW_REPEAT, GLFW_MOUSE_BUTTON_1, glfw_post_empty_event,
GLFW_CURSOR_NORMAL, GLFW_CURSOR, GLFW_CURSOR_HIDDEN, drain_read
GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT,
drain_read, glBlendFunc, glfw_post_empty_event, glViewport
)
from .fonts.render import set_font_family
from .borders import BordersProgram
from .char_grid import cursor_shader, cell_shader
from .constants import is_key_pressed
from .keys import interpret_text_event, interpret_key_event, get_shortcut
from .keys import (
get_sent_data, get_shortcut, interpret_key_event, interpret_text_event
)
from .session import create_session
from .shaders import Sprites, ShaderProgram
from .tabs import TabManager, SpecialWindow
from .shaders import Sprites
from .tabs import SpecialWindow, TabManager
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):
@@ -59,6 +63,7 @@ def callback(func):
pass
else:
self.queue_action(conditional_run, w, i)
return f
@@ -80,7 +85,9 @@ class Boss(Thread):
self.screen_update_delay = opts.repaint_delay / 1000.0
self.signal_fd = handle_unix_signals()
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.timers = Timers()
self.ui_timers = Timers()
@@ -100,8 +107,7 @@ class Boss(Thread):
glfw_window.window_focus_callback = self.on_focus
self.tab_manager = TabManager(opts, args, startup_session)
self.sprites = Sprites()
self.cell_program = ShaderProgram(*cell_shader)
self.cursor_program = ShaderProgram(*cursor_shader)
self.cell_program, self.cursor_program = load_shader_programs()
self.borders_program = BordersProgram()
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
self.sprites.do_layout(cell_size.width, cell_size.height)
@@ -205,8 +211,10 @@ class Boss(Thread):
def loop(self):
while not self.shutting_down:
all_readers = list(self.read_dispatch_map)
all_writers = [w.child_fd for w in self.iterwindows() if w.write_buf]
readers, writers, _ = select.select(all_readers, all_writers, [], self.timers.timeout())
all_writers = [
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:
self.read_dispatch_map[r]()
for w in writers:
@@ -214,7 +222,8 @@ class Boss(Thread):
self.timers()
for w in self.iterwindows():
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
def on_window_resize(self, window, w, h):
@@ -231,10 +240,16 @@ class Boss(Thread):
glfw_post_empty_event()
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):
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):
self.change_font_size(self.opts.font_size)
@@ -308,7 +323,9 @@ class Boss(Thread):
return
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS:
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:
window.write_to_child(data)
@@ -326,7 +343,9 @@ class Boss(Thread):
else:
tab = self.active_tab
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):
tab = self.active_tab
@@ -365,7 +384,8 @@ class Boss(Thread):
@callback
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()
w = self.window_for_pos(xpos, ypos)
if w is not None:
@@ -387,7 +407,8 @@ class Boss(Thread):
def show_mouse_cursor(self):
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_NORMAL)
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):
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_HIDDEN)
@@ -399,12 +420,14 @@ class Boss(Thread):
try:
self.glfw_window.request_window_attention()
except AttributeError:
pass # needs glfw 3.3
pass # needs glfw 3.3
def start_cursor_blink(self):
self.cursor_blinking = True
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):
self.cursor_blinking = False
@@ -420,15 +443,22 @@ class Boss(Thread):
if tab.title != self.glfw_window_title:
self.glfw_window_title = tab.title
self.glfw_window.set_title(self.glfw_window_title)
if isosx:
cocoa_update_title(self.glfw_window_title)
with self.sprites:
self.sprites.render_dirty_cells()
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:
self.tab_manager.render(self.cell_program, self.sprites)
for window, rd in render_data.items():
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
rd = render_data.get(active)
if rd is not None:
@@ -439,15 +469,16 @@ class Boss(Thread):
d = int(self.opts.cursor_blink_interval * 1000)
n = t // d
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:
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):
if window.char_grid.buffer_id is not None:
self.sprites.destroy_sprite_map(window.char_grid.buffer_id)
window.char_grid.buffer_id = None
window.char_grid.destroy(self.cell_program)
for tab in self.tab_manager:
if window in tab:
break
@@ -499,5 +530,8 @@ class Boss(Thread):
self.queue_action(self.tab_manager.move_tab, -1)
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 sys
from enum import Enum
from collections import namedtuple
from ctypes import addressof, memmove, sizeof
from enum import Enum
from threading import Lock
from .config import build_ansi_color_table, defaults
@@ -15,11 +15,12 @@ from .constants import (
)
from .fast_data_types import (
CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DATA_CELL_SIZE, GL_BLEND,
GL_LINE_LOOP, GL_TRIANGLE_FAN, ColorProfile, glDisable, glDrawArrays,
glDrawArraysInstanced, glEnable, glUniform1i, glUniform2f, glUniform2ui,
glUniform4f
GL_LINE_LOOP, GL_TRIANGLE_FAN, GL_UNSIGNED_INT, ColorProfile, glDisable,
glDrawArrays, glDrawArraysInstanced, glEnable, glUniform1i, glUniform2f,
glUniform2i, glUniform2ui, glUniform4f
)
from .rgb import to_color
from .shaders import ShaderProgram, load_shaders
from .utils import (
color_as_int, color_from_int, get_logical_dpi, open_url, safe_print,
set_primary_selection
@@ -32,133 +33,22 @@ class DynamicColor(Enum):
default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6)
if DATA_CELL_SIZE % 3:
raise ValueError('Incorrect data cell size, must be a multiple of 3')
class CellProgram(ShaderProgram):
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 {{{
cell_shader = (
'''\
uniform uvec2 dimensions; // xnum, ynum
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;
}
''')
# }}}
def load_shader_programs():
cell = CellProgram(*load_shaders('cell'))
cursor = ShaderProgram(*load_shaders('cursor'))
cursor.vao_id = cursor.add_vertex_arrays()
return cell, cursor
class Selection: # {{{
@@ -230,16 +120,15 @@ def calculate_gl_geometry(window_geometry):
return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy)
def render_cells(buffer_id, sg, cell_program, sprites, invert_colors=False):
sprites.bind_sprite_map(buffer_id)
def render_cells(vao_id, sg, cell_program, sprites, invert_colors=False):
ul = cell_program.uniform_location
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)
glUniform1i(ul('sprites'), sprites.sampler_num)
glUniform1i(ul('sprite_map'), sprites.buffer_sampler_num)
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:
@@ -248,7 +137,7 @@ class CharGrid:
def __init__(self, screen, opts):
self.buffer_lock = Lock()
self.buffer_id = None
self.vao_id = None
self.current_selection = Selection()
self.last_rendered_selection = self.current_selection.limits(0, screen.lines, screen.columns)
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)
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):
self.screen_geometry = calculate_gl_geometry(window_geometry)
@@ -297,7 +191,7 @@ class CharGrid:
if raw is None:
return 0
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():
val = item(val)
@@ -322,10 +216,9 @@ class CharGrid:
sprites = get_boss().sprites
is_dirty = self.screen.is_dirty()
with sprites.lock:
fg = self.screen.default_fg
fg = fg >> 8 if fg & 1 else self.default_fg
bg = self.screen.default_bg
bg = bg >> 8 if bg & 1 else self.default_bg
fg, bg = self.screen.default_fg, self.screen.default_bg
fg = fg >> 8 if fg & 2 else self.default_fg
bg = bg >> 8 if bg & 2 else self.default_bg
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)
if self.scrolled_by:
@@ -461,13 +354,13 @@ class CharGrid:
s = sel or self.current_selection
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:
sg = self.render_data
if sg is None:
return
if self.buffer_id is None:
self.buffer_id = sprites.add_sprite_map()
if self.vao_id is None:
self.vao_id = cell_program.create_sprite_map()
buf = self.render_buf
start, end = sel = self.current_selection.limits(self.scrolled_by, self.screen.lines, self.screen.columns)
if start != end:
@@ -475,18 +368,18 @@ class CharGrid:
if self.render_buf_is_dirty or sel != self.last_rendered_selection:
memmove(buf, self.render_buf, sizeof(type(buf)))
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 = 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)
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.last_rendered_selection = sel
return sg
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):
cursor = self.current_cursor
@@ -503,7 +396,7 @@ class CharGrid:
left = sg.xstart + cursor.x * sg.dx
top = sg.ystart - cursor.y * sg.dy
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
alpha = self.opts.cursor_opacity
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)
glUniform2f(ul('xpos'), left, right)
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)

View File

@@ -125,14 +125,14 @@ def write_osc(code, string=''):
write(OSC + str(code) + string + '\x07')
set_dynamic_color = write_osc
set_dynamic_color = set_color_table_color = write_osc
def replay(raw):
for line in raw.splitlines():
if line.strip():
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)
else:
rest = map(int, rest.split()) if rest else ()

175
kitty/cocoa_window.m Normal file
View File

@@ -0,0 +1,175 @@
/*
* cocoa_window.m
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include <Cocoa/Cocoa.h>
#include <AvailabilityMacros.h>
// Needed for _NSGetProgname
#include <crt_externs.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask
#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*
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);
@try {
[window setStyleMask:
[window styleMask] | NSWindowStyleMaskResizable];
} @catch (NSException *e) {
return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
}
Py_RETURN_NONE;
}
PyObject*
cocoa_get_lang(PyObject UNUSED *self) {
NSString* locale = nil;
NSString* lang_code = [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode];
NSString* country_code = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
if (lang_code && country_code) {
locale = [NSString stringWithFormat:@"%@_%@", lang_code, country_code];
} else {
locale = [[NSLocale currentLocale] localeIdentifier];
}
if (!locale) { Py_RETURN_NONE; }
return Py_BuildValue("s", [locale UTF8String]);
}

View File

@@ -99,6 +99,7 @@ as_color(ColorProfile *self, PyObject *val) {
break;
case 2:
col = entry >> 8;
break;
default:
ans = Py_None; Py_INCREF(Py_None);
}

View File

@@ -2,6 +2,7 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import ast
import json
import os
import re
@@ -84,16 +85,22 @@ named_keys = {
}
def parse_key(val, keymap):
sc, action = val.partition(' ')[::2]
action = action.strip()
sc = sc.strip()
if not sc or not action:
return
def parse_shortcut(sc):
parts = sc.split('+')
mods = parse_mods(parts[:-1])
key = parts[-1].upper()
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:
safe_print(
'Shortcut: {} has an unknown key, ignoring'.format(val),
@@ -137,6 +144,39 @@ def parse_symbol_map(val):
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):
return parse_mods(val.split('+'))
@@ -150,29 +190,40 @@ def to_layout_names(raw):
raise ValueError('The window layout {} is unknown'.format(p))
def positive_int(x):
return max(0, int(x))
def positive_float(x):
return max(0, float(x))
type_map = {
'scrollback_lines': int,
'scrollback_lines': positive_int,
'scrollback_pager': shlex.split,
'scrollback_in_new_tab': to_bool,
'font_size': to_font_size,
'font_size_delta': float,
'font_size_delta': positive_float,
'cursor_shape': to_cursor_shape,
'cursor_opacity': to_opacity,
'open_url_modifiers': to_open_url_modifiers,
'repaint_delay': int,
'window_border_width': float,
'repaint_delay': positive_int,
'window_border_width': positive_float,
'window_margin_width': positive_float,
'window_padding_width': positive_float,
'wheel_scroll_multiplier': float,
'visual_bell_duration': float,
'visual_bell_duration': positive_float,
'enable_audio_bell': to_bool,
'click_interval': float,
'mouse_hide_wait': float,
'cursor_blink_interval': float,
'cursor_stop_blinking_after': float,
'click_interval': positive_float,
'mouse_hide_wait': positive_float,
'cursor_blink_interval': positive_float,
'cursor_stop_blinking_after': positive_float,
'enabled_layouts': to_layout_names,
'remember_window_size': to_bool,
'initial_window_width': int,
'initial_window_height': int,
'initial_window_width': positive_int,
'initial_window_height': positive_int,
'use_system_wcwidth': to_bool,
'macos_hide_titlebar': to_bool,
}
for name in (
@@ -187,8 +238,10 @@ for a in ('active', 'inactive'):
type_map['%s_tab_%s' % (a, b)] = lambda x: to_color(x, validate=True)
def parse_config(lines):
ans = {'keymap': {}, 'symbol_map': {}}
def parse_config(lines, check_keys=True):
ans = {'keymap': {}, 'symbol_map': {}, 'send_text_map': {'kitty': {}, 'normal': {}, 'application': {}}}
if check_keys:
all_keys = defaults._asdict()
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
@@ -202,6 +255,15 @@ def parse_config(lines):
if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val))
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)
if tm is not None:
val = tm(val)
@@ -212,38 +274,42 @@ def parse_config(lines):
with open(
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf')
) as f:
defaults = parse_config(f.readlines())
defaults = parse_config(f.readlines(), check_keys=False)
Options = namedtuple('Defaults', ','.join(defaults.keys()))
defaults = Options(**defaults)
actions = frozenset(defaults.keymap.values())
def update_dict(a, b):
a.update(b)
return a
def merge_keymaps(defaults, newvals):
ans = defaults.copy()
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):
return {
k: update_dict(v, vals.get(k, {}))
if isinstance(v, dict) else vals.get(k, v)
for k, v in defaults.items()
}
def merge_dicts(defaults, newvals):
ans = defaults.copy()
ans.update(newvals)
return ans
def merge_configs(ans, vals):
vals['keymap'] = {
k: v
for k, v in vals.get('keymap', {}).items() if v in actions
}
remove_keys = {
k
for k, v in vals.get('keymap', {}).items()
if v in ('noop', 'no-op', 'no_op')
}
ans = merge_dicts(vals, ans)
for k in remove_keys:
ans['keymap'].pop(k, None)
def merge_configs(defaults, vals):
ans = {}
for k, v in defaults.items():
if isinstance(v, dict):
newvals = vals.get(k, {})
if k == 'keymap':
ans['keymap'] = merge_keymaps(v, newvals)
elif k == 'send_text_map':
ans[k] = {m: merge_dicts(mm, newvals.get(m, {})) for m, mm in v.items()}
else:
ans[k] = merge_dicts(v, newvals)
else:
ans[k] = vals.get(k, v)
return ans

View File

@@ -15,7 +15,7 @@ from .fast_data_types import (
GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER)
appname = 'kitty'
version = (0, 2, 5)
version = (0, 3, 0)
str_version = '.'.join(map(str, version))
_plat = sys.platform.lower()
isosx = 'darwin' in _plat
@@ -32,10 +32,7 @@ def _get_config_dir():
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if isosx else '~/.config')))
ans = os.path.join(candidate, appname)
try:
os.makedirs(ans)
except FileExistsError:
pass
os.makedirs(ans, exist_ok=True)
return ans

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;
}
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[] = {
GL_METHODS
{"drain_read", (PyCFunction)drain_read, METH_O, ""},
@@ -38,6 +49,7 @@ static PyMethodDef module_methods[] = {
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
{"read_bytes", (PyCFunction)read_bytes, 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, ""},
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
#ifndef __APPLE__
@@ -58,7 +70,7 @@ static struct PyModuleDef module = {
#include <termios.h>
PyMODINIT_FUNC
EXPORTED PyMODINIT_FUNC
PyInit_fast_data_types(void) {
PyObject *m;

View File

@@ -13,6 +13,7 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#define UNUSED __attribute__ ((unused))
#define EXPORTED __attribute__ ((visibility ("default")))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) > (y)) ? (y) : (x))
#define xstr(s) str(s)
@@ -34,8 +35,7 @@ typedef unsigned int index_type;
#define CELL_FIELD_COUNT 5
#define CELL_SIZE (CELL_FIELD_COUNT * 4)
// The data cell size must be a multiple of 3
#define DATA_CELL_SIZE 2 * 3
#define DATA_CELL_SIZE 6
#define CHAR_MASK 0xFFFFFF
#define ATTRS_SHIFT 24

View File

@@ -84,6 +84,16 @@ Uniform2ui(PyObject UNUSED *self, PyObject *args) {
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*
Uniform1i(PyObject UNUSED *self, PyObject *args) {
int location;
@@ -147,7 +157,6 @@ _glewInit(PyObject UNUSED *self) {
return NULL; \
}
ARB_TEST(texture_storage);
ARB_TEST(texture_buffer_object_rgb32);
#undef ARB_TEST
#endif
Py_RETURN_NONE;
@@ -528,29 +537,37 @@ GetTexImage(PyObject UNUSED *self, PyObject *args) {
}
static PyObject*
NamedBufferData(PyObject UNUSED *self, PyObject *args) {
int usage;
unsigned long size, target;
GetBufferSubData(PyObject UNUSED *self, PyObject *args) {
int buftype;
unsigned long size, target, offset;
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);
if (data == NULL) { PyErr_SetString(PyExc_TypeError, "Not a valid data pointer"); return NULL; }
glBindBuffer(buftype, target);
Py_BEGIN_ALLOW_THREADS;
#ifdef glNamedBufferData
#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
glGetBufferSubData(buftype, offset, size, data);
Py_END_ALLOW_THREADS;
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;
}
@@ -606,6 +623,14 @@ DeleteBuffer(PyObject UNUSED *self, PyObject *val) {
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*
BlendFunc(PyObject UNUSED *self, PyObject *args) {
int s, d;
@@ -641,17 +666,70 @@ EnableVertexAttribArray(PyObject UNUSED *self, PyObject *val) {
static PyObject*
VertexAttribPointer(PyObject UNUSED *self, PyObject *args) {
unsigned int index, stride;
int type=GL_FLOAT, normalized, size;
void *offset;
PyObject *l;
if (!PyArg_ParseTuple(args, "IiipIO!", &index, &size, &type, &normalized, &stride, &PyLong_Type, &l)) return NULL;
offset = PyLong_AsVoidPtr(l);
glVertexAttribPointer(index, size, type, normalized, stride, offset);
unsigned int index;
int type, normalized, size, stride;
unsigned long offset;
if (!PyArg_ParseTuple(args, "Iiipik", &index, &size, &type, &normalized, &stride, &offset)) return NULL;
switch(type) {
case GL_BYTE:
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;
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) {
#define GLC(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return 0; }
GLC(GL_VERSION);
@@ -667,17 +745,17 @@ int add_module_gl_constants(PyObject *module) {
GLC(GL_COMPILE_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_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_TEXTURE_2D_ARRAY);
GLC(GL_LINEAR); GLC(GL_CLAMP_TO_EDGE); GLC(GL_NEAREST);
GLC(GL_TEXTURE_MIN_FILTER); GLC(GL_TEXTURE_MAG_FILTER);
GLC(GL_TEXTURE_WRAP_S); GLC(GL_TEXTURE_WRAP_T);
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_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;
}
@@ -685,13 +763,16 @@ int add_module_gl_constants(PyObject *module) {
#define GL_METHODS \
{"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}, \
{"replace_or_create_buffer", (PyCFunction)replace_or_create_buffer, METH_VARARGS, NULL}, \
{"glewInit", (PyCFunction)_glewInit, METH_NOARGS, NULL}, \
{"check_for_extensions", (PyCFunction)check_for_extensions, METH_NOARGS, NULL}, \
METH(Viewport, METH_VARARGS) \
METH(CheckError, METH_NOARGS) \
METH(ClearColor, METH_VARARGS) \
METH(GetProgramiv, METH_VARARGS) \
METH(GetShaderiv, METH_VARARGS) \
METH(Uniform2ui, METH_VARARGS) \
METH(Uniform2i, METH_VARARGS) \
METH(Uniform1i, METH_VARARGS) \
METH(Uniform2f, METH_VARARGS) \
METH(Uniform4f, METH_VARARGS) \
@@ -702,6 +783,7 @@ int add_module_gl_constants(PyObject *module) {
METH(CompileShader, METH_O) \
METH(DeleteTexture, METH_O) \
METH(DeleteBuffer, METH_O) \
METH(DeleteVertexArray, METH_O) \
METH(GetString, METH_O) \
METH(GetIntegerv, METH_O) \
METH(Clear, METH_O) \
@@ -718,6 +800,7 @@ int add_module_gl_constants(PyObject *module) {
METH(Disable, METH_O) \
METH(EnableVertexAttribArray, METH_O) \
METH(VertexAttribPointer, METH_VARARGS) \
METH(VertexAttribDivisor, METH_VARARGS) \
METH(GetProgramInfoLog, METH_O) \
METH(GetShaderInfoLog, METH_O) \
METH(ActiveTexture, METH_O) \
@@ -735,6 +818,8 @@ int add_module_gl_constants(PyObject *module) {
METH(CopyImageSubData, METH_VARARGS) \
METH(TexSubImage3D, METH_VARARGS) \
METH(GetTexImage, METH_VARARGS) \
METH(NamedBufferData, METH_VARARGS) \
METH(GetBufferSubData, METH_VARARGS) \
METH(BlendFunc, METH_VARARGS) \
METH(Finish, METH_NOARGS) \
METH(Flush, METH_NOARGS) \

View File

@@ -7,6 +7,10 @@
#include "data-types.h"
#include <structmember.h>
#include <GLFW/glfw3.h>
#if defined(__APPLE__)
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3native.h>
#endif
#if GLFW_VERSION_MAJOR < 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR < 2)
#error "glfw >= 3.2 required"
@@ -204,6 +208,18 @@ glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
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
@@ -373,6 +389,14 @@ request_window_attention(Window *self) {
}
#endif
#ifdef __APPLE__
static PyObject*
cocoa_window_id(Window *self) {
void *wid = glfwGetCocoaWindow(self->window);
if (wid == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get native window handle"); return NULL; }
return PyLong_FromVoidPtr(wid);
}
#endif
// Boilerplate {{{
#define MND(name, args) {#name, (PyCFunction)name, args, ""}
@@ -387,6 +411,9 @@ static PyMethodDef methods[] = {
MND(current_monitor_dpi, METH_NOARGS),
#ifdef glfwRequestWindowAttention
MND(request_window_attention, METH_NOARGS),
#endif
#ifdef __APPLE__
MND(cocoa_window_id, METH_NOARGS),
#endif
MND(set_should_close, METH_VARARGS),
MND(set_input_mode, METH_VARARGS),
@@ -433,6 +460,10 @@ init_glfw(PyObject *m) {
PyEval_InitThreads();
glfwSetErrorCallback(cb_error_callback);
#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_PRESS);
ADDC(GLFW_REPEAT);

View File

@@ -17,6 +17,21 @@ PyObject* glfw_wait_events(PyObject UNUSED *self, PyObject*);
PyObject* glfw_post_empty_event(PyObject UNUSED *self);
PyObject* glfw_get_physical_dpi(PyObject UNUSED *self);
PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
PyObject* glfw_init_hint_string(PyObject UNUSED *self, PyObject *args);
#ifdef __APPLE__
PyObject* cocoa_get_lang(PyObject UNUSED *self);
PyObject* cocoa_make_window_resizable(PyObject UNUSED *self, PyObject *window_id);
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
#define COCOA_FUNCS
#endif
#define GLFW_FUNC_WRAPPERS \
{"glfw_set_error_callback", (PyCFunction)glfw_set_error_callback, METH_O, ""}, \
@@ -28,4 +43,5 @@ PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, 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_init_hint_string", (PyCFunction)glfw_init_hint_string, METH_VARARGS, ""}, \
COCOA_FUNCS

View File

@@ -85,6 +85,7 @@ control_codes.update({
})
control_codes[defines.GLFW_KEY_6] = (30,)
control_codes[defines.GLFW_KEY_SLASH] = (31,)
control_codes[defines.GLFW_KEY_SPACE] = (0,)
rmkx_key_map = smkx_key_map.copy()
@@ -104,6 +105,12 @@ def get_key_map(screen):
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 = {
k: getattr(defines, 'GLFW_KEY_' + k)
for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
@@ -194,3 +201,11 @@ def interpret_text_event(codepoint, mods, window):
def get_shortcut(keymap, mods, key, scancode):
key = get_localized_key(key, scancode)
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

@@ -45,7 +45,7 @@ cursor_shape block
cursor_blink_interval 0.5
# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to
# zero or a negative number to never stop blinking.
# zero to never stop blinking.
cursor_stop_blinking_after 15.0
# Number of lines of history to keep in memory for scrolling back
@@ -59,7 +59,8 @@ scrollback_pager less +G -R
# When viewing scrollback in a new window, put it in a new tab as well
scrollback_in_new_tab no
# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel)
# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel). Use negative
# numbers to change scroll direction.
wheel_scroll_multiplier 5.0
# The interval between successive clicks to detect double/triple clicks (in seconds)
@@ -71,7 +72,7 @@ click_interval 0.5
select_by_word_characters :@-./_~?&=%+#
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to
# zero or a negative number to disable mouse cursor hiding.
# zero to disable mouse cursor hiding.
mouse_hide_wait 3.0
# The enabled window layouts. A comma separated list of layout names. The special value * means
@@ -120,6 +121,12 @@ term xterm-kitty
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
window_border_width 1
# The window margin (in pts) (blank area outside the border)
window_margin_width 0
# The window padding (in pts) (blank area between the text and the window border)
window_padding_width 0
# The color for the border of the active window
active_border_color #00ff00
@@ -224,6 +231,23 @@ map ctrl+shift+equal increase_font_size
map ctrl+shift+minus decrease_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
# specified unicode codepoints to a particular font. Useful if you need special
# rendering for some symbols, such as for Powerline. Avoids the need for
@@ -237,3 +261,9 @@ map ctrl+shift+backspace restore_font_size
# For example:
#
# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
# OS specific tweaks
# Hide the kitty window's title bar on macOS.
macos_hide_titlebar no

View File

@@ -6,25 +6,29 @@ from collections import namedtuple
from itertools import islice
from .constants import WindowGeometry, viewport_size, cell_size, get_boss
from .utils import pt_to_px
def available_height():
return viewport_size.height - get_boss().current_tab_bar_height
def layout_dimension(length, cell_length, number_of_windows=1, border_length=0, left_align=False):
def layout_dimension(length, cell_length, number_of_windows=1, border_length=0, margin_length=0, padding_length=0, left_align=False):
number_of_cells = length // cell_length
border_length += padding_length
space_needed_for_border = number_of_windows * 2 * border_length
space_needed_for_padding = number_of_windows * 2 * margin_length
space_needed = space_needed_for_padding + space_needed_for_border
extra = length - number_of_cells * cell_length
while extra < space_needed_for_border:
while extra < space_needed:
number_of_cells -= 1
extra = length - number_of_cells * cell_length
cells_per_window = number_of_cells // number_of_windows
extra -= space_needed_for_border
extra -= space_needed
pos = 0 if left_align else (extra // 2)
pos += border_length
pos += border_length + margin_length
inner_length = cells_per_window * cell_length
window_length = 2 * border_length + inner_length
window_length = 2 * (border_length + margin_length) + inner_length
extra = number_of_cells - (cells_per_window * number_of_windows)
while number_of_windows > 0:
number_of_windows -= 1
@@ -43,6 +47,8 @@ class Layout:
def __init__(self, opts, border_width, windows):
self.opts = opts
self.border_width = border_width
self.margin_width = pt_to_px(opts.window_margin_width)
self.padding_width = pt_to_px(opts.window_padding_width)
# A set of rectangles corresponding to the blank spaces at the edges of
# this layout, i.e. spaces that are not covered by any window
self.blank_rects = ()
@@ -76,9 +82,9 @@ def window_geometry(xstart, xnum, ystart, ynum):
return WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_size.width * xnum, bottom=ystart + cell_size.height * ynum)
def layout_single_window():
xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width))
ystart, ynum = next(layout_dimension(available_height(), cell_size.height))
def layout_single_window(margin_length, padding_length):
xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width, margin_length=margin_length, padding_length=padding_length))
ystart, ynum = next(layout_dimension(available_height(), cell_size.height, margin_length=margin_length, padding_length=padding_length))
return window_geometry(xstart, xnum, ystart, ynum)
@@ -120,7 +126,7 @@ class Stack(Layout):
def __call__(self, windows, active_window_idx):
self.blank_rects = []
wg = layout_single_window()
wg = layout_single_window(self.margin_width, self.padding_width)
for i, w in enumerate(windows):
w.is_visible_in_layout = i == active_window_idx
w.set_geometry(wg)
@@ -135,17 +141,23 @@ class Tall(Layout):
def __call__(self, windows, active_window_idx):
self.blank_rects = br = []
if len(windows) == 1:
wg = layout_single_window()
wg = layout_single_window(self.margin_width, self.padding_width)
windows[0].set_geometry(wg)
self.blank_rects = blank_rects_for_window(windows[0])
return
xlayout = layout_dimension(viewport_size.width, cell_size.width, 2, self.border_width)
xlayout = layout_dimension(
viewport_size.width, cell_size.width, 2, self.border_width,
margin_length=self.margin_width, padding_length=self.padding_width)
xstart, xnum = next(xlayout)
ystart, ynum = next(layout_dimension(available_height(), cell_size.height, 1, self.border_width, left_align=True))
ystart, ynum = next(layout_dimension(
available_height(), cell_size.height, 1, self.border_width, left_align=True,
margin_length=self.margin_width, padding_length=self.padding_width))
windows[0].set_geometry(window_geometry(xstart, xnum, ystart, ynum))
vh = available_height()
xstart, xnum = next(xlayout)
ylayout = layout_dimension(available_height(), cell_size.height, len(windows) - 1, self.border_width, left_align=True)
ylayout = layout_dimension(
available_height(), cell_size.height, len(windows) - 1, self.border_width, left_align=True,
margin_length=self.margin_width, padding_length=self.padding_width)
for w, (ystart, ynum) in zip(islice(windows, 1, None), ylayout):
w.set_geometry(window_geometry(xstart, xnum, ystart, ynum))
left_blank_rect(windows[0], br, vh), top_blank_rect(windows[0], br, vh), right_blank_rect(windows[-1], br, vh)

View File

@@ -8,7 +8,6 @@ import os
import sys
import tempfile
from gettext import gettext as _
from queue import Empty
from .boss import Boss
@@ -20,16 +19,21 @@ from .constants import (
)
from .fast_data_types import (
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_STENCIL_BITS, Window, change_wcwidth,
GLFW_STENCIL_BITS, Window, change_wcwidth, check_for_extensions,
enable_automatic_opengl_error_checking, glClear, glClearColor, glewInit,
glfw_init, glfw_set_error_callback, glfw_swap_interval, glfw_terminate,
glfw_wait_events, glfw_window_hint
glfw_init, glfw_init_hint_string, glfw_set_error_callback,
glfw_swap_interval, glfw_terminate, glfw_wait_events, glfw_window_hint
)
from .layout import all_layouts
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')
@@ -95,6 +99,13 @@ def option_parser():
default=False,
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(
'--replay-commands',
default=None,
@@ -137,7 +148,9 @@ def option_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_MINOR, GL_VERSION[1])
glfw_window_hint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
@@ -175,7 +188,7 @@ def dispatch_pending_calls(boss):
def run_app(opts, args):
setup_opengl()
setup_opengl(opts)
load_cached_values()
if 'window-size' in cached_values and opts.remember_window_size:
ws = cached_values['window-size']
@@ -188,10 +201,22 @@ def run_app(opts, args):
else:
viewport_size.width = opts.initial_window_width
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.make_context_current()
if not 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:
cocoa_make_window_resizable(window.cocoa_window_id())
else:
with open(logo_data_file, 'rb') as f:
window.set_icon(f.read(), 256, 256)
viewport_size.width, viewport_size.height = window.get_framebuffer_size()
@@ -224,12 +249,38 @@ def on_glfw_error(code, msg):
safe_print('[glfw error] ', msg, file=sys.stderr)
def ensure_osx_locale():
# Ensure the LANG env var is set. See
# https://github.com/kovidgoyal/kitty/issues/90
from .fast_data_types import cocoa_get_lang
if 'LANG' not in os.environ:
lang = cocoa_get_lang()
if lang is not None:
os.environ['LANG'] = lang + '.UTF-8'
def main():
locale.setlocale(locale.LC_ALL, '')
if isosx:
ensure_osx_locale()
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',
None) == '1' and getattr(sys, 'frozen', True):
os.chdir(os.path.expanduser('~'))
if not os.path.isdir(os.getcwd()):
os.chdir(os.path.expanduser('~'))
args = option_parser().parse_args()
if getattr(args, 'detach', False):
detach()
if args.cmd:
exec(args.cmd)
return
@@ -243,6 +294,8 @@ def main():
change_wcwidth(not opts.use_system_wcwidth)
glfw_set_error_callback(on_glfw_error)
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():
raise SystemExit('GLFW initialization failed')
try:

View File

@@ -58,5 +58,5 @@ def encode_mouse_event(tracking_mode, tracking_protocol, button, action, mods, x
ans = bytes(ans)
else:
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

View File

@@ -543,6 +543,7 @@ accumulate_osc(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback)
screen->parser_buf_pos--;
return true;
}
/* fallthrough */
default:
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
REPORT_ERROR("OSC sequence too long, truncating.");
@@ -594,6 +595,7 @@ accumulate_oth(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback)
screen->parser_buf_pos--;
return true;
}
/* fallthrough */
default:
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
REPORT_ERROR("OTH sequence too long, truncating.");

View File

@@ -7,6 +7,7 @@
#include "data-types.h"
#include <structmember.h>
#include <limits.h>
#include "unicode-data.h"
#include "tracker.h"
#include "modes.h"
@@ -1332,6 +1333,14 @@ static PyGetSetDef getsetters[] = {
{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[] = {
{"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"},
{"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"},
@@ -1341,11 +1350,11 @@ static PyMemberDef members[] = {
{"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"},
{"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"},
{"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"},
{"default_fg", T_ULONG, offsetof(Screen, default_fg), 0, "default_fg"},
{"default_bg", T_ULONG, offsetof(Screen, default_bg), 0, "default_bg"},
{"highlight_fg", T_ULONG, offsetof(Screen, highlight_fg), 0, "highlight_fg"},
{"highlight_bg", T_ULONG, offsetof(Screen, highlight_bg), 0, "highlight_bg"},
{"cursor_color", T_ULONG, offsetof(Screen, cursor_color), 0, "cursor_color"},
{"default_fg", T_COL, offsetof(Screen, default_fg), 0, "default_fg"},
{"default_bg", T_COL, offsetof(Screen, default_bg), 0, "default_bg"},
{"highlight_fg", T_COL, offsetof(Screen, highlight_fg), 0, "highlight_fg"},
{"highlight_bg", T_COL, offsetof(Screen, highlight_bg), 0, "highlight_bg"},
{"cursor_color", T_COL, offsetof(Screen, cursor_color), 0, "cursor_color"},
{NULL}
};

View File

@@ -2,39 +2,106 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from collections import namedtuple
from contextlib import contextmanager
from ctypes import addressof, sizeof
from functools import lru_cache
from threading import Lock
from .fonts.render import render_cell
from .fast_data_types import (
glCreateProgram, glAttachShader, GL_FRAGMENT_SHADER, GL_VERTEX_SHADER,
glLinkProgram, GL_TRUE, GL_LINK_STATUS, glGetProgramiv,
glGetProgramInfoLog, glDeleteShader, glDeleteProgram, glGenVertexArrays,
glCreateShader, glShaderSource, glCompileShader, glGetShaderiv,
GL_COMPILE_STATUS, glGetShaderInfoLog, glGetUniformLocation,
glGetAttribLocation, glUseProgram, glBindVertexArray, GL_TEXTURE0,
GL_TEXTURE1, glGetIntegerv, GL_MAX_ARRAY_TEXTURE_LAYERS, glNamedBufferData,
GL_MAX_TEXTURE_SIZE, glDeleteTexture, GL_TEXTURE_2D_ARRAY, glGenTextures,
glBindTexture, glTexParameteri, GL_CLAMP_TO_EDGE, glDeleteBuffer,
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S,
GL_NEAREST, GL_TEXTURE_WRAP_T, glGenBuffers, GL_R8, GL_RED,
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_STATIC_DRAW, GL_STREAM_DRAW,
GL_TEXTURE_BUFFER, GL_RGB32UI, GL_FLOAT, GL_ARRAY_BUFFER, glBindBuffer,
glPixelStorei, glTexBuffer, glActiveTexture, glTexStorage3D,
glCopyImageSubData, glTexSubImage3D, ITALIC, BOLD, SpriteMap,
glEnableVertexAttribArray, glVertexAttribPointer, copy_image_sub_data
BOLD, GL_ARRAY_BUFFER, GL_CLAMP_TO_EDGE, GL_COMPILE_STATUS, GL_FLOAT,
GL_FRAGMENT_SHADER, GL_LINK_STATUS, GL_MAX_ARRAY_TEXTURE_LAYERS,
GL_MAX_TEXTURE_SIZE, GL_NEAREST, GL_R8, GL_RED, GL_STREAM_DRAW,
GL_TEXTURE0, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER,
GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_TRUE,
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_VERTEX_SHADER, ITALIC, SpriteMap,
copy_image_sub_data, glActiveTexture, glAttachShader, glBindBuffer,
glBindTexture, glBindVertexArray, glCompileShader, glCopyImageSubData,
glCreateProgram, glCreateShader, glDeleteBuffer, glDeleteProgram,
glDeleteShader, glDeleteTexture, glDeleteVertexArray,
glEnableVertexAttribArray, glGenBuffers, glGenTextures, glGenVertexArrays,
glGetAttribLocation, glGetBufferSubData, glGetIntegerv,
glGetProgramInfoLog, glGetProgramiv, glGetShaderInfoLog, glGetShaderiv,
glGetUniformLocation, glLinkProgram, glPixelStorei, glShaderSource,
glTexParameteri, glTexStorage3D, glTexSubImage3D, glUseProgram,
glVertexAttribDivisor, glVertexAttribPointer, replace_or_create_buffer
)
from .fonts.render import render_cell
from .utils import safe_print
GL_VERSION = (3, 3)
VERSION = GL_VERSION[0] * 100 + GL_VERSION[1] * 10
ITALIC_MASK = 1 << ITALIC
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
array with each texture being a sprite sheet. '''
@@ -43,15 +110,14 @@ class Sprites:
def __init__(self):
self.xnum = self.ynum = 1
self.sampler_num = 0
self.buffer_sampler_num = 1
self.first_cell_cache = {}
self.second_cell_cache = {}
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_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.lock = Lock()
@@ -103,7 +169,6 @@ class Sprites:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
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))
glBindTexture(tgt, 0)
def realloc_texture(self):
tgt = GL_TEXTURE_2D_ARRAY
@@ -138,51 +203,28 @@ class Sprites:
self.last_num_of_layers = znum
self.last_ynum = self.backend.ynum
self.texture_id = tex
glBindTexture(tgt, 0)
def destroy(self):
if self.texture_id is not None:
glDeleteTexture(self.texture_id)
self.texture_id = None
if self.buffer_texture_id is not None:
glDeleteTexture(self.buffer_texture_id)
def ensure_state(self):
if self.texture_id is None:
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):
self.ensure_state()
glActiveTexture(self.texture_unit)
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):
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 """
def __init__(self, vertex, fragment):
@@ -206,23 +248,43 @@ class ShaderProgram:
raise ValueError('Error linking shader program: \n%s' % info.decode('utf-8'))
glDeleteShader(vs_id)
glDeleteShader(frag_id)
self.vao_id = glGenVertexArrays(1)
self.buffers = {}
self.vertex_arrays = {}
def add_vertex_array(self, name, size=3, dtype=GL_FLOAT, normalized=False, stride=0, offset=0):
glBindVertexArray(self.vao_id)
if name not in self.buffers:
self.buffers[name] = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.buffers[name])
aid = self.attribute_location(name)
glEnableVertexAttribArray(aid)
glVertexAttribPointer(aid, size, dtype, normalized, stride, offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def vertex_array(self, name, size=3, dtype=GL_FLOAT, normalized=False, stride=0, offset=0, divisor=0):
return VertexArray(name, size, dtype, normalized, stride, offset, divisor)
def add_vertex_arrays(self, *arrays):
vao_id = glGenVertexArrays(1)
self.vertex_arrays[vao_id] = buf_id = buffer_manager.create(for_use=GL_ARRAY_BUFFER)
with self.bound_vertex_array(vao_id), buffer_manager.bound_buffer(buf_id):
for x in arrays:
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)
def send_vertex_data(self, name, data, usage=GL_STATIC_DRAW):
bufid = self.buffers[name]
glNamedBufferData(bufid, sizeof(data), addressof(data), usage)
def remove_vertex_array(self, vao_id):
buf_id = self.vertex_arrays.pop(vao_id, None)
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:
return self.program_id
@@ -260,8 +322,7 @@ class ShaderProgram:
def __enter__(self):
glUseProgram(self.program_id)
glBindVertexArray(self.vao_id)
def __exit__(self, *args):
glUseProgram(0)
glBindVertexArray(0)
# }}}

View File

@@ -3,18 +3,23 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from collections import deque
from ctypes import addressof
from functools import partial
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 .config import build_ansi_color_table
from .constants import get_boss, appname, shell_path, cell_size, queue_action, viewport_size, WindowGeometry, GLuint
from .fast_data_types import glfw_post_empty_event, Screen, DECAWM, DATA_CELL_SIZE, ColorProfile
from .char_grid import calculate_gl_geometry, render_cells
from .layout import all_layouts, Rect
from .constants import (
GLuint, WindowGeometry, appname, cell_size, get_boss, queue_action,
shell_path, viewport_size
)
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 .borders import Borders
from .window import Window
@@ -32,9 +37,12 @@ class Tab:
self.borders = Borders(opts)
self.windows = deque()
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:
self.cwd = args.directory
l = self.enabled_layouts[0]
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
if special_window is None:
queue_action(self.new_window)
else:
@@ -42,10 +50,8 @@ class Tab:
else:
self.cwd = session_tab.cwd or args.directory
l = session_tab.layout
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
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):
for cmd in session_tab.windows:
@@ -201,7 +207,7 @@ class TabManager:
def __init__(self, opts, args, startup_session):
self.opts, self.args = opts, args
self.buffer_id = None
self.vao_id = None
self.tabbar_lock = Lock()
self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs]
self.cell_ranges = []
@@ -305,7 +311,7 @@ class TabManager:
if needs_resize:
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.cursor.x = 0
s.erase_in_line(2, False)
@@ -328,15 +334,16 @@ class TabManager:
s.cursor.bold = s.cursor.italic = False
s.cursor.fg = s.cursor.bg = 0
s.draw('')
if s.cursor.x > s.columns - max_title_length:
if s.cursor.x > s.columns - max_title_length and t is not self.tabs[-1]:
s.draw('')
break
s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab
s.update_cell_data(
sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True)
sprites.render_dirty_cells()
if self.buffer_id is None:
self.buffer_id = sprites.add_sprite_map()
sprites.set_sprite_map(self.buffer_id, self.sprite_map)
if self.vao_id is None:
self.vao_id = cell_program.create_sprite_map()
cell_program.send_vertex_data(self.vao_id, self.sprite_map)
def activate_tab_at(self, x):
x = (x - self.window_geometry.left) // cell_size.width
@@ -356,5 +363,5 @@ class TabManager:
return
with self.tabbar_lock:
if self.tabbar_dirty:
self.update_tab_bar_data(sprites)
render_cells(self.buffer_id, self.screen_geometry, cell_program, sprites)
self.update_tab_bar_data(sprites, cell_program)
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 .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
@@ -37,6 +37,13 @@ def wcwidth(c: str) -> int:
return wcwidth_impl(ord(c[0]))
@lru_cache()
def pt_to_px(pts):
dpix, dpiy = get_dpi()['logical']
dpi = (dpix + dpiy) / 2
return round(pts * dpi / 72)
@contextmanager
def timeit(name, do_timing=False):
if do_timing:
@@ -174,7 +181,7 @@ def get_primary_selection():
return '' # There is no primary selection on OS X
# glfw has no way to get the primary selection
# 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(
@@ -196,7 +203,7 @@ def set_primary_selection(text):
return # There is no primary selection on OS X
if isinstance(text, str):
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.wait()
@@ -208,3 +215,14 @@ def open_url(url, program='default'):
cmd = shlex.split(program)
cmd.append(url)
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

@@ -115,7 +115,7 @@ def init_env(debug=False, sanitize=False, native_optimizations=True):
cflags = os.environ.get(
'OVERRIDE_CFLAGS', (
'-Wextra -Wno-missing-field-initializers -Wall -std=c99 -D_XOPEN_SOURCE=700'
' -pedantic-errors -Werror {} {} -D{}DEBUG -fwrapv {} {} -pipe {}'
' -pedantic-errors -Werror {} {} -D{}DEBUG -fwrapv {} {} -pipe {} -fvisibility=hidden'
).format(
optimize, ' '.join(sanitize_args), ('' if debug else 'N'), stack_protector, missing_braces, '-march=native'
if native_optimizations else ''
@@ -266,7 +266,7 @@ def option_parser():
def find_c_files():
ans, headers = [], []
d = os.path.join(base, 'kitty')
exclude = {'freetype.c', 'fontconfig.c'} if isosx else {'core_text.m'}
exclude = {'freetype.c', 'fontconfig.c'} if isosx else {'core_text.m', 'cocoa_window.m'}
for x in os.listdir(d):
ext = os.path.splitext(x)[1]
if ext in ('.c', '.m') and os.path.basename(x) not in exclude:
@@ -288,10 +288,7 @@ def build(args, native_optimizations=True):
def safe_makedirs(path):
try:
os.makedirs(path)
except FileExistsError:
pass
os.makedirs(path, exist_ok=True)
def build_test_launcher(args):
@@ -322,7 +319,7 @@ def package(args, for_bundle=False): # {{{
def src_ignore(parent, entries):
return [
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)