Compare commits

...

25 Commits

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

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

View File

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

View File

@@ -271,12 +271,13 @@ class Boss(Thread):
@callback
def on_text_input(self, window, codepoint, mods):
data = interpret_text_event(codepoint, mods)
if data:
w = self.active_window
w = self.active_window
if w is not None:
yield w
if w is not None:
yield w
w.write_to_child(data)
data = interpret_text_event(codepoint, mods, w)
if data:
w.write_to_child(data)
@callback
def on_key(self, window, key, scancode, action, mods):

View File

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

View File

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

View File

@@ -376,6 +376,8 @@ void screen_change_default_color(Screen *self, unsigned int which, uint32_t col)
void screen_alignment_display(Screen *self);
void screen_reverse_index(Screen *self);
void screen_index(Screen *self);
void screen_scroll(Screen *self, unsigned int count);
void screen_reverse_scroll(Screen *self, unsigned int count);
void screen_reset(Screen *self);
void screen_set_tab_stop(Screen *self);
void screen_tab(Screen *self);

View File

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

View File

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

View File

@@ -96,6 +96,9 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
#define REPORT_OSC(name, string) \
Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", #name, string)); PyErr_Clear();
#define REPORT_OSC2(name, code, string) \
Py_XDECREF(PyObject_CallFunction(dump_callback, "sIO", #name, code, string)); PyErr_Clear();
#else
#define DUMP_UNUSED UNUSED
@@ -107,6 +110,7 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
#define REPORT_PARAMS(...)
#define FLUSH_DRAW
#define REPORT_OSC(name, string)
#define REPORT_OSC2(name, code, string)
#endif
@@ -266,7 +270,7 @@ handle_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_cal
static inline void
dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
#define DISPATCH_OSC(name) REPORT_OSC(name, string); name(screen, string);
#define SET_COLOR(name) REPORT_OSC(name, string); name(screen, code, string);
#define SET_COLOR(name) REPORT_OSC2(name, code, string); name(screen, code, string);
const unsigned int limit = screen->parser_buf_pos;
unsigned int code=0, i;
for (i = 0; i < MIN(limit, 5); i++) {
@@ -331,11 +335,7 @@ screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, fa
static inline void
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); }
static inline void
screen_indexn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1, count); i++) screen_index(s); }
static inline void
screen_tabn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1, count); i++) screen_tab(s); }
static inline void
screen_reverse_indexn(Screen *s, unsigned int count) { for (index_type i=0; i < count; i++) screen_reverse_index(s); }
static inline void
save_cursor(Screen *s, unsigned int UNUSED param, bool private) {
if (private) fprintf(stderr, "%s %s", ERROR_PREFIX, "CSI s in private mode not supported");
@@ -484,9 +484,9 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
case DECSCUSR:
CALL_CSI_HANDLER1M(screen_set_cursor, 1);
case SU:
CALL_CSI_HANDLER1(screen_indexn, 1);
CALL_CSI_HANDLER1(screen_scroll, 1);
case SD:
CALL_CSI_HANDLER1(screen_reverse_indexn, 1);
CALL_CSI_HANDLER1(screen_reverse_scroll, 1);
case DECSTR:
if (end_modifier == '$') {
// DECRQM

View File

@@ -582,36 +582,64 @@ screen_cursor_to_column(Screen *self, unsigned int column) {
}
}
#define INDEX_UP \
linebuf_index(self->linebuf, top, bottom); \
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) { \
/* Only add to history when no page margins have been set */ \
linebuf_init_line(self->linebuf, bottom); \
historybuf_add_line(self->historybuf, self->linebuf->line); \
tracker_line_added_to_history(self->change_tracker); \
} \
linebuf_clear_line(self->linebuf, bottom); \
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker); \
else tracker_update_line_range(self->change_tracker, top, bottom);
void
screen_index(Screen *self) {
// Move cursor down one line, scrolling screen if needed
unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (self->cursor->y == bottom) {
linebuf_index(self->linebuf, top, bottom);
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) {
// Only add to history when no page margins have been set
linebuf_init_line(self->linebuf, bottom);
historybuf_add_line(self->historybuf, self->linebuf->line);
tracker_line_added_to_history(self->change_tracker);
}
linebuf_clear_line(self->linebuf, bottom);
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker);
else tracker_update_line_range(self->change_tracker, top, bottom);
INDEX_UP;
} else screen_cursor_down(self, 1);
}
void
screen_scroll(Screen *self, unsigned int count) {
// Scroll the screen up by count lines, not moving the cursor
count = MIN(self->lines, count);
unsigned int top = self->margin_top, bottom = self->margin_bottom;
while (count > 0) {
count--;
INDEX_UP;
}
}
#define INDEX_DOWN \
linebuf_reverse_index(self->linebuf, top, bottom); \
linebuf_clear_line(self->linebuf, top); \
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker); \
else tracker_update_line_range(self->change_tracker, top, bottom);
void
screen_reverse_index(Screen *self) {
// Move cursor up one line, scrolling screen if needed
unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (self->cursor->y == top) {
linebuf_reverse_index(self->linebuf, top, bottom);
linebuf_clear_line(self->linebuf, top);
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker);
else tracker_update_line_range(self->change_tracker, top, bottom);
INDEX_DOWN;
} else screen_cursor_up(self, 1, false, -1);
}
void
screen_reverse_scroll(Screen *self, unsigned int count) {
// Scroll the screen down by count lines, not moving the cursor
count = MIN(self->lines, count);
unsigned int top = self->margin_top, bottom = self->margin_bottom;
while (count > 0) {
count--;
INDEX_DOWN;
}
}
void
screen_carriage_return(Screen *self) {
@@ -710,13 +738,7 @@ screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
void
screen_cursor_to_line(Screen *self, unsigned int line) {
unsigned int y = MAX(line, 1) - 1;
y += self->margin_top;
if (y != self->cursor->y) {
self->cursor->y = y;
screen_ensure_bounds(self, false); // TODO: should we also restrict the cursor to the scrolling region?
tracker_cursor_changed(self->change_tracker);
}
screen_cursor_position(self, line, self->cursor->x + 1);
}
// }}}

View File

@@ -50,17 +50,71 @@ def sanitize_title(x):
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19]', '', x))
@lru_cache()
def load_libx11():
import ctypes
from ctypes.util import find_library
libx11 = ctypes.CDLL(find_library('X11'))
ans = []
def cdef(name, restype, *argtypes):
f = getattr(libx11, name)
if restype is not None:
f.restype = restype
if argtypes:
f.argtypes = argtypes
ans.append(f)
cdef('XOpenDisplay', ctypes.c_void_p, ctypes.c_char_p)
cdef('XCloseDisplay', ctypes.c_int, ctypes.c_void_p)
cdef('XResourceManagerString', ctypes.c_char_p, ctypes.c_void_p)
return ans
def parse_xrdb(raw):
q = 'Xft.dpi:\t'
for line in raw.decode('utf-8').splitlines():
if line.startswith(q):
return float(line[len(q):])
def x11_dpi_native():
XOpenDisplay, XCloseDisplay, XResourceManagerString = load_libx11()
display = XOpenDisplay(None)
if display is None:
raise RuntimeError('Could not connect to the X server')
try:
raw = XResourceManagerString(display)
return parse_xrdb(raw)
finally:
XCloseDisplay(display)
def x11_dpi():
try:
return x11_dpi_native()
except Exception:
pass
try:
raw = subprocess.check_output(['xrdb', '-query'])
return parse_xrdb(raw)
except Exception:
pass
def get_logical_dpi():
if not hasattr(get_logical_dpi, 'ans'):
if isosx:
# TODO: Investigate if this needs a different implementation on OS X
get_logical_dpi.ans = glfw_get_physical_dpi()
else:
raw = subprocess.check_output(['xdpyinfo']).decode('utf-8')
m = re.search(
r'^\s*resolution:\s*(\d+)+x(\d+)', raw, flags=re.MULTILINE
)
get_logical_dpi.ans = int(m.group(1)), int(m.group(2))
# See https://github.com/glfw/glfw/issues/1019 for why we cant use
# glfw_get_physical_dpi()
dpi = x11_dpi()
if dpi is None:
get_logical_dpi.ans = glfw_get_physical_dpi()
else:
get_logical_dpi.ans = dpi, dpi
return get_logical_dpi.ans

View File

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

81
kitty_tests/keys.py Normal file
View File

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

View File

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

View File

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

View File

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