Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5117a2c17a | ||
|
|
ebce1d7c61 | ||
|
|
e4668c1aff | ||
|
|
2519c49c02 | ||
|
|
9e512ff58a | ||
|
|
ed50595aca | ||
|
|
9d62d087e0 | ||
|
|
6b933e33f5 | ||
|
|
a86931f401 | ||
|
|
fc4e1595e8 | ||
|
|
b36c3f3425 | ||
|
|
e427fd1233 | ||
|
|
dd3af45043 | ||
|
|
304d42d4c2 | ||
|
|
abca9280e7 | ||
|
|
9a103f3979 | ||
|
|
270dde7020 | ||
|
|
11de18e737 | ||
|
|
62db44c71e | ||
|
|
3d5c65eaea | ||
|
|
ea298f95f2 | ||
|
|
2f21e0e341 | ||
|
|
ae62d36a4a | ||
|
|
76e3101d9b | ||
|
|
c3442545a8 | ||
|
|
3a9b0faa06 | ||
|
|
4989b1f8bb | ||
|
|
348fe4ada4 | ||
|
|
bbc6b2d86a | ||
|
|
b4d4ed718f | ||
|
|
836724709e | ||
|
|
96d2567815 | ||
|
|
419f43ceed | ||
|
|
47851ebb1b | ||
|
|
24a4fbd987 | ||
|
|
8047743882 | ||
|
|
e9b5963610 | ||
|
|
a1d4630a25 | ||
|
|
fafd710ce3 | ||
|
|
a79bb3add2 | ||
|
|
b3a718b1e4 | ||
|
|
952aa7ad4a | ||
|
|
149d606154 | ||
|
|
ad21c7ed0f | ||
|
|
38d2839206 | ||
|
|
24d0bb8bd5 | ||
|
|
08f336769f | ||
|
|
5525d4db49 | ||
|
|
448ba26257 | ||
|
|
1d1138ca31 | ||
|
|
357a415386 | ||
|
|
a65856ec98 | ||
|
|
83855e16ce | ||
|
|
ccf66fc621 | ||
|
|
c27b597951 | ||
|
|
85dbae1de4 | ||
|
|
cd1ba334c1 | ||
|
|
1cff4f9d29 | ||
|
|
d180601711 | ||
|
|
01d0e7474f |
@@ -82,7 +82,7 @@ the following dependencies are installed first.
|
|||||||
* glfw >= 3.2
|
* glfw >= 3.2
|
||||||
* glew >= 2.0 (not needed on macOS)
|
* glew >= 2.0 (not needed on macOS)
|
||||||
* fontconfig (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)
|
* gcc or clang (required only for building)
|
||||||
* pkg-config (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
|
This applies to creating packages for kitty for macOS package managers such as
|
||||||
brew or MacPorts as well.
|
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
|
== Resources on terminal behavior
|
||||||
|
|
||||||
http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
|
|||||||
@@ -5,11 +5,12 @@
|
|||||||
from ctypes import addressof
|
from ctypes import addressof
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from .constants import GLfloat, GLint, GLuint, viewport_size
|
from .constants import GLfloat, GLint, GLuint, viewport_size
|
||||||
from .fast_data_types import GL_TRIANGLE_FAN, glMultiDrawArrays, glUniform3fv
|
from .fast_data_types import GL_TRIANGLE_FAN, glMultiDrawArrays, glUniform3fv
|
||||||
from .shaders import ShaderProgram
|
from .shaders import ShaderProgram
|
||||||
from .utils import get_dpi
|
from .utils import pt_to_px
|
||||||
|
|
||||||
|
|
||||||
def as_color(c):
|
def as_color(c):
|
||||||
@@ -31,7 +32,8 @@ def as_rect(left, top, right, bottom, color=0):
|
|||||||
class BordersProgram(ShaderProgram):
|
class BordersProgram(ShaderProgram):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
ShaderProgram.__init__(self, '''\
|
ShaderProgram.__init__(
|
||||||
|
self, '''\
|
||||||
uniform vec3 colors[3];
|
uniform vec3 colors[3];
|
||||||
in vec3 rect;
|
in vec3 rect;
|
||||||
out vec3 color;
|
out vec3 color;
|
||||||
@@ -57,34 +59,73 @@ void main() {
|
|||||||
glUniform3fv(self.uniform_location('colors'), 3, addressof(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:
|
class Borders:
|
||||||
|
|
||||||
def __init__(self, opts):
|
def __init__(self, opts):
|
||||||
self.is_dirty = False
|
self.is_dirty = False
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
self.can_render = False
|
self.can_render = False
|
||||||
dpix, dpiy = get_dpi()['logical']
|
self.border_width = pt_to_px(opts.window_border_width)
|
||||||
dpi = (dpix + dpiy) / 2
|
self.padding_width = pt_to_px(opts.window_padding_width)
|
||||||
self.border_width = round(opts.window_border_width * dpi / 72)
|
|
||||||
self.color_buf = (GLfloat * 9)(
|
self.color_buf = (GLfloat * 9)(
|
||||||
*as_color(opts.background),
|
*as_color(opts.background), *as_color(opts.active_border_color),
|
||||||
*as_color(opts.active_border_color),
|
*as_color(opts.inactive_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 = []
|
rects = []
|
||||||
for br in chain(current_layout.blank_rects, extra_blank_rects):
|
for br in chain(current_layout.blank_rects, extra_blank_rects):
|
||||||
rects.extend(as_rect(*br))
|
rects.extend(as_rect(*br))
|
||||||
if draw_window_borders and self.border_width > 0:
|
bw, pw = self.border_width, self.padding_width
|
||||||
bw = self.border_width
|
fw = bw + pw
|
||||||
|
border = border_maker(rects)
|
||||||
|
|
||||||
|
if fw > 0:
|
||||||
for w in windows:
|
for w in windows:
|
||||||
g = w.geometry
|
g = w.geometry
|
||||||
color = 1 if w is active_window else 2
|
if bw > 0 and draw_window_borders:
|
||||||
rects.extend(as_rect(g.left - bw, g.top - bw, g.left, g.bottom + bw, color))
|
# Draw the border rectangles
|
||||||
rects.extend(as_rect(g.left - bw, g.top - bw, g.right + bw, g.top, color))
|
color = 1 if w is active_window else 2
|
||||||
rects.extend(as_rect(g.right, g.top - bw, g.right + bw, g.bottom + bw, color))
|
border(
|
||||||
rects.extend(as_rect(g.left - bw, g.bottom, g.right + bw, g.bottom + bw, color))
|
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:
|
with self.lock:
|
||||||
self.num_of_rects = len(rects) // 12
|
self.num_of_rects = len(rects) // 12
|
||||||
self.rects = (GLfloat * len(rects))()
|
self.rects = (GLfloat * len(rects))()
|
||||||
@@ -108,4 +149,7 @@ class Borders:
|
|||||||
program.send_data(self.rects)
|
program.send_data(self.rects)
|
||||||
program.set_colors(self.color_buf)
|
program.set_colors(self.color_buf)
|
||||||
self.is_dirty = False
|
self.is_dirty = False
|
||||||
glMultiDrawArrays(GL_TRIANGLE_FAN, addressof(self.starts), addressof(self.counts), self.num_of_rects)
|
glMultiDrawArrays(
|
||||||
|
GL_TRIANGLE_FAN,
|
||||||
|
addressof(self.starts),
|
||||||
|
addressof(self.counts), self.num_of_rects)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from .fonts.render import set_font_family
|
|||||||
from .borders import BordersProgram
|
from .borders import BordersProgram
|
||||||
from .char_grid import cursor_shader, cell_shader
|
from .char_grid import cursor_shader, cell_shader
|
||||||
from .constants import is_key_pressed
|
from .constants import is_key_pressed
|
||||||
from .keys import interpret_text_event, interpret_key_event, get_shortcut
|
from .keys import interpret_text_event, interpret_key_event, get_shortcut, get_sent_data
|
||||||
from .session import create_session
|
from .session import create_session
|
||||||
from .shaders import Sprites, ShaderProgram
|
from .shaders import Sprites, ShaderProgram
|
||||||
from .tabs import TabManager, SpecialWindow
|
from .tabs import TabManager, SpecialWindow
|
||||||
@@ -271,12 +271,13 @@ class Boss(Thread):
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def on_text_input(self, window, codepoint, mods):
|
def on_text_input(self, window, codepoint, mods):
|
||||||
data = interpret_text_event(codepoint, mods)
|
w = self.active_window
|
||||||
if data:
|
if w is not None:
|
||||||
w = self.active_window
|
yield w
|
||||||
if w is not None:
|
if w is not None:
|
||||||
yield w
|
data = interpret_text_event(codepoint, mods, w)
|
||||||
w.write_to_child(data)
|
if data:
|
||||||
|
w.write_to_child(data)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def on_key(self, window, key, scancode, action, mods):
|
def on_key(self, window, key, scancode, action, mods):
|
||||||
@@ -307,7 +308,7 @@ class Boss(Thread):
|
|||||||
return
|
return
|
||||||
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS:
|
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS:
|
||||||
window.scroll_end()
|
window.scroll_end()
|
||||||
data = interpret_key_event(key, scancode, mods, window, action)
|
data = get_sent_data(self.opts.send_text_map, key, scancode, mods, window, action) or interpret_key_event(key, scancode, mods, window, action)
|
||||||
if data:
|
if data:
|
||||||
window.write_to_child(data)
|
window.write_to_child(data)
|
||||||
|
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ class CharGrid:
|
|||||||
if raw is None:
|
if raw is None:
|
||||||
return 0
|
return 0
|
||||||
val = to_color(raw)
|
val = to_color(raw)
|
||||||
return None if val is None else (color_as_int(val) << 8) | 1
|
return None if val is None else (color_as_int(val) << 8) | 2
|
||||||
|
|
||||||
for which, val in changes.items():
|
for which, val in changes.items():
|
||||||
val = item(val)
|
val = item(val)
|
||||||
@@ -322,10 +322,9 @@ class CharGrid:
|
|||||||
sprites = get_boss().sprites
|
sprites = get_boss().sprites
|
||||||
is_dirty = self.screen.is_dirty()
|
is_dirty = self.screen.is_dirty()
|
||||||
with sprites.lock:
|
with sprites.lock:
|
||||||
fg = self.screen.default_fg
|
fg, bg = self.screen.default_fg, self.screen.default_bg
|
||||||
fg = fg >> 8 if fg & 1 else self.default_fg
|
fg = fg >> 8 if fg & 2 else self.default_fg
|
||||||
bg = self.screen.default_bg
|
bg = bg >> 8 if bg & 2 else self.default_bg
|
||||||
bg = bg >> 8 if bg & 1 else self.default_bg
|
|
||||||
cursor_changed, history_line_added_count = self.screen.update_cell_data(
|
cursor_changed, history_line_added_count = self.screen.update_cell_data(
|
||||||
sprites.backend, self.color_profile, addressof(self.main_sprite_map), fg, bg, force_full_refresh)
|
sprites.backend, self.color_profile, addressof(self.main_sprite_map), fg, bg, force_full_refresh)
|
||||||
if self.scrolled_by:
|
if self.scrolled_by:
|
||||||
@@ -475,9 +474,9 @@ class CharGrid:
|
|||||||
if self.render_buf_is_dirty or sel != self.last_rendered_selection:
|
if self.render_buf_is_dirty or sel != self.last_rendered_selection:
|
||||||
memmove(buf, self.render_buf, sizeof(type(buf)))
|
memmove(buf, self.render_buf, sizeof(type(buf)))
|
||||||
fg = self.screen.highlight_fg
|
fg = self.screen.highlight_fg
|
||||||
fg = fg >> 8 if fg & 1 else self.highlight_fg
|
fg = fg >> 8 if fg & 2 else self.highlight_fg
|
||||||
bg = self.screen.highlight_bg
|
bg = self.screen.highlight_bg
|
||||||
bg = bg >> 8 if bg & 1 else self.highlight_bg
|
bg = bg >> 8 if bg & 2 else self.highlight_bg
|
||||||
self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], fg, bg)
|
self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], fg, bg)
|
||||||
if self.render_buf_is_dirty or self.last_rendered_selection != sel:
|
if self.render_buf_is_dirty or self.last_rendered_selection != sel:
|
||||||
sprites.set_sprite_map(self.buffer_id, buf)
|
sprites.set_sprite_map(self.buffer_id, buf)
|
||||||
@@ -503,7 +502,7 @@ class CharGrid:
|
|||||||
left = sg.xstart + cursor.x * sg.dx
|
left = sg.xstart + cursor.x * sg.dx
|
||||||
top = sg.ystart - cursor.y * sg.dy
|
top = sg.ystart - cursor.y * sg.dy
|
||||||
cc = self.screen.cursor_color
|
cc = self.screen.cursor_color
|
||||||
col = color_from_int(cc >> 8) if cc & 1 else self.opts.cursor
|
col = color_from_int(cc >> 8) if cc & 2 else self.opts.cursor
|
||||||
shape = cursor.shape or self.default_cursor.shape
|
shape = cursor.shape or self.default_cursor.shape
|
||||||
alpha = self.opts.cursor_opacity
|
alpha = self.opts.cursor_opacity
|
||||||
if alpha < 1.0 and shape == CURSOR_BLOCK:
|
if alpha < 1.0 and shape == CURSOR_BLOCK:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import sys
|
|||||||
|
|
||||||
|
|
||||||
CSI = '\033['
|
CSI = '\033['
|
||||||
|
OSC = '\033]'
|
||||||
|
|
||||||
|
|
||||||
def write(x):
|
def write(x):
|
||||||
@@ -73,6 +74,10 @@ def screen_set_margins(t, b):
|
|||||||
write(CSI + '%d;%dr' % (t, b))
|
write(CSI + '%d;%dr' % (t, b))
|
||||||
|
|
||||||
|
|
||||||
|
def screen_indexn(n):
|
||||||
|
write(CSI + '%dS' % n)
|
||||||
|
|
||||||
|
|
||||||
def screen_erase_in_display(how, private):
|
def screen_erase_in_display(how, private):
|
||||||
write(CSI + ('?' if private else '') + str(how) + 'J')
|
write(CSI + ('?' if private else '') + str(how) + 'J')
|
||||||
|
|
||||||
@@ -85,6 +90,10 @@ def screen_cursor_up2(count):
|
|||||||
write(CSI + '%dA' % count)
|
write(CSI + '%dA' % count)
|
||||||
|
|
||||||
|
|
||||||
|
def screen_cursor_down(count):
|
||||||
|
write(CSI + '%dB' % count)
|
||||||
|
|
||||||
|
|
||||||
def screen_carriage_return():
|
def screen_carriage_return():
|
||||||
write('\r')
|
write('\r')
|
||||||
|
|
||||||
@@ -101,11 +110,29 @@ def draw(*a):
|
|||||||
write(' '.join(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):
|
def replay(raw):
|
||||||
for line in raw.splitlines():
|
for line in raw.splitlines():
|
||||||
if line.strip():
|
if line.strip():
|
||||||
cmd, rest = line.partition(' ')[::2]
|
cmd, rest = line.partition(' ')[::2]
|
||||||
if cmd in {'draw', 'set_title', 'set_icon'}:
|
if cmd in {'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color'}:
|
||||||
globals()[cmd](rest)
|
globals()[cmd](rest)
|
||||||
else:
|
else:
|
||||||
rest = map(int, rest.split()) if rest else ()
|
rest = map(int, rest.split()) if rest else ()
|
||||||
@@ -115,4 +142,7 @@ def replay(raw):
|
|||||||
def main(path):
|
def main(path):
|
||||||
raw = open(path).read()
|
raw = open(path).read()
|
||||||
replay(raw)
|
replay(raw)
|
||||||
input()
|
try:
|
||||||
|
input()
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
pass
|
||||||
|
|||||||
43
kitty/cocoa_window.m
Normal file
43
kitty/cocoa_window.m
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||||
|
#define NSWindowStyleMaskTitled NSTitledWindowMask
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
cocoa_hide_titlebar(PyObject UNUSED *self, PyObject *window_id) {
|
||||||
|
NSWindow *window = (NSWindow*)PyLong_AsVoidPtr(window_id);
|
||||||
|
|
||||||
|
@try {
|
||||||
|
[window setStyleMask:
|
||||||
|
[window styleMask] & ~NSWindowStyleMaskTitled];
|
||||||
|
} @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]);
|
||||||
|
}
|
||||||
@@ -99,6 +99,7 @@ as_color(ColorProfile *self, PyObject *val) {
|
|||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
col = entry >> 8;
|
col = entry >> 8;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ans = Py_None; Py_INCREF(Py_None);
|
ans = Py_None; Py_INCREF(Py_None);
|
||||||
}
|
}
|
||||||
|
|||||||
150
kitty/config.py
150
kitty/config.py
@@ -2,6 +2,7 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import ast
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -84,16 +85,22 @@ named_keys = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_key(val, keymap):
|
def parse_shortcut(sc):
|
||||||
sc, action = val.partition(' ')[::2]
|
|
||||||
action = action.strip()
|
|
||||||
sc = sc.strip()
|
|
||||||
if not sc or not action:
|
|
||||||
return
|
|
||||||
parts = sc.split('+')
|
parts = sc.split('+')
|
||||||
mods = parse_mods(parts[:-1])
|
mods = parse_mods(parts[:-1])
|
||||||
key = parts[-1].upper()
|
key = parts[-1].upper()
|
||||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
||||||
|
if key is not None:
|
||||||
|
return mods, key
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_key(val, keymap):
|
||||||
|
sc, action = val.partition(' ')[::2]
|
||||||
|
sc, action = sc.strip(), action.strip()
|
||||||
|
if not sc or not action:
|
||||||
|
return
|
||||||
|
mods, key = parse_shortcut(sc)
|
||||||
if key is None:
|
if key is None:
|
||||||
safe_print(
|
safe_print(
|
||||||
'Shortcut: {} has an unknown key, ignoring'.format(val),
|
'Shortcut: {} has an unknown key, ignoring'.format(val),
|
||||||
@@ -137,6 +144,39 @@ def parse_symbol_map(val):
|
|||||||
return symbol_map
|
return symbol_map
|
||||||
|
|
||||||
|
|
||||||
|
def parse_send_text(val):
|
||||||
|
parts = val.split(' ')
|
||||||
|
|
||||||
|
def abort(msg):
|
||||||
|
safe_print(
|
||||||
|
'Send text: {} is invalid ({}), ignoring'.format(val, msg), file=sys.stderr
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if len(parts) < 3:
|
||||||
|
return abort('Incomplete')
|
||||||
|
|
||||||
|
text = ' '.join(parts[2:])
|
||||||
|
mode, sc = parts[:2]
|
||||||
|
mods, key = parse_shortcut(sc.strip())
|
||||||
|
if key is None:
|
||||||
|
return abort('Invalid shortcut')
|
||||||
|
text = ast.literal_eval("'''" + text + "'''").encode('utf-8')
|
||||||
|
if not text:
|
||||||
|
return abort('Empty text')
|
||||||
|
|
||||||
|
if mode in ('all', '*'):
|
||||||
|
modes = parse_send_text.all_modes
|
||||||
|
else:
|
||||||
|
modes = frozenset(mode.split(',')).intersection(parse_send_text.all_modes)
|
||||||
|
if not modes:
|
||||||
|
return abort('Invalid keyboard modes')
|
||||||
|
return {mode: {(mods, key): text} for mode in modes}
|
||||||
|
|
||||||
|
|
||||||
|
parse_send_text.all_modes = frozenset({'normal', 'application', 'kitty'})
|
||||||
|
|
||||||
|
|
||||||
def to_open_url_modifiers(val):
|
def to_open_url_modifiers(val):
|
||||||
return parse_mods(val.split('+'))
|
return parse_mods(val.split('+'))
|
||||||
|
|
||||||
@@ -150,29 +190,40 @@ def to_layout_names(raw):
|
|||||||
raise ValueError('The window layout {} is unknown'.format(p))
|
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 = {
|
type_map = {
|
||||||
'scrollback_lines': int,
|
'scrollback_lines': positive_int,
|
||||||
'scrollback_pager': shlex.split,
|
'scrollback_pager': shlex.split,
|
||||||
'scrollback_in_new_tab': to_bool,
|
'scrollback_in_new_tab': to_bool,
|
||||||
'font_size': to_font_size,
|
'font_size': to_font_size,
|
||||||
'font_size_delta': float,
|
'font_size_delta': positive_float,
|
||||||
'cursor_shape': to_cursor_shape,
|
'cursor_shape': to_cursor_shape,
|
||||||
'cursor_opacity': to_opacity,
|
'cursor_opacity': to_opacity,
|
||||||
'open_url_modifiers': to_open_url_modifiers,
|
'open_url_modifiers': to_open_url_modifiers,
|
||||||
'repaint_delay': int,
|
'repaint_delay': positive_int,
|
||||||
'window_border_width': float,
|
'window_border_width': positive_float,
|
||||||
|
'window_margin_width': positive_float,
|
||||||
|
'window_padding_width': positive_float,
|
||||||
'wheel_scroll_multiplier': float,
|
'wheel_scroll_multiplier': float,
|
||||||
'visual_bell_duration': float,
|
'visual_bell_duration': positive_float,
|
||||||
'enable_audio_bell': to_bool,
|
'enable_audio_bell': to_bool,
|
||||||
'click_interval': float,
|
'click_interval': positive_float,
|
||||||
'mouse_hide_wait': float,
|
'mouse_hide_wait': positive_float,
|
||||||
'cursor_blink_interval': float,
|
'cursor_blink_interval': positive_float,
|
||||||
'cursor_stop_blinking_after': float,
|
'cursor_stop_blinking_after': positive_float,
|
||||||
'enabled_layouts': to_layout_names,
|
'enabled_layouts': to_layout_names,
|
||||||
'remember_window_size': to_bool,
|
'remember_window_size': to_bool,
|
||||||
'initial_window_width': int,
|
'initial_window_width': positive_int,
|
||||||
'initial_window_height': int,
|
'initial_window_height': positive_int,
|
||||||
'use_system_wcwidth': to_bool,
|
'use_system_wcwidth': to_bool,
|
||||||
|
'macos_hide_titlebar': to_bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
for name in (
|
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)
|
type_map['%s_tab_%s' % (a, b)] = lambda x: to_color(x, validate=True)
|
||||||
|
|
||||||
|
|
||||||
def parse_config(lines):
|
def parse_config(lines, check_keys=True):
|
||||||
ans = {'keymap': {}, 'symbol_map': {}}
|
ans = {'keymap': {}, 'symbol_map': {}, 'send_text_map': {'kitty': {}, 'normal': {}, 'application': {}}}
|
||||||
|
if check_keys:
|
||||||
|
all_keys = defaults._asdict()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line or line.startswith('#'):
|
if not line or line.startswith('#'):
|
||||||
@@ -202,6 +255,15 @@ def parse_config(lines):
|
|||||||
if key == 'symbol_map':
|
if key == 'symbol_map':
|
||||||
ans['symbol_map'].update(parse_symbol_map(val))
|
ans['symbol_map'].update(parse_symbol_map(val))
|
||||||
continue
|
continue
|
||||||
|
if key == 'send_text':
|
||||||
|
stvals = parse_send_text(val)
|
||||||
|
for k, v in ans['send_text_map'].items():
|
||||||
|
v.update(stvals.get(k, {}))
|
||||||
|
continue
|
||||||
|
if check_keys:
|
||||||
|
if key not in all_keys:
|
||||||
|
safe_print('Ignoring unknown config key: {}'.format(key), file=sys.stderr)
|
||||||
|
continue
|
||||||
tm = type_map.get(key)
|
tm = type_map.get(key)
|
||||||
if tm is not None:
|
if tm is not None:
|
||||||
val = tm(val)
|
val = tm(val)
|
||||||
@@ -212,38 +274,42 @@ def parse_config(lines):
|
|||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf')
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf')
|
||||||
) as f:
|
) as f:
|
||||||
defaults = parse_config(f.readlines())
|
defaults = parse_config(f.readlines(), check_keys=False)
|
||||||
Options = namedtuple('Defaults', ','.join(defaults.keys()))
|
Options = namedtuple('Defaults', ','.join(defaults.keys()))
|
||||||
defaults = Options(**defaults)
|
defaults = Options(**defaults)
|
||||||
actions = frozenset(defaults.keymap.values())
|
actions = frozenset(defaults.keymap.values())
|
||||||
|
|
||||||
|
|
||||||
def update_dict(a, b):
|
def merge_keymaps(defaults, newvals):
|
||||||
a.update(b)
|
ans = defaults.copy()
|
||||||
return a
|
for k, v in newvals.items():
|
||||||
|
if v in {'noop', 'no-op', 'no_op'}:
|
||||||
|
ans.pop(k, None)
|
||||||
|
continue
|
||||||
|
if v in actions:
|
||||||
|
ans[k] = v
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def merge_dicts(vals, defaults):
|
def merge_dicts(defaults, newvals):
|
||||||
return {
|
ans = defaults.copy()
|
||||||
k: update_dict(v, vals.get(k, {}))
|
ans.update(newvals)
|
||||||
if isinstance(v, dict) else vals.get(k, v)
|
return ans
|
||||||
for k, v in defaults.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def merge_configs(ans, vals):
|
def merge_configs(defaults, vals):
|
||||||
vals['keymap'] = {
|
ans = {}
|
||||||
k: v
|
for k, v in defaults.items():
|
||||||
for k, v in vals.get('keymap', {}).items() if v in actions
|
if isinstance(v, dict):
|
||||||
}
|
newvals = vals.get(k, {})
|
||||||
remove_keys = {
|
if k == 'keymap':
|
||||||
k
|
ans['keymap'] = merge_keymaps(v, newvals)
|
||||||
for k, v in vals.get('keymap', {}).items()
|
elif k == 'send_text_map':
|
||||||
if v in ('noop', 'no-op', 'no_op')
|
ans[k] = {m: merge_dicts(mm, newvals.get(m, {})) for m, mm in v.items()}
|
||||||
}
|
else:
|
||||||
ans = merge_dicts(vals, ans)
|
ans[k] = merge_dicts(v, newvals)
|
||||||
for k in remove_keys:
|
else:
|
||||||
ans['keymap'].pop(k, None)
|
ans[k] = vals.get(k, v)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from .fast_data_types import (
|
|||||||
GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER)
|
GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER)
|
||||||
|
|
||||||
appname = 'kitty'
|
appname = 'kitty'
|
||||||
version = (0, 2, 4)
|
version = (0, 2, 8)
|
||||||
str_version = '.'.join(map(str, version))
|
str_version = '.'.join(map(str, version))
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
isosx = 'darwin' in _plat
|
isosx = 'darwin' in _plat
|
||||||
@@ -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')))
|
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)
|
ans = os.path.join(candidate, appname)
|
||||||
try:
|
os.makedirs(ans, exist_ok=True)
|
||||||
os.makedirs(ans)
|
|
||||||
except FileExistsError:
|
|
||||||
pass
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ change_wcwidth_wrap(PyObject UNUSED *self, PyObject *use9) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
|
||||||
|
char *devnull = NULL;
|
||||||
|
if (!PyArg_ParseTuple(args, "s", &devnull)) return NULL;
|
||||||
|
if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
|
||||||
|
if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
|
||||||
|
if (freopen(devnull, "w", stderr) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef module_methods[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
GL_METHODS
|
GL_METHODS
|
||||||
{"drain_read", (PyCFunction)drain_read, METH_O, ""},
|
{"drain_read", (PyCFunction)drain_read, METH_O, ""},
|
||||||
@@ -38,6 +49,7 @@ static PyMethodDef module_methods[] = {
|
|||||||
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
|
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
|
||||||
{"read_bytes", (PyCFunction)read_bytes, METH_VARARGS, ""},
|
{"read_bytes", (PyCFunction)read_bytes, METH_VARARGS, ""},
|
||||||
{"read_bytes_dump", (PyCFunction)read_bytes_dump, METH_VARARGS, ""},
|
{"read_bytes_dump", (PyCFunction)read_bytes_dump, METH_VARARGS, ""},
|
||||||
|
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
||||||
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
|
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
|
||||||
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
|
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
@@ -58,7 +70,7 @@ static struct PyModuleDef module = {
|
|||||||
|
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
EXPORTED PyMODINIT_FUNC
|
||||||
PyInit_fast_data_types(void) {
|
PyInit_fast_data_types(void) {
|
||||||
PyObject *m;
|
PyObject *m;
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#define PY_SSIZE_T_CLEAN
|
#define PY_SSIZE_T_CLEAN
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
#define UNUSED __attribute__ ((unused))
|
#define UNUSED __attribute__ ((unused))
|
||||||
|
#define EXPORTED __attribute__ ((visibility ("default")))
|
||||||
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
|
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
|
||||||
#define MIN(x, y) (((x) > (y)) ? (y) : (x))
|
#define MIN(x, y) (((x) > (y)) ? (y) : (x))
|
||||||
#define xstr(s) str(s)
|
#define xstr(s) str(s)
|
||||||
@@ -376,6 +377,8 @@ void screen_change_default_color(Screen *self, unsigned int which, uint32_t col)
|
|||||||
void screen_alignment_display(Screen *self);
|
void screen_alignment_display(Screen *self);
|
||||||
void screen_reverse_index(Screen *self);
|
void screen_reverse_index(Screen *self);
|
||||||
void screen_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_reset(Screen *self);
|
||||||
void screen_set_tab_stop(Screen *self);
|
void screen_set_tab_stop(Screen *self);
|
||||||
void screen_tab(Screen *self);
|
void screen_tab(Screen *self);
|
||||||
|
|||||||
89
kitty/glfw.c
89
kitty/glfw.c
@@ -7,6 +7,10 @@
|
|||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
#include <structmember.h>
|
#include <structmember.h>
|
||||||
#include <GLFW/glfw3.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)
|
#if GLFW_VERSION_MAJOR < 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR < 2)
|
||||||
#error "glfw >= 3.2 required"
|
#error "glfw >= 3.2 required"
|
||||||
@@ -93,7 +97,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
|||||||
self = (Window *)type->tp_alloc(type, 0);
|
self = (Window *)type->tp_alloc(type, 0);
|
||||||
if (self != NULL) {
|
if (self != NULL) {
|
||||||
self->window = glfwCreateWindow(width, height, title, NULL, 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++) {
|
for(i = 0; i < MAX_WINDOWS; i++) {
|
||||||
if (window_weakrefs[i] == NULL) { window_weakrefs[i] = self; break; }
|
if (window_weakrefs[i] == NULL) { window_weakrefs[i] = self; break; }
|
||||||
}
|
}
|
||||||
@@ -178,10 +182,8 @@ glfw_post_empty_event(PyObject UNUSED *self) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject*
|
static PyObject*
|
||||||
glfw_get_physical_dpi(PyObject UNUSED *self) {
|
get_physical_dpi(GLFWmonitor *m) {
|
||||||
GLFWmonitor *m = glfwGetPrimaryMonitor();
|
|
||||||
if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; }
|
|
||||||
int width = 0, height = 0;
|
int width = 0, height = 0;
|
||||||
glfwGetMonitorPhysicalSize(m, &width, &height);
|
glfwGetMonitorPhysicalSize(m, &width, &height);
|
||||||
if (width == 0 || height == 0) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor size"); return NULL; }
|
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);
|
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*
|
PyObject*
|
||||||
glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
|
glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
|
||||||
int key, scancode;
|
int key, scancode;
|
||||||
@@ -199,6 +208,18 @@ glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
|
|||||||
return Py_BuildValue("s", glfwGetKeyName(key, scancode));
|
return Py_BuildValue("s", glfwGetKeyName(key, scancode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
glfw_init_hint_string(PyObject UNUSED *self, PyObject *args) {
|
||||||
|
int hint_id;
|
||||||
|
char *hint;
|
||||||
|
if (!PyArg_ParseTuple(args, "is", &hint_id, &hint)) return NULL;
|
||||||
|
#ifdef glfwInitHintString
|
||||||
|
glfwInitHintString(hint_id, hint);
|
||||||
|
#endif
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -318,6 +339,48 @@ get_window_size(Window *self) {
|
|||||||
return Py_BuildValue("ii", w, h);
|
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
|
#ifdef glfwRequestWindowAttention
|
||||||
static PyObject*
|
static PyObject*
|
||||||
request_window_attention(Window *self) {
|
request_window_attention(Window *self) {
|
||||||
@@ -326,6 +389,14 @@ request_window_attention(Window *self) {
|
|||||||
}
|
}
|
||||||
#endif
|
#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 {{{
|
// Boilerplate {{{
|
||||||
#define MND(name, args) {#name, (PyCFunction)name, args, ""}
|
#define MND(name, args) {#name, (PyCFunction)name, args, ""}
|
||||||
@@ -337,8 +408,12 @@ static PyMethodDef methods[] = {
|
|||||||
MND(should_close, METH_NOARGS),
|
MND(should_close, METH_NOARGS),
|
||||||
MND(get_framebuffer_size, METH_NOARGS),
|
MND(get_framebuffer_size, METH_NOARGS),
|
||||||
MND(get_window_size, METH_NOARGS),
|
MND(get_window_size, METH_NOARGS),
|
||||||
|
MND(current_monitor_dpi, METH_NOARGS),
|
||||||
#ifdef glfwRequestWindowAttention
|
#ifdef glfwRequestWindowAttention
|
||||||
MND(request_window_attention, METH_NOARGS),
|
MND(request_window_attention, METH_NOARGS),
|
||||||
|
#endif
|
||||||
|
#ifdef __APPLE__
|
||||||
|
MND(cocoa_window_id, METH_NOARGS),
|
||||||
#endif
|
#endif
|
||||||
MND(set_should_close, METH_VARARGS),
|
MND(set_should_close, METH_VARARGS),
|
||||||
MND(set_input_mode, METH_VARARGS),
|
MND(set_input_mode, METH_VARARGS),
|
||||||
@@ -385,6 +460,10 @@ init_glfw(PyObject *m) {
|
|||||||
PyEval_InitThreads();
|
PyEval_InitThreads();
|
||||||
glfwSetErrorCallback(cb_error_callback);
|
glfwSetErrorCallback(cb_error_callback);
|
||||||
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false;
|
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false;
|
||||||
|
#ifdef GLFW_X11_WM_CLASS_NAME
|
||||||
|
ADDC(GLFW_X11_WM_CLASS_NAME)
|
||||||
|
ADDC(GLFW_X11_WM_CLASS_CLASS)
|
||||||
|
#endif
|
||||||
ADDC(GLFW_RELEASE);
|
ADDC(GLFW_RELEASE);
|
||||||
ADDC(GLFW_PRESS);
|
ADDC(GLFW_PRESS);
|
||||||
ADDC(GLFW_REPEAT);
|
ADDC(GLFW_REPEAT);
|
||||||
|
|||||||
14
kitty/glfw.h
14
kitty/glfw.h
@@ -17,6 +17,17 @@ PyObject* glfw_wait_events(PyObject UNUSED *self, PyObject*);
|
|||||||
PyObject* glfw_post_empty_event(PyObject UNUSED *self);
|
PyObject* glfw_post_empty_event(PyObject UNUSED *self);
|
||||||
PyObject* glfw_get_physical_dpi(PyObject UNUSED *self);
|
PyObject* glfw_get_physical_dpi(PyObject UNUSED *self);
|
||||||
PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
|
PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
|
||||||
|
PyObject* glfw_init_hint_string(PyObject UNUSED *self, PyObject *args);
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
PyObject* cocoa_hide_titlebar(PyObject UNUSED *self, PyObject *window_id);
|
||||||
|
PyObject* cocoa_get_lang(PyObject UNUSED *self);
|
||||||
|
#define COCOA_HIDE_TITLEBAR {"cocoa_hide_titlebar", (PyCFunction)cocoa_hide_titlebar, METH_O, ""},
|
||||||
|
#define COCOA_GET_LANG {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||||
|
#else
|
||||||
|
#define COCOA_HIDE_TITLEBAR
|
||||||
|
#define COCOA_GET_LANG
|
||||||
|
#endif
|
||||||
|
|
||||||
#define GLFW_FUNC_WRAPPERS \
|
#define GLFW_FUNC_WRAPPERS \
|
||||||
{"glfw_set_error_callback", (PyCFunction)glfw_set_error_callback, METH_O, ""}, \
|
{"glfw_set_error_callback", (PyCFunction)glfw_set_error_callback, METH_O, ""}, \
|
||||||
@@ -28,4 +39,7 @@ PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
|
|||||||
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, \
|
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, \
|
||||||
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, \
|
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, \
|
||||||
{"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, \
|
{"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, \
|
||||||
|
{"glfw_init_hint_string", (PyCFunction)glfw_init_hint_string, METH_VARARGS, ""}, \
|
||||||
|
COCOA_HIDE_TITLEBAR \
|
||||||
|
COCOA_GET_LANG
|
||||||
|
|
||||||
|
|||||||
101
kitty/keys.py
101
kitty/keys.py
@@ -7,7 +7,28 @@ from .terminfo import key_as_bytes
|
|||||||
from .utils import base64_encode
|
from .utils import base64_encode
|
||||||
from .key_encoding import KEY_MAP
|
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_UP: 'kcuu1',
|
||||||
defines.GLFW_KEY_DOWN: 'kcud1',
|
defines.GLFW_KEY_DOWN: 'kcud1',
|
||||||
defines.GLFW_KEY_LEFT: 'kcub1',
|
defines.GLFW_KEY_LEFT: 'kcub1',
|
||||||
@@ -18,12 +39,30 @@ smkx_key_map = {
|
|||||||
defines.GLFW_KEY_DELETE: 'kdch1',
|
defines.GLFW_KEY_DELETE: 'kdch1',
|
||||||
defines.GLFW_KEY_PAGE_UP: 'kpp',
|
defines.GLFW_KEY_PAGE_UP: 'kpp',
|
||||||
defines.GLFW_KEY_PAGE_DOWN: 'knp',
|
defines.GLFW_KEY_PAGE_DOWN: 'knp',
|
||||||
}
|
}.items():
|
||||||
smkx_key_map = {k: key_as_bytes(v) for k, v in smkx_key_map.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):
|
for f in range(1, 13):
|
||||||
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
|
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
|
||||||
smkx_key_map[kf] = key_as_bytes('kf{}'.format(f))
|
kn = 'kf{}'.format(f)
|
||||||
del f, kf
|
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_ESCAPE] = b'\033'
|
||||||
smkx_key_map[defines.GLFW_KEY_ENTER] = b'\r'
|
smkx_key_map[defines.GLFW_KEY_ENTER] = b'\r'
|
||||||
@@ -39,27 +78,16 @@ SHIFTED_KEYS = {
|
|||||||
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
|
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
|
||||||
}
|
}
|
||||||
|
|
||||||
control_codes = {
|
control_codes.update({
|
||||||
k: (1 + i, )
|
k: (1 + i, )
|
||||||
for i, k in
|
for i, k in
|
||||||
enumerate(range(defines.GLFW_KEY_A, defines.GLFW_KEY_RIGHT_BRACKET + 1))
|
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 = smkx_key_map.copy()
|
||||||
rmkx_key_map.update({
|
rmkx_key_map.update({
|
||||||
defines.GLFW_KEY_UP: b'\033[A',
|
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_HOME: b'\033[H',
|
||||||
defines.GLFW_KEY_END: b'\033[F',
|
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}
|
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]
|
return cursor_key_mode_map[screen.cursor_key_mode]
|
||||||
|
|
||||||
|
|
||||||
|
def keyboard_mode_name(screen):
|
||||||
|
if screen.extended_keyboard:
|
||||||
|
return 'kitty'
|
||||||
|
return 'application' if screen.cursor_key_mode else 'normal'
|
||||||
|
|
||||||
|
|
||||||
valid_localized_key_names = {
|
valid_localized_key_names = {
|
||||||
k: getattr(defines, 'GLFW_KEY_' + k)
|
k: getattr(defines, 'GLFW_KEY_' + k)
|
||||||
for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
@@ -131,7 +162,7 @@ def extended_key_event(key, scancode, mods, action):
|
|||||||
).encode('ascii')
|
).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
|
screen = window.screen
|
||||||
key = get_localized_key(key, scancode)
|
key = get_localized_key(key, scancode)
|
||||||
if screen.extended_keyboard:
|
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:
|
if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
|
||||||
# Map Ctrl-key to ascii control code
|
# Map Ctrl-key to ascii control code
|
||||||
data.extend(control_codes[key])
|
data.extend(control_codes[key])
|
||||||
elif mods == defines.GLFW_MOD_ALT and key in alt_codes:
|
elif mods in alt_mods and key in alt_codes:
|
||||||
# Map Alt+key to Esc-key
|
# Printable keys handled by interpret_text_event()
|
||||||
data.extend(alt_codes[key])
|
data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key])
|
||||||
else:
|
else:
|
||||||
key_map = get_key_map(screen)
|
key_map = get_key_map(screen)
|
||||||
x = key_map.get(key)
|
x = key_map.get(key)
|
||||||
@@ -156,8 +187,12 @@ def interpret_key_event(key, scancode, mods, window, action):
|
|||||||
return bytes(data)
|
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 > 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
|
return b'' # Handled by interpret_key_event above
|
||||||
data = chr(codepoint).encode('utf-8')
|
data = chr(codepoint).encode('utf-8')
|
||||||
return data
|
return data
|
||||||
@@ -166,3 +201,11 @@ def interpret_text_event(codepoint, mods):
|
|||||||
def get_shortcut(keymap, mods, key, scancode):
|
def get_shortcut(keymap, mods, key, scancode):
|
||||||
key = get_localized_key(key, scancode)
|
key = get_localized_key(key, scancode)
|
||||||
return keymap.get((mods & 0b1111, key))
|
return keymap.get((mods & 0b1111, key))
|
||||||
|
|
||||||
|
|
||||||
|
def get_sent_data(send_text_map, key, scancode, mods, window, action):
|
||||||
|
if action in (defines.GLFW_PRESS, defines.GLFW_REPEAT):
|
||||||
|
key = get_localized_key(key, scancode)
|
||||||
|
m = keyboard_mode_name(window.screen)
|
||||||
|
keymap = send_text_map[m]
|
||||||
|
return keymap.get((mods & 0b1111, key))
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ cursor_shape block
|
|||||||
cursor_blink_interval 0.5
|
cursor_blink_interval 0.5
|
||||||
|
|
||||||
# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to
|
# 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
|
cursor_stop_blinking_after 15.0
|
||||||
|
|
||||||
# Number of lines of history to keep in memory for scrolling back
|
# 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
|
# When viewing scrollback in a new window, put it in a new tab as well
|
||||||
scrollback_in_new_tab no
|
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
|
wheel_scroll_multiplier 5.0
|
||||||
|
|
||||||
# The interval between successive clicks to detect double/triple clicks (in seconds)
|
# The interval between successive clicks to detect double/triple clicks (in seconds)
|
||||||
@@ -71,7 +72,7 @@ click_interval 0.5
|
|||||||
select_by_word_characters :@-./_~?&=%+#
|
select_by_word_characters :@-./_~?&=%+#
|
||||||
|
|
||||||
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to
|
# 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
|
mouse_hide_wait 3.0
|
||||||
|
|
||||||
# The enabled window layouts. A comma separated list of layout names. The special value * means
|
# 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.
|
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||||
window_border_width 1
|
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
|
# The color for the border of the active window
|
||||||
active_border_color #00ff00
|
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+minus decrease_font_size
|
||||||
map ctrl+shift+backspace restore_font_size
|
map ctrl+shift+backspace restore_font_size
|
||||||
|
|
||||||
|
# Sending arbitrary text on shortcut key presses
|
||||||
|
# You can tell kitty to send arbitrary (UTF-8) encoded text to
|
||||||
|
# the client program when pressing specified shortcut keys. For example:
|
||||||
|
# send_text all ctrl+alt+a Special text
|
||||||
|
# This will send "Special text" when you press the Ctrl+Alt+a key combination.
|
||||||
|
# The text to be sent is a python string literal so you can use escapes like
|
||||||
|
# \x1b to send control codes or \u21fb to send unicode characters (or you can
|
||||||
|
# just input the unicode characters directly as UTF-8 text). The first argument
|
||||||
|
# to send_text is the keyboard modes in which to activate the shortcut. The possible
|
||||||
|
# values are normal or application or kitty or a comma separated combination of them.
|
||||||
|
# The special keyword all means all modes. The modes normal and application refer to
|
||||||
|
# the DECCKM cursor key mode for terminals, and kitty refers to the special kitty
|
||||||
|
# extended keyboard protocol. Another example, that outputs a word and then moves the cursor
|
||||||
|
# to the start of the line (same as pressing the Home key):
|
||||||
|
# send_text normal ctrl+alt+a Word\x1b[H
|
||||||
|
# send_text application ctrl+alt+a Word\x1bOH
|
||||||
|
|
||||||
# Symbol mapping (special font for specified unicode code points). Map the
|
# Symbol mapping (special font for specified unicode code points). Map the
|
||||||
# specified unicode codepoints to a particular font. Useful if you need special
|
# specified unicode codepoints to a particular font. Useful if you need special
|
||||||
# rendering for some symbols, such as for Powerline. Avoids the need for
|
# rendering for some symbols, such as for Powerline. Avoids the need for
|
||||||
@@ -237,3 +261,9 @@ map ctrl+shift+backspace restore_font_size
|
|||||||
# For example:
|
# For example:
|
||||||
#
|
#
|
||||||
# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
|
# 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
|
||||||
|
|||||||
@@ -6,25 +6,29 @@ from collections import namedtuple
|
|||||||
from itertools import islice
|
from itertools import islice
|
||||||
|
|
||||||
from .constants import WindowGeometry, viewport_size, cell_size, get_boss
|
from .constants import WindowGeometry, viewport_size, cell_size, get_boss
|
||||||
|
from .utils import pt_to_px
|
||||||
|
|
||||||
|
|
||||||
def available_height():
|
def available_height():
|
||||||
return viewport_size.height - get_boss().current_tab_bar_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
|
number_of_cells = length // cell_length
|
||||||
|
border_length += padding_length
|
||||||
space_needed_for_border = number_of_windows * 2 * border_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
|
extra = length - number_of_cells * cell_length
|
||||||
while extra < space_needed_for_border:
|
while extra < space_needed:
|
||||||
number_of_cells -= 1
|
number_of_cells -= 1
|
||||||
extra = length - number_of_cells * cell_length
|
extra = length - number_of_cells * cell_length
|
||||||
cells_per_window = number_of_cells // number_of_windows
|
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 = 0 if left_align else (extra // 2)
|
||||||
pos += border_length
|
pos += border_length + margin_length
|
||||||
inner_length = cells_per_window * cell_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)
|
extra = number_of_cells - (cells_per_window * number_of_windows)
|
||||||
while number_of_windows > 0:
|
while number_of_windows > 0:
|
||||||
number_of_windows -= 1
|
number_of_windows -= 1
|
||||||
@@ -43,6 +47,8 @@ class Layout:
|
|||||||
def __init__(self, opts, border_width, windows):
|
def __init__(self, opts, border_width, windows):
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.border_width = border_width
|
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
|
# 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
|
# this layout, i.e. spaces that are not covered by any window
|
||||||
self.blank_rects = ()
|
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)
|
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():
|
def layout_single_window(margin_length, padding_length):
|
||||||
xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width))
|
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))
|
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)
|
return window_geometry(xstart, xnum, ystart, ynum)
|
||||||
|
|
||||||
|
|
||||||
@@ -120,7 +126,7 @@ class Stack(Layout):
|
|||||||
|
|
||||||
def __call__(self, windows, active_window_idx):
|
def __call__(self, windows, active_window_idx):
|
||||||
self.blank_rects = []
|
self.blank_rects = []
|
||||||
wg = layout_single_window()
|
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||||
for i, w in enumerate(windows):
|
for i, w in enumerate(windows):
|
||||||
w.is_visible_in_layout = i == active_window_idx
|
w.is_visible_in_layout = i == active_window_idx
|
||||||
w.set_geometry(wg)
|
w.set_geometry(wg)
|
||||||
@@ -135,17 +141,23 @@ class Tall(Layout):
|
|||||||
def __call__(self, windows, active_window_idx):
|
def __call__(self, windows, active_window_idx):
|
||||||
self.blank_rects = br = []
|
self.blank_rects = br = []
|
||||||
if len(windows) == 1:
|
if len(windows) == 1:
|
||||||
wg = layout_single_window()
|
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||||
windows[0].set_geometry(wg)
|
windows[0].set_geometry(wg)
|
||||||
self.blank_rects = blank_rects_for_window(windows[0])
|
self.blank_rects = blank_rects_for_window(windows[0])
|
||||||
return
|
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)
|
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))
|
windows[0].set_geometry(window_geometry(xstart, xnum, ystart, ynum))
|
||||||
vh = available_height()
|
vh = available_height()
|
||||||
xstart, xnum = next(xlayout)
|
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):
|
for w, (ystart, ynum) in zip(islice(windows, 1, None), ylayout):
|
||||||
w.set_geometry(window_geometry(xstart, xnum, ystart, ynum))
|
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)
|
left_blank_rect(windows[0], br, vh), top_blank_rect(windows[0], br, vh), right_blank_rect(windows[-1], br, vh)
|
||||||
|
|||||||
@@ -25,11 +25,15 @@ from .fast_data_types import (
|
|||||||
GLFW_STENCIL_BITS, Window, change_wcwidth,
|
GLFW_STENCIL_BITS, Window, change_wcwidth,
|
||||||
enable_automatic_opengl_error_checking, glClear, glClearColor, glewInit,
|
enable_automatic_opengl_error_checking, glClear, glClearColor, glewInit,
|
||||||
glfw_init, glfw_set_error_callback, glfw_swap_interval, glfw_terminate,
|
glfw_init, glfw_set_error_callback, glfw_swap_interval, glfw_terminate,
|
||||||
glfw_wait_events, glfw_window_hint
|
glfw_wait_events, glfw_window_hint, glfw_init_hint_string
|
||||||
)
|
)
|
||||||
|
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
|
||||||
from .layout import all_layouts
|
from .layout import all_layouts
|
||||||
from .shaders import GL_VERSION
|
from .shaders import GL_VERSION
|
||||||
from .utils import safe_print
|
from .utils import safe_print, detach
|
||||||
|
|
||||||
|
|
||||||
defconf = os.path.join(config_dir, 'kitty.conf')
|
defconf = os.path.join(config_dir, 'kitty.conf')
|
||||||
@@ -95,6 +99,13 @@ def option_parser():
|
|||||||
default=False,
|
default=False,
|
||||||
help=_('Output commands received from child process to stdout')
|
help=_('Output commands received from child process to stdout')
|
||||||
)
|
)
|
||||||
|
if not isosx:
|
||||||
|
a(
|
||||||
|
'--detach',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=_('Detach from the controlling terminal, if any')
|
||||||
|
)
|
||||||
a(
|
a(
|
||||||
'--replay-commands',
|
'--replay-commands',
|
||||||
default=None,
|
default=None,
|
||||||
@@ -188,10 +199,20 @@ def run_app(opts, args):
|
|||||||
else:
|
else:
|
||||||
viewport_size.width = opts.initial_window_width
|
viewport_size.width = opts.initial_window_width
|
||||||
viewport_size.height = opts.initial_window_height
|
viewport_size.height = opts.initial_window_height
|
||||||
window = Window(viewport_size.width, viewport_size.height, args.cls)
|
try:
|
||||||
|
window = Window(viewport_size.width, viewport_size.height, args.cls)
|
||||||
|
except ValueError:
|
||||||
|
safe_print('Failed to create GLFW window with initial size:', viewport_size)
|
||||||
|
viewport_size.width = 640
|
||||||
|
viewport_size.height = 400
|
||||||
|
window = Window(viewport_size.width, viewport_size.height, args.cls)
|
||||||
window.set_title(appname)
|
window.set_title(appname)
|
||||||
window.make_context_current()
|
window.make_context_current()
|
||||||
if not isosx:
|
if isosx:
|
||||||
|
if opts.macos_hide_titlebar:
|
||||||
|
from .fast_data_types import cocoa_hide_titlebar
|
||||||
|
cocoa_hide_titlebar(window.cocoa_window_id())
|
||||||
|
else:
|
||||||
with open(logo_data_file, 'rb') as f:
|
with open(logo_data_file, 'rb') as f:
|
||||||
window.set_icon(f.read(), 256, 256)
|
window.set_icon(f.read(), 256, 256)
|
||||||
viewport_size.width, viewport_size.height = window.get_framebuffer_size()
|
viewport_size.width, viewport_size.height = window.get_framebuffer_size()
|
||||||
@@ -224,12 +245,38 @@ def on_glfw_error(code, msg):
|
|||||||
safe_print('[glfw error] ', msg, file=sys.stderr)
|
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():
|
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',
|
if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES',
|
||||||
None) == '1' and getattr(sys, 'frozen', True):
|
None) == '1' and getattr(sys, 'frozen', True):
|
||||||
os.chdir(os.path.expanduser('~'))
|
os.chdir(os.path.expanduser('~'))
|
||||||
|
if not os.path.isdir(os.getcwd()):
|
||||||
|
os.chdir(os.path.expanduser('~'))
|
||||||
args = option_parser().parse_args()
|
args = option_parser().parse_args()
|
||||||
|
if getattr(args, 'detach', False):
|
||||||
|
detach()
|
||||||
if args.cmd:
|
if args.cmd:
|
||||||
exec(args.cmd)
|
exec(args.cmd)
|
||||||
return
|
return
|
||||||
@@ -243,6 +290,8 @@ def main():
|
|||||||
change_wcwidth(not opts.use_system_wcwidth)
|
change_wcwidth(not opts.use_system_wcwidth)
|
||||||
glfw_set_error_callback(on_glfw_error)
|
glfw_set_error_callback(on_glfw_error)
|
||||||
enable_automatic_opengl_error_checking(args.debug_gl)
|
enable_automatic_opengl_error_checking(args.debug_gl)
|
||||||
|
if GLFW_X11_WM_CLASS_CLASS is not None:
|
||||||
|
glfw_init_hint_string(GLFW_X11_WM_CLASS_CLASS, opts.cls)
|
||||||
if not glfw_init():
|
if not glfw_init():
|
||||||
raise SystemExit('GLFW initialization failed')
|
raise SystemExit('GLFW initialization failed')
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -58,5 +58,5 @@ def encode_mouse_event(tracking_mode, tracking_protocol, button, action, mods, x
|
|||||||
ans = bytes(ans)
|
ans = bytes(ans)
|
||||||
else:
|
else:
|
||||||
if x <= 223 and y <= 223:
|
if x <= 223 and y <= 223:
|
||||||
ans = bytearray([0o33, ord('['), cb + 32, x + 32, y + 32])
|
ans = bytearray([0o33, ord('['), ord('M'), cb + 32, x + 32, y + 32])
|
||||||
return ans
|
return ans
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
|
|||||||
#define REPORT_OSC(name, string) \
|
#define REPORT_OSC(name, string) \
|
||||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", #name, string)); PyErr_Clear();
|
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
|
#else
|
||||||
|
|
||||||
#define DUMP_UNUSED UNUSED
|
#define DUMP_UNUSED UNUSED
|
||||||
@@ -107,6 +110,7 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
|
|||||||
#define REPORT_PARAMS(...)
|
#define REPORT_PARAMS(...)
|
||||||
#define FLUSH_DRAW
|
#define FLUSH_DRAW
|
||||||
#define REPORT_OSC(name, string)
|
#define REPORT_OSC(name, string)
|
||||||
|
#define REPORT_OSC2(name, code, string)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -266,7 +270,7 @@ handle_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_cal
|
|||||||
static inline void
|
static inline void
|
||||||
dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||||
#define DISPATCH_OSC(name) REPORT_OSC(name, string); name(screen, string);
|
#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;
|
const unsigned int limit = screen->parser_buf_pos;
|
||||||
unsigned int code=0, i;
|
unsigned int code=0, i;
|
||||||
for (i = 0; i < MIN(limit, 5); 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
|
static inline void
|
||||||
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); }
|
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); }
|
||||||
static inline void
|
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); }
|
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
|
static inline void
|
||||||
save_cursor(Screen *s, unsigned int UNUSED param, bool private) {
|
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");
|
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:
|
case DECSCUSR:
|
||||||
CALL_CSI_HANDLER1M(screen_set_cursor, 1);
|
CALL_CSI_HANDLER1M(screen_set_cursor, 1);
|
||||||
case SU:
|
case SU:
|
||||||
CALL_CSI_HANDLER1(screen_indexn, 1);
|
CALL_CSI_HANDLER1(screen_scroll, 1);
|
||||||
case SD:
|
case SD:
|
||||||
CALL_CSI_HANDLER1(screen_reverse_indexn, 1);
|
CALL_CSI_HANDLER1(screen_reverse_scroll, 1);
|
||||||
case DECSTR:
|
case DECSTR:
|
||||||
if (end_modifier == '$') {
|
if (end_modifier == '$') {
|
||||||
// DECRQM
|
// DECRQM
|
||||||
@@ -543,6 +543,7 @@ accumulate_osc(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback)
|
|||||||
screen->parser_buf_pos--;
|
screen->parser_buf_pos--;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
/* fallthrough */
|
||||||
default:
|
default:
|
||||||
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
||||||
REPORT_ERROR("OSC sequence too long, truncating.");
|
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--;
|
screen->parser_buf_pos--;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
/* fallthrough */
|
||||||
default:
|
default:
|
||||||
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
||||||
REPORT_ERROR("OTH sequence too long, truncating.");
|
REPORT_ERROR("OTH sequence too long, truncating.");
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
#include <structmember.h>
|
#include <structmember.h>
|
||||||
|
#include <limits.h>
|
||||||
#include "unicode-data.h"
|
#include "unicode-data.h"
|
||||||
#include "tracker.h"
|
#include "tracker.h"
|
||||||
#include "modes.h"
|
#include "modes.h"
|
||||||
@@ -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
|
void
|
||||||
screen_index(Screen *self) {
|
screen_index(Screen *self) {
|
||||||
// Move cursor down one line, scrolling screen if needed
|
// Move cursor down one line, scrolling screen if needed
|
||||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
||||||
if (self->cursor->y == bottom) {
|
if (self->cursor->y == bottom) {
|
||||||
linebuf_index(self->linebuf, top, bottom);
|
INDEX_UP;
|
||||||
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);
|
|
||||||
} else screen_cursor_down(self, 1);
|
} 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
|
void
|
||||||
screen_reverse_index(Screen *self) {
|
screen_reverse_index(Screen *self) {
|
||||||
// Move cursor up one line, scrolling screen if needed
|
// Move cursor up one line, scrolling screen if needed
|
||||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
||||||
if (self->cursor->y == top) {
|
if (self->cursor->y == top) {
|
||||||
linebuf_reverse_index(self->linebuf, top, bottom);
|
INDEX_DOWN;
|
||||||
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);
|
|
||||||
} else screen_cursor_up(self, 1, false, -1);
|
} 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
|
void
|
||||||
screen_carriage_return(Screen *self) {
|
screen_carriage_return(Screen *self) {
|
||||||
@@ -710,13 +739,7 @@ screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_cursor_to_line(Screen *self, unsigned int line) {
|
screen_cursor_to_line(Screen *self, unsigned int line) {
|
||||||
unsigned int y = MAX(line, 1) - 1;
|
screen_cursor_position(self, line, self->cursor->x + 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
@@ -1310,6 +1333,14 @@ static PyGetSetDef getsetters[] = {
|
|||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if UINT_MAX == UINT32_MAX
|
||||||
|
#define T_COL T_UINT
|
||||||
|
#elif ULONG_MAX == UINT32_MAX
|
||||||
|
#define T_COL T_ULONG
|
||||||
|
#else
|
||||||
|
#error Neither int nor long is 4-bytes in size
|
||||||
|
#endif
|
||||||
|
|
||||||
static PyMemberDef members[] = {
|
static PyMemberDef members[] = {
|
||||||
{"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"},
|
{"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"},
|
||||||
{"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"},
|
{"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"},
|
||||||
@@ -1319,11 +1350,11 @@ static PyMemberDef members[] = {
|
|||||||
{"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"},
|
{"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"},
|
||||||
{"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"},
|
{"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"},
|
||||||
{"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"},
|
{"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"},
|
||||||
{"default_fg", T_ULONG, offsetof(Screen, default_fg), 0, "default_fg"},
|
{"default_fg", T_COL, offsetof(Screen, default_fg), 0, "default_fg"},
|
||||||
{"default_bg", T_ULONG, offsetof(Screen, default_bg), 0, "default_bg"},
|
{"default_bg", T_COL, offsetof(Screen, default_bg), 0, "default_bg"},
|
||||||
{"highlight_fg", T_ULONG, offsetof(Screen, highlight_fg), 0, "highlight_fg"},
|
{"highlight_fg", T_COL, offsetof(Screen, highlight_fg), 0, "highlight_fg"},
|
||||||
{"highlight_bg", T_ULONG, offsetof(Screen, highlight_bg), 0, "highlight_bg"},
|
{"highlight_bg", T_COL, offsetof(Screen, highlight_bg), 0, "highlight_bg"},
|
||||||
{"cursor_color", T_ULONG, offsetof(Screen, cursor_color), 0, "cursor_color"},
|
{"cursor_color", T_COL, offsetof(Screen, cursor_color), 0, "cursor_color"},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,12 @@ class Tab:
|
|||||||
self.borders = Borders(opts)
|
self.borders = Borders(opts)
|
||||||
self.windows = deque()
|
self.windows = deque()
|
||||||
self.active_window_idx = 0
|
self.active_window_idx = 0
|
||||||
|
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
|
||||||
|
setattr(self, which + '_window', partial(self.nth_window, num=i))
|
||||||
if session_tab is None:
|
if session_tab is None:
|
||||||
self.cwd = args.directory
|
self.cwd = args.directory
|
||||||
l = self.enabled_layouts[0]
|
l = self.enabled_layouts[0]
|
||||||
|
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
|
||||||
if special_window is None:
|
if special_window is None:
|
||||||
queue_action(self.new_window)
|
queue_action(self.new_window)
|
||||||
else:
|
else:
|
||||||
@@ -42,10 +45,8 @@ class Tab:
|
|||||||
else:
|
else:
|
||||||
self.cwd = session_tab.cwd or args.directory
|
self.cwd = session_tab.cwd or args.directory
|
||||||
l = session_tab.layout
|
l = session_tab.layout
|
||||||
|
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
|
||||||
queue_action(self.startup, session_tab)
|
queue_action(self.startup, session_tab)
|
||||||
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
|
|
||||||
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
|
|
||||||
setattr(self, which + '_window', partial(self.nth_window, num=i))
|
|
||||||
|
|
||||||
def startup(self, session_tab):
|
def startup(self, session_tab):
|
||||||
for cmd in session_tab.windows:
|
for cmd in session_tab.windows:
|
||||||
@@ -328,9 +329,10 @@ class TabManager:
|
|||||||
s.cursor.bold = s.cursor.italic = False
|
s.cursor.bold = s.cursor.italic = False
|
||||||
s.cursor.fg = s.cursor.bg = 0
|
s.cursor.fg = s.cursor.bg = 0
|
||||||
s.draw('┇')
|
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('…')
|
s.draw('…')
|
||||||
break
|
break
|
||||||
|
s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab
|
||||||
s.update_cell_data(
|
s.update_cell_data(
|
||||||
sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True)
|
sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True)
|
||||||
sprites.render_dirty_cells()
|
sprites.render_dirty_cells()
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from functools import lru_cache
|
|||||||
from time import monotonic
|
from time import monotonic
|
||||||
|
|
||||||
from .constants import isosx
|
from .constants import isosx
|
||||||
from .fast_data_types import glfw_get_physical_dpi, wcwidth as wcwidth_impl
|
from .fast_data_types import glfw_get_physical_dpi, wcwidth as wcwidth_impl, redirect_std_streams
|
||||||
from .rgb import Color, to_color
|
from .rgb import Color, to_color
|
||||||
|
|
||||||
|
|
||||||
@@ -37,6 +37,13 @@ def wcwidth(c: str) -> int:
|
|||||||
return wcwidth_impl(ord(c[0]))
|
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
|
@contextmanager
|
||||||
def timeit(name, do_timing=False):
|
def timeit(name, do_timing=False):
|
||||||
if do_timing:
|
if do_timing:
|
||||||
@@ -50,17 +57,71 @@ def sanitize_title(x):
|
|||||||
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19]', '', 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():
|
def get_logical_dpi():
|
||||||
if not hasattr(get_logical_dpi, 'ans'):
|
if not hasattr(get_logical_dpi, 'ans'):
|
||||||
if isosx:
|
if isosx:
|
||||||
# TODO: Investigate if this needs a different implementation on OS X
|
# TODO: Investigate if this needs a different implementation on OS X
|
||||||
get_logical_dpi.ans = glfw_get_physical_dpi()
|
get_logical_dpi.ans = glfw_get_physical_dpi()
|
||||||
else:
|
else:
|
||||||
raw = subprocess.check_output(['xdpyinfo']).decode('utf-8')
|
# See https://github.com/glfw/glfw/issues/1019 for why we cant use
|
||||||
m = re.search(
|
# glfw_get_physical_dpi()
|
||||||
r'^\s*resolution:\s*(\d+)+x(\d+)', raw, flags=re.MULTILINE
|
dpi = x11_dpi()
|
||||||
)
|
if dpi is None:
|
||||||
get_logical_dpi.ans = int(m.group(1)), int(m.group(2))
|
get_logical_dpi.ans = glfw_get_physical_dpi()
|
||||||
|
else:
|
||||||
|
get_logical_dpi.ans = dpi, dpi
|
||||||
return get_logical_dpi.ans
|
return get_logical_dpi.ans
|
||||||
|
|
||||||
|
|
||||||
@@ -120,7 +181,7 @@ def get_primary_selection():
|
|||||||
return '' # There is no primary selection on OS X
|
return '' # There is no primary selection on OS X
|
||||||
# glfw has no way to get the primary selection
|
# glfw has no way to get the primary selection
|
||||||
# https://github.com/glfw/glfw/issues/894
|
# https://github.com/glfw/glfw/issues/894
|
||||||
return subprocess.check_output(['xsel', '-p']).decode('utf-8')
|
return subprocess.check_output(['xsel', '-p'], stderr=open(os.devnull, 'wb'), stdin=open(os.devnull, 'rb')).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def base64_encode(
|
def base64_encode(
|
||||||
@@ -142,7 +203,7 @@ def set_primary_selection(text):
|
|||||||
return # There is no primary selection on OS X
|
return # There is no primary selection on OS X
|
||||||
if isinstance(text, str):
|
if isinstance(text, str):
|
||||||
text = text.encode('utf-8')
|
text = text.encode('utf-8')
|
||||||
p = subprocess.Popen(['xsel', '-i', '-p'], stdin=subprocess.PIPE)
|
p = subprocess.Popen(['xsel', '-i', '-p'], stdin=subprocess.PIPE, stdout=open(os.devnull, 'wb'), stderr=subprocess.STDOUT)
|
||||||
p.stdin.write(text), p.stdin.close()
|
p.stdin.write(text), p.stdin.close()
|
||||||
p.wait()
|
p.wait()
|
||||||
|
|
||||||
@@ -154,3 +215,14 @@ def open_url(url, program='default'):
|
|||||||
cmd = shlex.split(program)
|
cmd = shlex.split(program)
|
||||||
cmd.append(url)
|
cmd.append(url)
|
||||||
subprocess.Popen(cmd).wait()
|
subprocess.Popen(cmd).wait()
|
||||||
|
|
||||||
|
|
||||||
|
def detach(fork=True, setsid=True, redirect=True):
|
||||||
|
if fork:
|
||||||
|
# Detach from the controlling process.
|
||||||
|
if os.fork() != 0:
|
||||||
|
raise SystemExit(0)
|
||||||
|
if setsid:
|
||||||
|
os.setsid()
|
||||||
|
if redirect:
|
||||||
|
redirect_std_streams(os.devnull)
|
||||||
|
|||||||
@@ -30,11 +30,13 @@ DYNAMIC_COLOR_CODES = {
|
|||||||
19: DynamicColor.highlight_fg,
|
19: DynamicColor.highlight_fg,
|
||||||
}
|
}
|
||||||
DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()})
|
DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()})
|
||||||
|
dump_bytes_opened = False
|
||||||
|
|
||||||
|
|
||||||
class Window:
|
class Window:
|
||||||
|
|
||||||
def __init__(self, tab, child, opts, args):
|
def __init__(self, tab, child, opts, args):
|
||||||
|
global dump_bytes_opened
|
||||||
self.tabref = weakref.ref(tab)
|
self.tabref = weakref.ref(tab)
|
||||||
self.override_title = None
|
self.override_title = None
|
||||||
self.last_mouse_cursor_pos = 0, 0
|
self.last_mouse_cursor_pos = 0, 0
|
||||||
@@ -50,7 +52,9 @@ class Window:
|
|||||||
self.screen = Screen(self, 24, 80, opts.scrollback_lines)
|
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
|
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:
|
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.draw_dump_buf = []
|
||||||
self.write_buf = memoryview(b'')
|
self.write_buf = memoryview(b'')
|
||||||
self.char_grid = CharGrid(self.screen, opts)
|
self.char_grid = CharGrid(self.screen, opts)
|
||||||
|
|||||||
81
kitty_tests/keys.py
Normal file
81
kitty_tests/keys.py
Normal 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)
|
||||||
@@ -173,7 +173,7 @@ class TestParser(BaseTest):
|
|||||||
c.clear()
|
c.clear()
|
||||||
pb('\033]2;;;;\x07', ('set_title', ';;;'))
|
pb('\033]2;;;;\x07', ('set_title', ';;;'))
|
||||||
self.ae(c.titlebuf, ';;;')
|
self.ae(c.titlebuf, ';;;')
|
||||||
pb('\033]110\x07', ('set_dynamic_color', ''))
|
pb('\033]110\x07', ('set_dynamic_color', 110, ''))
|
||||||
self.ae(c.colorbuf, '')
|
self.ae(c.colorbuf, '')
|
||||||
|
|
||||||
def test_dcs_codes(self):
|
def test_dcs_codes(self):
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ There are various problems with the current state of keyboard handling. They
|
|||||||
include:
|
include:
|
||||||
|
|
||||||
* No way to use modifiers other than `Ctrl` and `Alt`
|
* 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 way to handle different types of keyboard events, such as press, release or repeat
|
||||||
* No reliable way to distinguish single `Esc` keypresses from the
|
* No reliable way to distinguish single `Esc` keypresses from the
|
||||||
start of a escape sequence. Currently, client programs use
|
start of a escape sequence. Currently, client programs use
|
||||||
|
|||||||
15
setup.py
15
setup.py
@@ -115,9 +115,9 @@ def init_env(debug=False, sanitize=False, native_optimizations=True):
|
|||||||
cflags = os.environ.get(
|
cflags = os.environ.get(
|
||||||
'OVERRIDE_CFLAGS', (
|
'OVERRIDE_CFLAGS', (
|
||||||
'-Wextra -Wno-missing-field-initializers -Wall -std=c99 -D_XOPEN_SOURCE=700'
|
'-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(
|
).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 ''
|
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', ''))
|
ldflags += shlex.split(os.environ.get('LDFLAGS', ''))
|
||||||
|
|
||||||
cflags.append('-pthread')
|
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]))
|
cflags.append('-DSECONDARY_VERSION={}'.format(version[1]))
|
||||||
if not is_travis and not isosx and subprocess.Popen(
|
if not is_travis and not isosx and subprocess.Popen(
|
||||||
[PKGCONFIG, 'glew', '--atleast-version=2']
|
[PKGCONFIG, 'glew', '--atleast-version=2']
|
||||||
@@ -264,7 +266,7 @@ def option_parser():
|
|||||||
def find_c_files():
|
def find_c_files():
|
||||||
ans, headers = [], []
|
ans, headers = [], []
|
||||||
d = os.path.join(base, 'kitty')
|
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):
|
for x in os.listdir(d):
|
||||||
ext = os.path.splitext(x)[1]
|
ext = os.path.splitext(x)[1]
|
||||||
if ext in ('.c', '.m') and os.path.basename(x) not in exclude:
|
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):
|
def safe_makedirs(path):
|
||||||
try:
|
os.makedirs(path, exist_ok=True)
|
||||||
os.makedirs(path)
|
|
||||||
except FileExistsError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def build_test_launcher(args):
|
def build_test_launcher(args):
|
||||||
|
|||||||
Reference in New Issue
Block a user