Compare commits

...

16 Commits

Author SHA1 Message Date
Kovid Goyal
4e7b957cc7 version 0.4.2 2017-10-23 17:11:04 +05:30
Kovid Goyal
b1424cc4c7 ... 2017-10-23 17:09:52 +05:30
Kovid Goyal
be7d0e2016 forgot to remove debugging printf 2017-10-23 16:46:57 +05:30
Kovid Goyal
7e8d509fdd Fix #153 2017-10-23 16:45:17 +05:30
Kovid Goyal
8672a29503 Use a C based SIGCHLD handler
Running python code in signal handlers makes me nervous
2017-10-23 12:42:03 +05:30
Kovid Goyal
32a1886b31 version 0.4.1 2017-10-23 11:19:30 +05:30
Kovid Goyal
70bfe8589f Better fix for #152 2017-10-23 11:12:31 +05:30
Kovid Goyal
4f3d71d4ee Fix a regression that broke setting custom key mappings
Fixes #152
2017-10-23 11:04:02 +05:30
Kovid Goyal
0df0fd6a0b Handle EINTR in reap_zombies 2017-10-22 19:29:13 +05:30
Kovid Goyal
2b9198866a ... 2017-10-22 19:25:51 +05:30
Kovid Goyal
6350652009 Reap zombies in a signal handler
Avoids relying on jernel behavior, which may not be portable across all
unices
2017-10-22 19:13:13 +05:30
Kovid Goyal
d239db8492 Simply reaping of dead processes
Let the kernel do the reaping, since we do not care about the exit code
anyway. This should also fix #151.
2017-10-22 17:42:49 +05:30
Kovid Goyal
bdb6723564 Remove unused code 2017-10-22 17:13:38 +05:30
Kovid Goyal
d6bf5aa011 Remove unused code 2017-10-22 17:13:11 +05:30
Kovid Goyal
b5c4fbb42b ... 2017-10-22 17:11:58 +05:30
Kovid Goyal
1276bd6f5d Simply code to query xrdb for Xft.dpi 2017-10-22 17:09:30 +05:30
9 changed files with 101 additions and 139 deletions

View File

@@ -3,6 +3,18 @@ Changelog
kitty is a feature full, cross-platform, *fast*, OpenGL based terminal emulator. kitty is a feature full, cross-platform, *fast*, OpenGL based terminal emulator.
version 0.4.2 [2017-10-23]
---------------------------
- Fix a regression in 0.4.0 that broke custom key mappings
- Fix a regression in 0.4.0 that broke support for non-QWERTY keyboard layouts
- Avoid using threads to reap zombie child processes. Also prevent kitty from
hanging if the open program hangs when clicking on a URL.
version 0.4.0 [2017-10-22] version 0.4.0 [2017-10-22]
--------------------------- ---------------------------

View File

@@ -662,37 +662,11 @@ hangup(pid_t pid) {
} }
} }
static pid_t pid_buf[MAX_CHILDREN] = {0};
static size_t pid_buf_pos = 0;
static pthread_t reap_thread;
static void*
reap(void *pid_p) {
set_thread_name("KittyReapChild");
pid_t pid = *((pid_t*)pid_p);
while(true) {
pid_t ret = waitpid(pid, NULL, 0);
if (ret != pid) {
if (errno == EINTR) continue;
fprintf(stderr, "Failed to reap child process with pid: %d with error: %s\n", pid, strerror(errno));
}
break;
}
return 0;
}
static inline void static inline void
cleanup_child(ssize_t i) { cleanup_child(ssize_t i) {
close(children[i].fd); close(children[i].fd);
hangup(children[i].pid); hangup(children[i].pid);
pid_buf[pid_buf_pos] = children[i].pid;
if (waitpid(pid_buf[pid_buf_pos], NULL, WNOHANG) != pid_buf[pid_buf_pos]) {
errno = 0;
int ret = pthread_create(&reap_thread, NULL, reap, pid_buf + pid_buf_pos);
if (ret != 0) perror("Failed to create thread to reap child");
}
pid_buf_pos = (pid_buf_pos + 1) % MAX_CHILDREN;
} }

View File

@@ -324,16 +324,18 @@ with open(
defaults = parse_config(f.readlines(), check_keys=False) 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(a.func for a in defaults.keymap.values()) actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset({'combine'})
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
def merge_keymaps(defaults, newvals): def merge_keymaps(defaults, newvals):
ans = defaults.copy() ans = defaults.copy()
for k, v in newvals.items(): for k, v in newvals.items():
if v in {'noop', 'no-op', 'no_op'}: f = v.func
if f in no_op_actions:
ans.pop(k, None) ans.pop(k, None)
continue continue
if v in actions: if f in actions:
ans[k] = v ans[k] = v
return ans return ans

View File

@@ -11,7 +11,7 @@ from collections import namedtuple
from .fast_data_types import set_boss as set_c_boss from .fast_data_types import set_boss as set_c_boss
appname = 'kitty' appname = 'kitty'
version = (0, 4, 0) version = (0, 4, 2)
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
@@ -124,4 +124,12 @@ def x11_window_id(window):
return lib.glfwGetX11Window(window.window_id()) return lib.glfwGetX11Window(window.window_id())
def x11_display():
lib = glfw_lib()
ans = lib.glfwGetX11Display
ans.restype = ctypes.c_void_p
ans.argtypes = []
return ans()
iswayland = not isosx and hasattr(glfw_lib(), 'glfwGetWaylandDisplay') iswayland = not isosx and hasattr(glfw_lib(), 'glfwGetWaylandDisplay')

View File

@@ -9,6 +9,8 @@
#include "modes.h" #include "modes.h"
#include <stddef.h> #include <stddef.h>
#include <termios.h> #include <termios.h>
#include <signal.h>
#include <sys/wait.h>
#ifdef WITH_PROFILER #ifdef WITH_PROFILER
#include <gperftools/profiler.h> #include <gperftools/profiler.h>
#endif #endif
@@ -81,6 +83,28 @@ pyset_iutf8(PyObject UNUSED *self, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static void
handle_sigchld(int UNUSED signum, siginfo_t *sinfo, void UNUSED *unused) {
if (sinfo->si_code != CLD_EXITED) return;
int sav_errno = errno, status;
while(true) {
if (waitpid(sinfo->si_pid, &status, WNOHANG) == -1) {
if (errno != EINTR) break;
} else break;
}
errno = sav_errno;
}
static PyObject*
install_sigchld_handler(PyObject UNUSED *self) {
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handle_sigchld;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGCHLD, &sa, NULL) == -1) return PyErr_SetFromErrno(PyExc_OSError);
Py_RETURN_NONE;
}
#ifdef WITH_PROFILER #ifdef WITH_PROFILER
static PyObject* static PyObject*
start_profiler(PyObject UNUSED *self, PyObject *args) { start_profiler(PyObject UNUSED *self, PyObject *args) {
@@ -105,6 +129,7 @@ static PyMethodDef module_methods[] = {
{"redirect_std_streams", (PyCFunction)redirect_std_streams, 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, ""},
{"install_sigchld_handler", (PyCFunction)install_sigchld_handler, METH_NOARGS, ""},
#ifdef WITH_PROFILER #ifdef WITH_PROFILER
{"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""}, {"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""},
{"stop_profiler", (PyCFunction)stop_profiler, METH_NOARGS, ""}, {"stop_profiler", (PyCFunction)stop_profiler, METH_NOARGS, ""},

View File

@@ -4,7 +4,6 @@
import os import os
import re import re
import subprocess
from collections import namedtuple from collections import namedtuple
from kitty.fast_data_types import Face, get_fontconfig_font from kitty.fast_data_types import Face, get_fontconfig_font
@@ -35,51 +34,7 @@ def font_not_found(err, char):
return FontNotFound(msg) return FontNotFound(msg)
def get_font_subprocess( def get_font(
family='monospace',
bold=False,
italic=False,
allow_bitmaped_fonts=False,
size_in_pts=None,
character=None,
dpi=None
):
query = escape_family_name(family)
if character is not None:
query += ':charset={:x}'.format(ord(character[0]))
if not allow_bitmaped_fonts:
query += ':scalable=true:outline=true'
if size_in_pts is not None:
query += ':size={:.1f}'.format(size_in_pts)
if dpi is not None:
query += ':dpi={:.1f}'.format(dpi)
if bold:
query += ':weight=200'
if italic:
query += ':slant=100'
try:
raw = subprocess.check_output([
'fc-match', query, '-f',
'%{file}\x1e%{hinting}\x1e%{hintstyle}\x1e%{scalable}\x1e%{outline}\x1e%{weight}\x1e%{slant}\x1e%{index}'
]).decode('utf-8')
except subprocess.CalledProcessError as err:
raise font_not_found(err, character)
parts = raw.split('\x1e')
try:
path, hinting, hintstyle, scalable, outline, weight, slant, index = parts
except ValueError:
path = parts[0]
hintstyle, hinting, scalable, outline, weight, slant, index = 1, 'True', 'True', 'True', 100, 0, 0
return Font(
path,
to_bool(hinting),
int(hintstyle), bold, italic,
to_bool(scalable),
to_bool(outline), int(weight), int(slant), int(index)
)
def get_font_lib(
family='monospace', family='monospace',
bold=False, bold=False,
italic=False, italic=False,
@@ -102,9 +57,6 @@ def get_font_lib(
) )
get_font = get_font_lib
def find_font_for_character( def find_font_for_character(
family, family,
char, char,

View File

@@ -89,31 +89,32 @@ get_localized_key(int key, int scancode) {
switch(name[0]) { switch(name[0]) {
#define K(ch, name) case ch: return GLFW_KEY_##name #define K(ch, name) case ch: return GLFW_KEY_##name
// key names {{{ // key names {{{
K('A', A); K('A', A); K('a', A);
K('B', B); K('B', B); K('b', B);
K('C', C); K('C', C); K('c', C);
K('D', D); K('D', D); K('d', D);
K('E', E); K('E', E); K('e', E);
K('F', F); K('F', F); K('f', F);
K('G', G); K('G', G); K('g', G);
K('H', H); K('H', H); K('h', H);
K('I', I); K('I', I); K('i', I);
K('J', J); K('J', J); K('j', J);
K('K', K); K('K', K); K('k', K);
K('L', L); K('L', L); K('l', L);
K('M', M); K('M', M); K('m', M);
K('N', N); K('N', N); K('n', N);
K('O', O); K('O', O); K('o', O);
K('P', P); K('P', P); K('p', P);
K('Q', Q); K('Q', Q); K('q', Q);
K('S', S); K('R', R); K('r', R);
K('T', T); K('S', S); K('s', S);
K('U', U); K('T', T); K('t', T);
K('V', V); K('U', U); K('u', U);
K('W', W); K('V', V); K('v', V);
K('X', X); K('W', W); K('w', W);
K('Y', Y); K('X', X); K('x', X);
K('Z', Z); K('Y', Y); K('y', Y);
K('Z', Z); K('z', Z);
K('0', 0); K('0', 0);
K('1', 1); K('1', 1);
K('2', 2); K('2', 2);

View File

@@ -5,6 +5,7 @@
import argparse import argparse
import locale import locale
import os import os
import signal
import sys import sys
from contextlib import contextmanager from contextlib import contextmanager
from gettext import gettext as _ from gettext import gettext as _
@@ -23,8 +24,8 @@ from .fast_data_types import (
GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_PROFILE, GLFW_SAMPLES, GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_PROFILE, GLFW_SAMPLES,
GLFW_STENCIL_BITS, GLFWWindow, change_wcwidth, check_for_extensions, GLFW_STENCIL_BITS, GLFWWindow, change_wcwidth, check_for_extensions,
clear_buffers, glewInit, glfw_init, glfw_init_hint_string, clear_buffers, glewInit, glfw_init, glfw_init_hint_string,
glfw_swap_interval, glfw_terminate, glfw_window_hint, set_logical_dpi, glfw_swap_interval, glfw_terminate, glfw_window_hint,
set_options install_sigchld_handler, set_logical_dpi, set_options
) )
from .layout import all_layouts from .layout import all_layouts
from .utils import color_as_int, detach, get_logical_dpi, safe_print from .utils import color_as_int, detach, get_logical_dpi, safe_print
@@ -235,11 +236,11 @@ def setup_profiling(args):
exe = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'kitty-profile') exe = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'kitty-profile')
cg = '/tmp/kitty-profile.callgrind' cg = '/tmp/kitty-profile.callgrind'
print('Post processing profile data for', exe, '...') print('Post processing profile data for', exe, '...')
subprocess.check_call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=open(cg, 'wb')) subprocess.call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=open(cg, 'wb'))
try: try:
subprocess.Popen(['kcachegrind', cg]) subprocess.Popen(['kcachegrind', cg])
except FileNotFoundError: except FileNotFoundError:
subprocess.check_call(['pprof', '--text', exe, '/tmp/kitty-profile.log']) subprocess.call(['pprof', '--text', exe, '/tmp/kitty-profile.log'])
print('To view the graphical call data, use: kcachegrind', cg) print('To view the graphical call data, use: kcachegrind', cg)
@@ -286,6 +287,9 @@ def main():
raise SystemExit('GLFW initialization failed') raise SystemExit('GLFW initialization failed')
try: try:
with setup_profiling(args): with setup_profiling(args):
# Avoid needing to launch threads to reap zombies
install_sigchld_handler()
run_app(opts, args) run_app(opts, args)
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
finally: finally:
glfw_terminate() glfw_terminate()

View File

@@ -12,7 +12,7 @@ from contextlib import contextmanager
from functools import lru_cache from functools import lru_cache
from time import monotonic from time import monotonic
from .constants import isosx, iswayland, selection_clipboard_funcs from .constants import isosx, iswayland, selection_clipboard_funcs, x11_display
from .fast_data_types import ( from .fast_data_types import (
GLSL_VERSION, glfw_get_physical_dpi, redirect_std_streams, GLSL_VERSION, glfw_get_physical_dpi, redirect_std_streams,
wcwidth as wcwidth_impl wcwidth as wcwidth_impl
@@ -72,7 +72,6 @@ def load_libx11():
import ctypes import ctypes
from ctypes.util import find_library from ctypes.util import find_library
libx11 = ctypes.CDLL(find_library('X11')) libx11 = ctypes.CDLL(find_library('X11'))
ans = []
def cdef(name, restype, *argtypes): def cdef(name, restype, *argtypes):
f = getattr(libx11, name) f = getattr(libx11, name)
@@ -80,43 +79,27 @@ def load_libx11():
f.restype = restype f.restype = restype
if argtypes: if argtypes:
f.argtypes = argtypes f.argtypes = argtypes
ans.append(f) return f
cdef('XOpenDisplay', ctypes.c_void_p, ctypes.c_char_p) return cdef('XResourceManagerString', ctypes.c_char_p, ctypes.c_void_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): def parse_xrdb(raw):
q = 'Xft.dpi:\t' q = 'Xft.dpi:\t'
for line in raw.decode('utf-8').splitlines(): for line in raw.decode('utf-8', 'replace').splitlines():
if line.startswith(q): if line.startswith(q):
return float(line[len(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(): def x11_dpi():
try: XResourceManagerString = load_libx11()
return x11_dpi_native() display = x11_display()
except Exception: if display:
pass try:
try: raw = XResourceManagerString(display)
raw = subprocess.check_output(['xrdb', '-query'], stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) return parse_xrdb(raw)
return parse_xrdb(raw) except Exception:
except Exception: pass
pass
def get_logical_dpi(): def get_logical_dpi():
@@ -182,7 +165,8 @@ def get_primary_selection():
return '' # There is no primary selection on OS X return '' # There is no primary selection on OS X
g = selection_clipboard_funcs()[0] g = selection_clipboard_funcs()[0]
if g is None: if g is None:
ans = subprocess.check_output(['xsel', '-p'], stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL).decode('utf-8') # We cannot use check_output as we set a SIGCHLD handler to reap zombies
ans = subprocess.Popen(['xsel', '-p'], stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
if ans: if ans:
# Without this for some reason repeated pastes dont work # Without this for some reason repeated pastes dont work
set_primary_selection(ans) set_primary_selection(ans)
@@ -209,7 +193,7 @@ def open_cmd(cmd, arg=None):
if arg is not None: if arg is not None:
cmd = list(cmd) cmd = list(cmd)
cmd.append(arg) cmd.append(arg)
return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).wait() return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def open_url(url, program='default'): def open_url(url, program='default'):