Compare commits

..

93 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
Kovid Goyal
8047743882 0.2.5 2017-05-26 14:02:14 +05:30
Kovid Goyal
e9b5963610 Fix incorrect replay of screen cursor down 2017-05-26 13:21:16 +05:30
Kovid Goyal
a1d4630a25 Fix incorrect implementation of the Vertical Position Adjust (VPA) CSI code
Fixes #80
2017-05-26 13:11:27 +05:30
Kovid Goyal
fafd710ce3 Clean exit for client on EOF/interrupt 2017-05-26 12:36:36 +05:30
Kovid Goyal
a79bb3add2 Correct the DEBUG define 2017-05-24 07:47:11 +05:30
Kovid Goyal
b3a718b1e4 ... 2017-05-23 20:28:50 +05:30
Kovid Goyal
952aa7ad4a A tribute to Thomas E. Dickey 2017-05-23 20:27:51 +05:30
Kovid Goyal
149d606154 Add note about impossibility of using multiple modifier keys 2017-05-23 08:25:41 +05:30
Kovid Goyal
ad21c7ed0f Mimic behavior of xterm when pressing Ctrl+<key>
Where <key> is one of ,.;'-=

These dont map to control codes, xterm and libvte just ignore the Ctrl
and echo the key as if control was not pressed. Mimic that behavior
2017-05-22 21:09:20 +05:30
Kovid Goyal
38d2839206 Fix Ctrl+6 and Ctrl+/ not working
Fixes #79
2017-05-22 21:01:14 +05:30
Kovid Goyal
24d0bb8bd5 Allow IME to generate unicode characters using Alt+key which is used on OSX, I think 2017-05-20 12:01:50 +05:30
Kovid Goyal
08f336769f Add tests for key mapping
Also fix Alt+Special keys no generating correct codes
2017-05-20 11:41:21 +05:30
Kovid Goyal
5525d4db49 Fix alt+key resulting in the upper case version of key even when shift is not pressed
Fixes #78
2017-05-20 08:47:02 +05:30
Kovid Goyal
448ba26257 DRYer 2017-05-20 00:58:40 +05:30
Kovid Goyal
1d1138ca31 Be a little more stringent with Xft parsing 2017-05-20 00:26:26 +05:30
Kovid Goyal
357a415386 ... 2017-05-20 00:23:43 +05:30
Kovid Goyal
a65856ec98 Use Xlib to directly query the xrm system.Only if it fails do we try to shell out to xrd 2017-05-20 00:21:09 +05:30
Kovid Goyal
83855e16ce On linux query xrdb for Xft.dpi and use that, if set as the logical DPI.
Fall back to the actual physical dpi as returned by GLFW if that fails.
2017-05-19 23:26:09 +05:30
Kovid Goyal
ccf66fc621 A method on the window object to get the current monitor for that window 2017-05-19 20:37:54 +05:30
Kovid Goyal
c27b597951 Fix incorrect implementation of the CSI scroll commands
I was lazy and just assumed they were n indexes, but they actually
scroll the screen without moving the cursor. Fixes #76
2017-05-19 19:25:41 +05:30
Kovid Goyal
85dbae1de4 Ensure that dump_bytes truncates the file it is dumping to 2017-05-19 18:09:37 +05:30
Kovid Goyal
cd1ba334c1 Forgot to change test 2017-05-19 15:54:34 +05:30
Kovid Goyal
1cff4f9d29 ... 2017-05-19 15:45:07 +05:30
Kovid Goyal
d180601711 More command replaying 2017-05-19 15:43:15 +05:30
Kovid Goyal
01d0e7474f Change reported version to >= 4000 so that vim autodetects SGR mouse support correctly 2017-05-19 14:23:11 +05:30
33 changed files with 1453 additions and 492 deletions

View File

@@ -82,7 +82,7 @@ the following dependencies are installed first.
* glfw >= 3.2
* glew >= 2.0 (not needed on macOS)
* fontconfig (not needed on macOS)
* xdpyinfo and xsel (only on X11 based systems)
* xrdb and xsel (only on X11 based systems)
* gcc or clang (required only for building)
* pkg-config (required only for building)
@@ -317,6 +317,25 @@ without needing to install all of kitty.
This applies to creating packages for kitty for macOS package managers such as
brew or MacPorts as well.
== A tribute
While over the decades I am sure many people have contributed to the
development of the terminal emulator space, there is one individual in
particular I would like to thank. link:http://invisible-island.net[Thomas E.
Dickey], the creator of xterm. xterm is the most comprehensive and
feature-rich terminal emulator I have had the pleasure of using. As I worked on
kitty, I ran headlong into more and more gnarly corners of terminal behavior.
On all those occasions, either the excellent documentation at
link:http://invisible-island.net/xterm/ctlseqs/ctlseqs.html[xterm-ctlseqs] or
investigating the behavior and code of xterm or vttest were invaluable tools to
aid my understanding.
My achievements, if any, in developing kitty would not have been possible without
his prior work and the generous sharing of knowledge accumulated over decades.
Thank you.
== Resources on terminal behavior
http://invisible-island.net/xterm/ctlseqs/ctlseqs.html

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)
@@ -271,12 +286,13 @@ class Boss(Thread):
@callback
def on_text_input(self, window, codepoint, mods):
data = interpret_text_event(codepoint, mods)
if data:
w = self.active_window
w = self.active_window
if w is not None:
yield w
if w is not None:
yield w
w.write_to_child(data)
data = interpret_text_event(codepoint, mods, w)
if data:
w.write_to_child(data)
@callback
def on_key(self, window, key, scancode, action, mods):
@@ -307,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)
@@ -325,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
@@ -364,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:
@@ -386,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)
@@ -398,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
@@ -419,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:
@@ -438,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
@@ -498,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

@@ -12,6 +12,7 @@ import sys
CSI = '\033['
OSC = '\033]'
def write(x):
@@ -73,6 +74,10 @@ def screen_set_margins(t, b):
write(CSI + '%d;%dr' % (t, b))
def screen_indexn(n):
write(CSI + '%dS' % n)
def screen_erase_in_display(how, private):
write(CSI + ('?' if private else '') + str(how) + 'J')
@@ -85,6 +90,10 @@ def screen_cursor_up2(count):
write(CSI + '%dA' % count)
def screen_cursor_down(count):
write(CSI + '%dB' % count)
def screen_carriage_return():
write('\r')
@@ -101,11 +110,29 @@ def draw(*a):
write(' '.join(a))
def report_device_attributes(mode, char):
x = CSI
if char:
x += ord(char)
if mode:
x += str(mode)
write(CSI + x + 'c')
def write_osc(code, string=''):
if string:
string = ';' + string
write(OSC + str(code) + string + '\x07')
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 ()
@@ -115,4 +142,7 @@ def replay(raw):
def main(path):
raw = open(path).read()
replay(raw)
input()
try:
input()
except (EOFError, KeyboardInterrupt):
pass

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, 4)
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
@@ -376,6 +376,8 @@ void screen_change_default_color(Screen *self, unsigned int which, uint32_t col)
void screen_alignment_display(Screen *self);
void screen_reverse_index(Screen *self);
void screen_index(Screen *self);
void screen_scroll(Screen *self, unsigned int count);
void screen_reverse_scroll(Screen *self, unsigned int count);
void screen_reset(Screen *self);
void screen_set_tab_stop(Screen *self);
void screen_tab(Screen *self);

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"
@@ -93,7 +97,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self = (Window *)type->tp_alloc(type, 0);
if (self != NULL) {
self->window = glfwCreateWindow(width, height, title, NULL, NULL);
if (self->window == NULL) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create GLFWWindow"); return NULL; }
if (self->window == NULL) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; }
for(i = 0; i < MAX_WINDOWS; i++) {
if (window_weakrefs[i] == NULL) { window_weakrefs[i] = self; break; }
}
@@ -178,10 +182,8 @@ glfw_post_empty_event(PyObject UNUSED *self) {
Py_RETURN_NONE;
}
PyObject*
glfw_get_physical_dpi(PyObject UNUSED *self) {
GLFWmonitor *m = glfwGetPrimaryMonitor();
if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; }
static PyObject*
get_physical_dpi(GLFWmonitor *m) {
int width = 0, height = 0;
glfwGetMonitorPhysicalSize(m, &width, &height);
if (width == 0 || height == 0) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor size"); return NULL; }
@@ -192,6 +194,13 @@ glfw_get_physical_dpi(PyObject UNUSED *self) {
return Py_BuildValue("ff", dpix, dpiy);
}
PyObject*
glfw_get_physical_dpi(PyObject UNUSED *self) {
GLFWmonitor *m = glfwGetPrimaryMonitor();
if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; }
return get_physical_dpi(m);
}
PyObject*
glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
int key, scancode;
@@ -199,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
@@ -318,6 +339,48 @@ get_window_size(Window *self) {
return Py_BuildValue("ii", w, h);
}
static GLFWmonitor*
current_monitor(GLFWwindow *window) {
// Find the monitor that has the maximum overlap with this window
int nmonitors, i;
int wx, wy, ww, wh;
int mx, my, mw, mh;
int overlap = 0, bestoverlap = 0;
GLFWmonitor *bestmonitor = NULL;
GLFWmonitor **monitors = NULL;
const GLFWvidmode *mode;
glfwGetWindowPos(window, &wx, &wy);
glfwGetWindowSize(window, &ww, &wh);
monitors = glfwGetMonitors(&nmonitors);
if (monitors == NULL || nmonitors < 1) { PyErr_SetString(PyExc_ValueError, "No monitors connected"); return NULL; }
for (i = 0; i < nmonitors; i++) {
mode = glfwGetVideoMode(monitors[i]);
glfwGetMonitorPos(monitors[i], &mx, &my);
mw = mode->width;
mh = mode->height;
overlap =
MAX(0, MIN(wx + ww, mx + mw) - MAX(wx, mx)) *
MAX(0, MIN(wy + wh, my + mh) - MAX(wy, my));
if (bestoverlap < overlap || bestmonitor == NULL) {
bestoverlap = overlap;
bestmonitor = monitors[i];
}
}
return bestmonitor;
}
static PyObject*
current_monitor_dpi(Window *self) {
GLFWmonitor *m = current_monitor(self->window);
if (m == NULL) return NULL;
return get_physical_dpi(m);
}
#ifdef glfwRequestWindowAttention
static PyObject*
request_window_attention(Window *self) {
@@ -326,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, ""}
@@ -337,8 +408,12 @@ static PyMethodDef methods[] = {
MND(should_close, METH_NOARGS),
MND(get_framebuffer_size, METH_NOARGS),
MND(get_window_size, METH_NOARGS),
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),
@@ -385,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

@@ -7,7 +7,28 @@ from .terminfo import key_as_bytes
from .utils import base64_encode
from .key_encoding import KEY_MAP
smkx_key_map = {
def modify_key_bytes(keybytes, amt):
ans = bytearray(keybytes)
amt = str(amt).encode('ascii')
if ans[-1] == ord('~'):
return bytes(ans[:-1] + bytearray(b';' + amt + b'~'))
if ans[1] == ord('O'):
return bytes(ans[:1] + bytearray(b'[1;' + amt) + ans[-1:])
raise ValueError('Unknown key type')
def modify_complex_key(name, amt):
return modify_key_bytes(key_as_bytes(name), amt)
control_codes = {}
smkx_key_map = {}
alt_codes = {defines.GLFW_KEY_TAB: b'\033\t'}
shift_alt_codes = {defines.GLFW_KEY_TAB: key_as_bytes('kcbt')}
alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT)
for kf, kn in {
defines.GLFW_KEY_UP: 'kcuu1',
defines.GLFW_KEY_DOWN: 'kcud1',
defines.GLFW_KEY_LEFT: 'kcub1',
@@ -18,12 +39,30 @@ smkx_key_map = {
defines.GLFW_KEY_DELETE: 'kdch1',
defines.GLFW_KEY_PAGE_UP: 'kpp',
defines.GLFW_KEY_PAGE_DOWN: 'knp',
}
smkx_key_map = {k: key_as_bytes(v) for k, v in smkx_key_map.items()}
}.items():
smkx_key_map[kf] = key_as_bytes(kn)
alt_codes[kf] = modify_complex_key(kn, 3)
shift_alt_codes[kf] = modify_complex_key(kn, 4)
control_codes[kf] = modify_complex_key(kn, 5)
for f in range(1, 13):
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
smkx_key_map[kf] = key_as_bytes('kf{}'.format(f))
del f, kf
kn = 'kf{}'.format(f)
smkx_key_map[kf] = key_as_bytes(kn)
alt_codes[kf] = modify_complex_key(kn, 3)
shift_alt_codes[kf] = modify_complex_key(kn, 4)
control_codes[kf] = modify_complex_key(kn, 5)
f = {k: k for k in '0123456789'}
f.update({
'COMMA': ',',
'PERIOD': '.',
'SEMICOLON': ';',
'APOSTROPHE': "'",
'MINUS': '-',
'EQUAL': '=',
})
for kf, kn in f.items():
control_codes[getattr(defines, 'GLFW_KEY_' + kf)] = (ord(kn),)
del f, kf, kn
smkx_key_map[defines.GLFW_KEY_ESCAPE] = b'\033'
smkx_key_map[defines.GLFW_KEY_ENTER] = b'\r'
@@ -39,27 +78,16 @@ SHIFTED_KEYS = {
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
}
control_codes = {
control_codes.update({
k: (1 + i, )
for i, k in
enumerate(range(defines.GLFW_KEY_A, defines.GLFW_KEY_RIGHT_BRACKET + 1))
}
})
control_codes[defines.GLFW_KEY_6] = (30,)
control_codes[defines.GLFW_KEY_SLASH] = (31,)
control_codes[defines.GLFW_KEY_SPACE] = (0,)
def rkey(name, a, b):
return bytearray(key_as_bytes(name).replace(a, b))
control_codes[defines.GLFW_KEY_PAGE_UP] = rkey('kpp', b'~', b';5~')
control_codes[defines.GLFW_KEY_PAGE_DOWN] = rkey('knp', b'~', b';5~')
control_codes[defines.GLFW_KEY_DELETE] = rkey('kdch1', b'~', b';5~')
alt_codes = {
k: (0x1b, k)
for i, k in enumerate(
range(defines.GLFW_KEY_SPACE, defines.GLFW_KEY_RIGHT_BRACKET + 1)
)
}
rmkx_key_map = smkx_key_map.copy()
rmkx_key_map.update({
defines.GLFW_KEY_UP: b'\033[A',
@@ -69,9 +97,6 @@ rmkx_key_map.update({
defines.GLFW_KEY_HOME: b'\033[H',
defines.GLFW_KEY_END: b'\033[F',
})
for sk in 'UP DOWN LEFT RIGHT HOME END'.split():
sk = getattr(defines, 'GLFW_KEY_' + sk)
control_codes[sk] = rmkx_key_map[sk].replace(b'[', b'[1;5')
cursor_key_mode_map = {True: smkx_key_map, False: rmkx_key_map}
@@ -80,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'
@@ -131,7 +162,7 @@ def extended_key_event(key, scancode, mods, action):
).encode('ascii')
def interpret_key_event(key, scancode, mods, window, action):
def interpret_key_event(key, scancode, mods, window, action, get_localized_key=get_localized_key):
screen = window.screen
key = get_localized_key(key, scancode)
if screen.extended_keyboard:
@@ -143,9 +174,9 @@ def interpret_key_event(key, scancode, mods, window, action):
if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
# Map Ctrl-key to ascii control code
data.extend(control_codes[key])
elif mods == defines.GLFW_MOD_ALT and key in alt_codes:
# Map Alt+key to Esc-key
data.extend(alt_codes[key])
elif mods in alt_mods and key in alt_codes:
# Printable keys handled by interpret_text_event()
data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key])
else:
key_map = get_key_map(screen)
x = key_map.get(key)
@@ -156,8 +187,12 @@ def interpret_key_event(key, scancode, mods, window, action):
return bytes(data)
def interpret_text_event(codepoint, mods):
def interpret_text_event(codepoint, mods, window):
screen = window.screen
if mods > defines.GLFW_MOD_SHIFT:
if mods in alt_mods and not screen.extended_keyboard:
data = chr(codepoint).encode('utf-8')
return b'\x1b' + data
return b'' # Handled by interpret_key_event above
data = chr(codepoint).encode('utf-8')
return data
@@ -166,3 +201,11 @@ def interpret_text_event(codepoint, mods):
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

@@ -96,6 +96,9 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
#define REPORT_OSC(name, string) \
Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", #name, string)); PyErr_Clear();
#define REPORT_OSC2(name, code, string) \
Py_XDECREF(PyObject_CallFunction(dump_callback, "sIO", #name, code, string)); PyErr_Clear();
#else
#define DUMP_UNUSED UNUSED
@@ -107,6 +110,7 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
#define REPORT_PARAMS(...)
#define FLUSH_DRAW
#define REPORT_OSC(name, string)
#define REPORT_OSC2(name, code, string)
#endif
@@ -266,7 +270,7 @@ handle_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_cal
static inline void
dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
#define DISPATCH_OSC(name) REPORT_OSC(name, string); name(screen, string);
#define SET_COLOR(name) REPORT_OSC(name, string); name(screen, code, string);
#define SET_COLOR(name) REPORT_OSC2(name, code, string); name(screen, code, string);
const unsigned int limit = screen->parser_buf_pos;
unsigned int code=0, i;
for (i = 0; i < MIN(limit, 5); i++) {
@@ -331,11 +335,7 @@ screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, fa
static inline void
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); }
static inline void
screen_indexn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1, count); i++) screen_index(s); }
static inline void
screen_tabn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1, count); i++) screen_tab(s); }
static inline void
screen_reverse_indexn(Screen *s, unsigned int count) { for (index_type i=0; i < count; i++) screen_reverse_index(s); }
static inline void
save_cursor(Screen *s, unsigned int UNUSED param, bool private) {
if (private) fprintf(stderr, "%s %s", ERROR_PREFIX, "CSI s in private mode not supported");
@@ -484,9 +484,9 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
case DECSCUSR:
CALL_CSI_HANDLER1M(screen_set_cursor, 1);
case SU:
CALL_CSI_HANDLER1(screen_indexn, 1);
CALL_CSI_HANDLER1(screen_scroll, 1);
case SD:
CALL_CSI_HANDLER1(screen_reverse_indexn, 1);
CALL_CSI_HANDLER1(screen_reverse_scroll, 1);
case DECSTR:
if (end_modifier == '$') {
// DECRQM
@@ -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"
@@ -582,36 +583,64 @@ screen_cursor_to_column(Screen *self, unsigned int column) {
}
}
#define INDEX_UP \
linebuf_index(self->linebuf, top, bottom); \
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) { \
/* Only add to history when no page margins have been set */ \
linebuf_init_line(self->linebuf, bottom); \
historybuf_add_line(self->historybuf, self->linebuf->line); \
tracker_line_added_to_history(self->change_tracker); \
} \
linebuf_clear_line(self->linebuf, bottom); \
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker); \
else tracker_update_line_range(self->change_tracker, top, bottom);
void
screen_index(Screen *self) {
// Move cursor down one line, scrolling screen if needed
unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (self->cursor->y == bottom) {
linebuf_index(self->linebuf, top, bottom);
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) {
// Only add to history when no page margins have been set
linebuf_init_line(self->linebuf, bottom);
historybuf_add_line(self->historybuf, self->linebuf->line);
tracker_line_added_to_history(self->change_tracker);
}
linebuf_clear_line(self->linebuf, bottom);
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker);
else tracker_update_line_range(self->change_tracker, top, bottom);
INDEX_UP;
} else screen_cursor_down(self, 1);
}
void
screen_scroll(Screen *self, unsigned int count) {
// Scroll the screen up by count lines, not moving the cursor
count = MIN(self->lines, count);
unsigned int top = self->margin_top, bottom = self->margin_bottom;
while (count > 0) {
count--;
INDEX_UP;
}
}
#define INDEX_DOWN \
linebuf_reverse_index(self->linebuf, top, bottom); \
linebuf_clear_line(self->linebuf, top); \
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker); \
else tracker_update_line_range(self->change_tracker, top, bottom);
void
screen_reverse_index(Screen *self) {
// Move cursor up one line, scrolling screen if needed
unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (self->cursor->y == top) {
linebuf_reverse_index(self->linebuf, top, bottom);
linebuf_clear_line(self->linebuf, top);
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker);
else tracker_update_line_range(self->change_tracker, top, bottom);
INDEX_DOWN;
} else screen_cursor_up(self, 1, false, -1);
}
void
screen_reverse_scroll(Screen *self, unsigned int count) {
// Scroll the screen down by count lines, not moving the cursor
count = MIN(self->lines, count);
unsigned int top = self->margin_top, bottom = self->margin_bottom;
while (count > 0) {
count--;
INDEX_DOWN;
}
}
void
screen_carriage_return(Screen *self) {
@@ -710,13 +739,7 @@ screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
void
screen_cursor_to_line(Screen *self, unsigned int line) {
unsigned int y = MAX(line, 1) - 1;
y += self->margin_top;
if (y != self->cursor->y) {
self->cursor->y = y;
screen_ensure_bounds(self, false); // TODO: should we also restrict the cursor to the scrolling region?
tracker_cursor_changed(self->change_tracker);
}
screen_cursor_position(self, line, self->cursor->x + 1);
}
// }}}
@@ -1310,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"},
@@ -1319,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:
@@ -50,17 +57,71 @@ def sanitize_title(x):
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19]', '', x))
@lru_cache()
def load_libx11():
import ctypes
from ctypes.util import find_library
libx11 = ctypes.CDLL(find_library('X11'))
ans = []
def cdef(name, restype, *argtypes):
f = getattr(libx11, name)
if restype is not None:
f.restype = restype
if argtypes:
f.argtypes = argtypes
ans.append(f)
cdef('XOpenDisplay', ctypes.c_void_p, ctypes.c_char_p)
cdef('XCloseDisplay', ctypes.c_int, ctypes.c_void_p)
cdef('XResourceManagerString', ctypes.c_char_p, ctypes.c_void_p)
return ans
def parse_xrdb(raw):
q = 'Xft.dpi:\t'
for line in raw.decode('utf-8').splitlines():
if line.startswith(q):
return float(line[len(q):])
def x11_dpi_native():
XOpenDisplay, XCloseDisplay, XResourceManagerString = load_libx11()
display = XOpenDisplay(None)
if display is None:
raise RuntimeError('Could not connect to the X server')
try:
raw = XResourceManagerString(display)
return parse_xrdb(raw)
finally:
XCloseDisplay(display)
def x11_dpi():
try:
return x11_dpi_native()
except Exception:
pass
try:
raw = subprocess.check_output(['xrdb', '-query'])
return parse_xrdb(raw)
except Exception:
pass
def get_logical_dpi():
if not hasattr(get_logical_dpi, 'ans'):
if isosx:
# TODO: Investigate if this needs a different implementation on OS X
get_logical_dpi.ans = glfw_get_physical_dpi()
else:
raw = subprocess.check_output(['xdpyinfo']).decode('utf-8')
m = re.search(
r'^\s*resolution:\s*(\d+)+x(\d+)', raw, flags=re.MULTILINE
)
get_logical_dpi.ans = int(m.group(1)), int(m.group(2))
# See https://github.com/glfw/glfw/issues/1019 for why we cant use
# glfw_get_physical_dpi()
dpi = x11_dpi()
if dpi is None:
get_logical_dpi.ans = glfw_get_physical_dpi()
else:
get_logical_dpi.ans = dpi, dpi
return get_logical_dpi.ans
@@ -120,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(
@@ -142,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()
@@ -154,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

@@ -30,11 +30,13 @@ DYNAMIC_COLOR_CODES = {
19: DynamicColor.highlight_fg,
}
DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()})
dump_bytes_opened = False
class Window:
def __init__(self, tab, child, opts, args):
global dump_bytes_opened
self.tabref = weakref.ref(tab)
self.override_title = None
self.last_mouse_cursor_pos = 0, 0
@@ -50,7 +52,9 @@ class Window:
self.screen = Screen(self, 24, 80, opts.scrollback_lines)
self.read_bytes = partial(read_bytes_dump, self.dump_commands) if args.dump_commands or args.dump_bytes else read_bytes
if args.dump_bytes:
self.dump_bytes_to = open(args.dump_bytes, 'ab')
mode = 'ab' if dump_bytes_opened else 'wb'
self.dump_bytes_to = open(args.dump_bytes, mode)
dump_bytes_opened = True
self.draw_dump_buf = []
self.write_buf = memoryview(b'')
self.char_grid = CharGrid(self.screen, opts)

81
kitty_tests/keys.py Normal file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from functools import partial
import kitty.fast_data_types as defines
from kitty.keys import (
interpret_key_event, modify_complex_key, modify_key_bytes, smkx_key_map
)
from . import BaseTest
class DummyWindow:
def __init__(self):
self.screen = self
self.extended_keyboard = False
self.cursor_key_mode = True
class TestParser(BaseTest):
def test_modify_complex_key(self):
self.ae(modify_complex_key('kcuu1', 4), b'\033[1;4A')
self.ae(modify_complex_key('kcuu1', 3), b'\033[1;3A')
self.ae(modify_complex_key('kf5', 3), b'\033[15;3~')
self.assertRaises(ValueError, modify_complex_key, 'kri', 3)
def test_interpret_key_event(self):
# test rmkx/smkx
w = DummyWindow()
def k(expected, key, mods=0):
actual = interpret_key_event(
getattr(defines, 'GLFW_KEY_' + key),
0,
mods,
w,
defines.GLFW_PRESS,
get_localized_key=lambda k, s: k
)
self.ae(b'\033' + expected.encode('ascii'), actual)
for ckm, mch in {True: 'O', False: '['}.items():
w.cursor_key_mode = ckm
for name, ch in {
'UP': 'A',
'DOWN': 'B',
'RIGHT': 'C',
'LEFT': 'D',
'HOME': 'H',
'END': 'F',
}.items():
k(mch + ch, name)
w.cursor_key_mode = True
# test remaining special keys
for key, num in zip('INSERT DELETE PAGE_UP PAGE_DOWN'.split(), '2356'):
k('[' + num + '~', key)
for key, num in zip('1234', 'PQRS'):
k('O' + num, 'F' + key)
for key, num in zip(range(5, 13), (15, 17, 18, 19, 20, 21, 23, 24)):
k('[' + str(num) + '~', 'F{}'.format(key))
# test modifiers
SPECIAL_KEYS = 'UP DOWN RIGHT LEFT HOME END INSERT DELETE PAGE_UP PAGE_DOWN '
for i in range(1, 13):
SPECIAL_KEYS += 'F{} '.format(i)
SPECIAL_KEYS = SPECIAL_KEYS.strip().split()
for mods, num in zip(('CONTROL', 'ALT', 'SHIFT+ALT'), '534'):
fmods = 0
num = int(num)
for m in mods.split('+'):
fmods |= getattr(defines, 'GLFW_MOD_' + m)
km = partial(k, mods=fmods)
for key in SPECIAL_KEYS:
keycode = getattr(defines, 'GLFW_KEY_' + key)
base_key = smkx_key_map[keycode]
km(modify_key_bytes(base_key, num).decode('ascii')[1:], key)

View File

@@ -173,7 +173,7 @@ class TestParser(BaseTest):
c.clear()
pb('\033]2;;;;\x07', ('set_title', ';;;'))
self.ae(c.titlebuf, ';;;')
pb('\033]110\x07', ('set_dynamic_color', ''))
pb('\033]110\x07', ('set_dynamic_color', 110, ''))
self.ae(c.colorbuf, '')
def test_dcs_codes(self):

View File

@@ -332,6 +332,7 @@ There are various problems with the current state of keyboard handling. They
include:
* No way to use modifiers other than `Ctrl` and `Alt`
* No way to use multiple modifier keys, other than, `Shift+Alt`.
* No way to handle different types of keyboard events, such as press, release or repeat
* No reliable way to distinguish single `Esc` keypresses from the
start of a escape sequence. Currently, client programs use

View File

@@ -115,9 +115,9 @@ 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 {} {} -DNDEBUG -fwrapv {} {} -pipe {}'
' -pedantic-errors -Werror {} {} -D{}DEBUG -fwrapv {} {} -pipe {} -fvisibility=hidden'
).format(
optimize, ' '.join(sanitize_args), stack_protector, missing_braces, '-march=native'
optimize, ' '.join(sanitize_args), ('' if debug else 'N'), stack_protector, missing_braces, '-march=native'
if native_optimizations else ''
)
)
@@ -131,7 +131,9 @@ def init_env(debug=False, sanitize=False, native_optimizations=True):
ldflags += shlex.split(os.environ.get('LDFLAGS', ''))
cflags.append('-pthread')
cflags.append('-DPRIMARY_VERSION={}'.format(version[0]))
# We add 4000 to the primary version because vim turns on SGR mouse mode
# automatically if this version is high enough
cflags.append('-DPRIMARY_VERSION={}'.format(version[0] + 4000))
cflags.append('-DSECONDARY_VERSION={}'.format(version[1]))
if not is_travis and not isosx and subprocess.Popen(
[PKGCONFIG, 'glew', '--atleast-version=2']
@@ -264,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:
@@ -286,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):
@@ -320,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)