Compare commits

...

18 Commits

Author SHA1 Message Date
Kovid Goyal
8d2a720fb9 version 0.5.1 2017-12-01 10:15:18 +05:30
Kovid Goyal
9ed980a511 More 32bit fixes 2017-11-30 00:02:44 +05:30
Kovid Goyal
1365eafce5 ... 2017-11-29 18:15:12 +05:30
Kovid Goyal
07b70f8c95 Image display: Fix displaying images taller than two screen heights not scrolling properly
Fixes #194
2017-11-29 18:12:22 +05:30
Kovid Goyal
f16bf2a136 ... 2017-11-29 08:32:00 +05:30
Kovid Goyal
0f2bcfabb3 Sanity check when moving baseline for line height adjustment 2017-11-29 08:30:34 +05:30
Kovid Goyal
d3e8c616b0 Fix text not vertically centered when adjusting line height
Fixes #193
2017-11-29 08:27:28 +05:30
Kovid Goyal
99888996a0 Implement special rendering for unicode block characters
Fixes #184
2017-11-26 21:29:00 +05:30
Kovid Goyal
4c94e16936 Refactor the box drawing showcase code
Make it work with the new font render infrastructure
2017-11-26 19:53:25 +05:30
Kovid Goyal
235f3940f2 DRYer 2017-11-26 18:10:34 +05:30
Kovid Goyal
e86c2f2000 Fix shift+up/down not generating correct escape codes
Fixes #186
2017-11-26 17:34:34 +05:30
Kovid Goyal
65b212fa1b Add a config option to control the line thickness for box drawing characters
Fixes #183
2017-11-26 11:24:27 +05:30
Kovid Goyal
4b0ff03868 Increase max ligature length to 9 chars
Fixes #182
2017-11-26 10:33:57 +05:30
Kovid Goyal
8961eaaa9d Fix compilation error on 32bot systems
Fixes #185
2017-11-26 08:22:14 +05:30
Kovid Goyal
356722b9a6 Fix build failure on systems with __OPTIMZE__ defined
Fixes #181
2017-11-24 22:42:56 +05:30
Kovid Goyal
89e5ae28bb Dont fail to start if the user's /etc/passwd is missing an entry for the current uid
Appears to happen on some nix systems. Fixes #178
2017-11-23 00:40:21 +05:30
Kovid Goyal
b40e1e6492 Decrease minimum fonts size to 4pt 2017-11-21 07:13:17 +05:30
Kovid Goyal
4dc6918b13 Get rid of the send_text_map code
Just transform send_text directives in the config file to map
directives.
2017-11-19 08:56:34 +05:30
22 changed files with 257 additions and 159 deletions

View File

@@ -3,6 +3,23 @@ Changelog
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
version 0.5.1 [2017-12-01]
---------------------------
- Add an option to control the thickness of lines in box drawing characters
- Increase max. allowed ligature length to nine characters
- Fix text not vertically centered when adjusting line height
- Fix unicode block characters not being rendered properly
- Fix shift+up/down not generating correct escape codes
- Image display: Fix displaying images taller than two screen heights not
scrolling properly
version 0.5.0 [2017-11-19]
---------------------------

View File

@@ -12,7 +12,7 @@ from .fast_data_types import (
destroy_sprite_map, glfw_post_empty_event, layout_sprite_map
)
from .fonts.render import prerender, resize_fonts, set_font_family
from .keys import get_key_map, get_sent_data, get_shortcut
from .keys import get_key_map, get_shortcut
from .session import create_session
from .tabs import SpecialWindow, TabManager
from .utils import (
@@ -199,13 +199,6 @@ class Boss:
passthrough = f(*key_action.args)
if passthrough is not True:
return True
key, scancode, action, mods = self.current_key_press_info
data = get_sent_data(
self.opts.send_text_map, key, scancode, mods, window, action
)
if data:
window.write_to_child(data)
return True
return False
def combine(self, *actions):

View File

@@ -391,7 +391,7 @@ resize_pty(ChildMonitor *self, PyObject *args) {
if (fd == -1) FIND(add_queue, add_queue_count);
if (fd != -1) {
if (!pty_resize(fd, &dim)) PyErr_SetFromErrno(PyExc_OSError);
} else fprintf(stderr, "Failed to send resize signal to child with id: %lu (children count: %u) (add queue: %lu)\n", window_id, self->count, add_queue_count);
} else fprintf(stderr, "Failed to send resize signal to child with id: %lu (children count: %u) (add queue: %zu)\n", window_id, self->count, add_queue_count);
children_mutex(unlock);
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;

View File

@@ -19,7 +19,7 @@ from .rgb import to_color
from .utils import safe_print
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
MINIMUM_FONT_SIZE = 6
MINIMUM_FONT_SIZE = 4
def to_font_size(x):
@@ -175,7 +175,7 @@ def parse_send_text_bytes(text):
return ast.literal_eval("'''" + text + "'''").encode('utf-8')
def parse_send_text(val):
def parse_send_text(val, keymap):
parts = val.split(' ')
def abort(msg):
@@ -187,28 +187,10 @@ def parse_send_text(val):
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 = parse_send_text_bytes(text)
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'})
text = ' '.join(parts[2:])
key_str = '{} send_text {} {}'.format(sc, mode, text)
return parse_key(key_str, keymap)
def to_open_url_modifiers(val):
@@ -238,6 +220,13 @@ def adjust_line_height(x):
return int(x)
def box_drawing_scale(x):
ans = tuple(float(x.strip()) for x in x.split(','))
if len(ans) != 4:
raise ValueError('Invalid box_drawing scale, must have four entries')
return ans
type_map = {
'adjust_line_height': adjust_line_height,
'scrollback_lines': positive_int,
@@ -267,6 +256,7 @@ type_map = {
'use_system_wcwidth': to_bool,
'macos_hide_titlebar': to_bool,
'macos_option_as_alt': to_bool,
'box_drawing_scale': box_drawing_scale,
}
for name in (
@@ -285,11 +275,6 @@ def parse_config(lines, check_keys=True):
ans = {
'keymap': {},
'symbol_map': {},
'send_text_map': {
'kitty': {},
'normal': {},
'application': {}
}
}
if check_keys:
all_keys = defaults._asdict()
@@ -307,9 +292,8 @@ def parse_config(lines, check_keys=True):
ans['symbol_map'].update(parse_symbol_map(val))
continue
if key == 'send_text':
stvals = parse_send_text(val)
for k, v in ans['send_text_map'].items():
v.update(stvals.get(k, {}))
# For legacy compatibility
parse_send_text(val, ans['keymap'])
continue
if check_keys:
if key not in all_keys:
@@ -360,11 +344,6 @@ def merge_configs(defaults, vals):
newvals = vals.get(k, {})
if k == 'keymap':
ans['keymap'] = merge_keymaps(v, newvals)
elif k == 'send_text_map':
ans[k] = {
m: merge_dicts(mm, newvals.get(m, {}))
for m, mm in v.items()
}
else:
ans[k] = merge_dicts(v, newvals)
else:

View File

@@ -11,7 +11,7 @@ from collections import namedtuple
from .fast_data_types import set_boss as set_c_boss
appname = 'kitty'
version = (0, 5, 0)
version = (0, 5, 1)
str_version = '.'.join(map(str, version))
_plat = sys.platform.lower()
isosx = 'darwin' in _plat
@@ -67,7 +67,11 @@ cell_size = ViewportSize()
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
terminfo_dir = os.path.join(base_dir, 'terminfo')
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
try:
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
except KeyError:
print('Failed to read login shell from /etc/passwd for current user, falling back to /bin/sh', file=sys.stderr)
shell_path = '/bin/sh'
GLint = ctypes.c_int if ctypes.sizeof(ctypes.c_int) == 4 else ctypes.c_long
GLuint = ctypes.c_uint if ctypes.sizeof(ctypes.c_uint) == 4 else ctypes.c_ulong

View File

@@ -9,6 +9,7 @@
#include "state.h"
#define MISSING_GLYPH 4
#define MAX_NUM_EXTRA_GLYPHS 8
typedef uint16_t glyph_index;
typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, uint8_t*);
@@ -21,13 +22,17 @@ typedef struct SpecialGlyphCache SpecialGlyphCache;
enum {NO_FONT=-3, MISSING_FONT=-2, BLANK_FONT=-1, BOX_FONT=0};
typedef struct {
glyph_index data[MAX_NUM_EXTRA_GLYPHS];
} ExtraGlyphs;
struct SpritePosition {
SpritePosition *next;
bool filled, rendered;
sprite_index x, y, z;
uint8_t ligature_index;
glyph_index glyph;
uint64_t extra_glyphs;
ExtraGlyphs extra_glyphs;
};
struct SpecialGlyphCache {
@@ -70,6 +75,7 @@ typedef struct {
static Fonts fonts = {0};
// Sprites {{{
static inline void
@@ -104,15 +110,25 @@ do_increment(int *error) {
}
static inline bool
extra_glyphs_equal(ExtraGlyphs *a, ExtraGlyphs *b) {
for (size_t i = 0; i < MAX_NUM_EXTRA_GLYPHS; i++) {
if (a->data[i] != b->data[i]) return false;
if (a->data[i] == 0) return true;
}
return true;
}
static SpritePosition*
sprite_position_for(Font *font, glyph_index glyph, uint64_t extra_glyphs, uint8_t ligature_index, int *error) {
sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, uint8_t ligature_index, int *error) {
glyph_index idx = glyph & 0x3ff;
SpritePosition *s = font->sprite_map + idx;
// Optimize for the common case of glyph under 1024 already in the cache
if (LIKELY(s->glyph == glyph && s->filled && s->extra_glyphs == extra_glyphs && s->ligature_index == ligature_index)) return s; // Cache hit
if (LIKELY(s->glyph == glyph && s->filled && extra_glyphs_equal(&s->extra_glyphs, extra_glyphs) && s->ligature_index == ligature_index)) return s; // Cache hit
while(true) {
if (s->filled) {
if (s->glyph == glyph && s->extra_glyphs == extra_glyphs && s->ligature_index == ligature_index) return s; // Cache hit
if (s->glyph == glyph && extra_glyphs_equal(&s->extra_glyphs, extra_glyphs) && s->ligature_index == ligature_index) return s; // Cache hit
} else {
break;
}
@@ -123,7 +139,7 @@ sprite_position_for(Font *font, glyph_index glyph, uint64_t extra_glyphs, uint8_
s = s->next;
}
s->glyph = glyph;
s->extra_glyphs = extra_glyphs;
memcpy(&s->extra_glyphs, extra_glyphs, sizeof(ExtraGlyphs));
s->ligature_index = ligature_index;
s->filled = true;
s->rendered = false;
@@ -179,7 +195,7 @@ free_maps(Font *font) {
void
clear_sprite_map(Font *font) {
#define CLEAR(s) s->filled = false; s->rendered = false; s->glyph = 0; s->extra_glyphs = 0; s->x = 0; s->y = 0; s->z = 0; s->ligature_index = 0;
#define CLEAR(s) s->filled = false; s->rendered = false; s->glyph = 0; memset(&s->extra_glyphs, 0, sizeof(ExtraGlyphs)); s->x = 0; s->y = 0; s->z = 0; s->ligature_index = 0;
SpritePosition *s;
for (size_t i = 0; i < sizeof(font->sprite_map)/sizeof(font->sprite_map[0]); i++) {
s = font->sprite_map + i;
@@ -246,7 +262,7 @@ del_font(Font *f) {
static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0;
static uint8_t *canvas = NULL;
#define CELLS_IN_CANVAS 16
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
static inline void
clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height); }
@@ -267,11 +283,17 @@ update_cell_metrics() {
CALL(fonts.bold_font_idx, 0, false); CALL(fonts.italic_font_idx, 0, false); CALL(fonts.bi_font_idx, 0, false);
cell_metrics(fonts.fonts[fonts.medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness);
if (!cell_width) { PyErr_SetString(PyExc_ValueError, "Failed to calculate cell width for the specified font."); return NULL; }
unsigned int before_cell_height = cell_height;
if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px);
if (OPT(adjust_line_height_frac) != 0.f) cell_height *= OPT(adjust_line_height_frac);
int line_height_adjustment = cell_height - before_cell_height;
if (cell_height < 4) { PyErr_SetString(PyExc_ValueError, "line height too small after adjustment"); return NULL; }
if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; }
underline_position = MIN(cell_height - 1, underline_position);
if (line_height_adjustment > 1) {
baseline += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
underline_position += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
}
sprite_tracker_set_layout(cell_width, cell_height);
global_state.cell_width = cell_width; global_state.cell_height = cell_height;
free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height);
@@ -360,7 +382,7 @@ START_ALLOW_CASE_RANGE
case ' ':
return BLANK_FONT;
case 0x2500 ... 0x2570:
case 0x2574 ... 0x257f:
case 0x2574 ... 0x259f:
case 0xe0b0:
case 0xe0b2:
return BOX_FONT;
@@ -393,14 +415,14 @@ static inline glyph_index
box_glyph_id(char_type ch) {
START_ALLOW_CASE_RANGE
switch(ch) {
case 0x2500 ... 0x257f:
case 0x2500 ... 0x259f:
return ch - 0x2500;
case 0xe0b0:
return 0x80;
return 0xfa;
case 0xe0b2:
return 0x81;
return 0xfb;
default:
return 0x82;
return 0xff;
}
END_ALLOW_CASE_RANGE
}
@@ -411,7 +433,8 @@ static void
render_box_cell(Cell *cell) {
int error = 0;
glyph_index glyph = box_glyph_id(cell->ch);
SpritePosition *sp = sprite_position_for(&fonts.fonts[BOX_FONT], glyph, 0, false, &error);
static ExtraGlyphs extra_glyphs = {{0}};
SpritePosition *sp = sprite_position_for(&fonts.fonts[BOX_FONT], glyph, &extra_glyphs, false, &error);
if (sp == NULL) {
sprite_map_set_error(error); PyErr_Print();
set_sprite(cell, 0, 0, 0);
@@ -462,7 +485,7 @@ extract_cell_from_canvas(unsigned int i, unsigned int num_cells) {
}
static inline void
render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, uint64_t extra_glyphs) {
render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) {
static SpritePosition* sprite_position[16];
int error = 0;
num_cells = MIN(sizeof(sprite_position)/sizeof(sprite_position[0]), num_cells);
@@ -550,7 +573,7 @@ check_cell_consumed(CellData *cell_data, Cell *last_cell) {
}
static inline glyph_index
next_group(Font *font, unsigned int *num_group_cells, unsigned int *num_group_glyphs, Cell *cells, hb_glyph_info_t *info, unsigned int max_num_glyphs, unsigned int max_num_cells, uint64_t *extra_glyphs) {
next_group(Font *font, unsigned int *num_group_cells, unsigned int *num_group_glyphs, Cell *cells, hb_glyph_info_t *info, unsigned int max_num_glyphs, unsigned int max_num_cells, ExtraGlyphs *extra_glyphs) {
// See https://github.com/behdad/harfbuzz/issues/615 for a discussion of
// how to break text into cells. In addition, we have to deal with
// monospace ligature fonts that use dummy glyphs of zero size to implement
@@ -558,7 +581,7 @@ next_group(Font *font, unsigned int *num_group_cells, unsigned int *num_group_gl
CellData cell_data;
cell_data.cell = cells; cell_data.num_codepoints = num_codepoints_in_cell(cells); cell_data.codepoints_consumed = 0; cell_data.current_codepoint = cells->ch;
#define LIMIT 5
#define LIMIT (MAX_NUM_EXTRA_GLYPHS + 1)
glyph_index glyphs_in_group[LIMIT];
unsigned int ncells = 0, nglyphs = 0, n;
uint32_t previous_cluster = UINT32_MAX, cluster;
@@ -584,27 +607,16 @@ next_group(Font *font, unsigned int *num_group_cells, unsigned int *num_group_gl
}
*num_group_cells = MAX(1, MIN(ncells, cell_limit));
*num_group_glyphs = MAX(1, MIN(nglyphs, glyph_limit));
memset(extra_glyphs, 0, sizeof(ExtraGlyphs));
#define G(n) ((uint64_t)(glyphs_in_group[n] & 0xffff))
switch(nglyphs) {
case 0:
case 1:
*extra_glyphs = 0;
break;
case 2:
*extra_glyphs = G(1);
break;
case 3:
*extra_glyphs = G(1) | (G(2) << 16);
break;
case 4:
*extra_glyphs = G(1) | (G(2) << 16) | (G(3) << 32);
break;
default: // we only support a maximum of four extra glyphs per cell
*extra_glyphs = G(1) | (G(2) << 16) | (G(3) << 32) | (G(4) << 48);
default:
memcpy(&extra_glyphs->data, glyphs_in_group + 1, sizeof(glyph_index) * MIN(MAX_NUM_EXTRA_GLYPHS, nglyphs - 1));
break;
}
#undef G
#undef LIMIT
return glyphs_in_group[0];
}
@@ -633,11 +645,12 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) {
clear_canvas();
#endif
unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells;
uint64_t extra_glyphs; glyph_index first_glyph;
ExtraGlyphs extra_glyphs;
glyph_index first_glyph;
while(run_pos < num_glyphs && cell_pos < num_cells) {
first_glyph = next_group(font, &num_group_cells, &num_group_glyphs, first_cell + cell_pos, info + run_pos, num_glyphs - run_pos, num_cells - cell_pos, &extra_glyphs);
/* printf("Group: num_group_cells: %u num_group_glyphs: %u\n", num_group_cells, num_group_glyphs); */
render_group(num_group_cells, num_group_glyphs, first_cell + cell_pos, info + run_pos, positions + run_pos, font, first_glyph, extra_glyphs);
render_group(num_group_cells, num_group_glyphs, first_cell + cell_pos, info + run_pos, positions + run_pos, font, first_glyph, &extra_glyphs);
run_pos += num_group_glyphs; cell_pos += num_group_cells;
}
}
@@ -665,10 +678,13 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
PyObject *ans = PyList_New(0);
unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells;
uint64_t extra_glyphs; glyph_index first_glyph;
ExtraGlyphs extra_glyphs;
glyph_index first_glyph;
while(run_pos < num_glyphs && cell_pos < num) {
first_glyph = next_group(font, &num_group_cells, &num_group_glyphs, line->cells + cell_pos, info + run_pos, num_glyphs - run_pos, num - cell_pos, &extra_glyphs);
PyList_Append(ans, Py_BuildValue("IIIK", num_group_cells, num_group_glyphs, first_glyph, extra_glyphs));
PyObject *eg = PyTuple_New(MAX_NUM_EXTRA_GLYPHS);
for (size_t g = 0; g < MAX_NUM_EXTRA_GLYPHS; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", extra_glyphs.data[g]));
PyList_Append(ans, Py_BuildValue("IIIN", num_group_cells, num_group_glyphs, first_glyph, eg));
run_pos += num_group_glyphs; cell_pos += num_group_cells;
}
if (face) { Py_CLEAR(face); free(font); }
@@ -778,10 +794,10 @@ sprite_map_set_layout(PyObject UNUSED *self, PyObject *args) {
static PyObject*
test_sprite_position_for(PyObject UNUSED *self, PyObject *args) {
glyph_index glyph;
uint64_t extra_glyphs = 0;
if (!PyArg_ParseTuple(args, "H|I", &glyph, &extra_glyphs)) return NULL;
ExtraGlyphs extra_glyphs = {{0}};
if (!PyArg_ParseTuple(args, "H|I", &glyph, &extra_glyphs.data)) return NULL;
int error;
SpritePosition *pos = sprite_position_for(&fonts.fonts[fonts.medium_font_idx], glyph, extra_glyphs, 0, &error);
SpritePosition *pos = sprite_position_for(&fonts.fonts[fonts.medium_font_idx], glyph, &extra_glyphs, 0, &error);
if (pos == NULL) { sprite_map_set_error(error); return NULL; }
return Py_BuildValue("HHH", pos->x, pos->y, pos->z);
}

View File

@@ -3,15 +3,23 @@
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import math
import os
from functools import partial as p
from itertools import repeat
from kitty.utils import get_logical_dpi
scale = (0.001, 1, 1.5, 2)
def set_scale(new_scale):
global scale
scale = tuple(new_scale)
def thickness(level=1, horizontal=True):
dpi = get_logical_dpi()[0 if horizontal else 1]
pts = (0.001, 1, 1.5, 2)[level]
pts = scale[level]
return int(math.ceil(pts * dpi / 72.0))
@@ -264,6 +272,49 @@ def inner_corner(buf, width, height, which='tl', level=1):
draw_vline(buf, width, y1, y2, width // 2 + (xd * hgap), level)
def vblock(buf, width, height, frac=1, gravity='top'):
num_rows = min(height, round(frac * height))
start = 0 if gravity == 'top' else height - num_rows
for r in range(start, start + num_rows):
off = r * width
for c in range(off, off + width):
buf[c] = 255
def hblock(buf, width, height, frac=1, gravity='left'):
num_cols = min(width, round(frac * width))
start = 0 if gravity == 'left' else width - num_cols
for r in range(height):
off = r * width + start
for c in range(off, off + num_cols):
buf[c] = 255
def shade(buf, width, height, frac=1/4):
rand = bytearray(os.urandom(width * height))
cutoff = int(frac * 255)
for r in range(height):
off = width * r
for c in range(width):
q = off + c
if rand[q] < cutoff:
buf[q] = 255
def quad(buf, width, height, x=0, y=0):
num_cols = width // 2
left = x * num_cols
right = width if x else num_cols
num_rows = height // 2
top = y * num_rows
bottom = height if y else num_rows
for r in range(top, bottom):
off = r * width
for c in range(left, right):
buf[off + c] = 255
box_chars = {
'': [hline],
'': [p(hline, level=3)],
@@ -308,6 +359,38 @@ box_chars = {
'': [p(inner_corner, which='tl'), p(inner_corner, which='bl'), p(dvline, only='right')],
'': [p(inner_corner, which='bl'), p(inner_corner, which='br'), p(dhline, only='top')],
'': [p(inner_corner, which='tl'), p(inner_corner, which='tr'), p(dhline, only='bottom')],
'': [p(vblock, frac=1/2)],
'': [p(vblock, frac=1/8, gravity='bottom')],
'': [p(vblock, frac=1/4, gravity='bottom')],
'': [p(vblock, frac=3/8, gravity='bottom')],
'': [p(vblock, frac=1/2, gravity='bottom')],
'': [p(vblock, frac=5/8, gravity='bottom')],
'': [p(vblock, frac=3/4, gravity='bottom')],
'': [p(vblock, frac=7/8, gravity='bottom')],
'': [p(vblock, frac=1, gravity='bottom')],
'': [p(hblock, frac=7/8)],
'': [p(hblock, frac=3/4)],
'': [p(hblock, frac=5/8)],
'': [p(hblock, frac=1/2)],
'': [p(hblock, frac=3/8)],
'': [p(hblock, frac=1/4)],
'': [p(hblock, frac=1/8)],
'': [p(hblock, frac=1/2, gravity='right')],
'': [p(shade, frac=1/4)],
'': [p(shade, frac=2/4)],
'': [p(shade, frac=3/4)],
'': [p(vblock, frac=1/8)],
'': [p(hblock, frac=1/8, gravity='right')],
'': [p(quad, y=1)],
'': [p(quad, x=1, y=1)],
'': [quad],
'': [quad, p(quad, y=1), p(quad, x=1, y=1)],
'': [quad, p(quad, x=1, y=1)],
'': [quad, p(quad, x=1), p(quad, y=1)],
'': [quad, p(quad, x=1, y=1), p(quad, x=1)],
'': [p(quad, x=1)],
'': [p(quad, x=1), p(quad, y=1)],
'': [p(quad, x=1), p(quad, y=1), p(quad, x=1, y=1)],
}
t, f = 1, 3
@@ -336,8 +419,6 @@ for chars, func in (('╒╕╘╛', dvcorner), ('╓╖╙╜', dhcorner), ('
for ch in chars:
box_chars[ch] = [p(func, which=ch)]
is_renderable_box_char = box_chars.__contains__
def render_box_char(ch, buf, width, height):
for func in box_chars[ch]:
@@ -354,34 +435,41 @@ def render_missing_glyph(buf, width, height):
draw_vline(buf, width, vgap, height - vgap + 1, width - hgap, 0)
def join_rows(width, height, rows):
import ctypes
ans = (ctypes.c_ubyte * (width * height * len(rows)))()
pos = ctypes.addressof(ans)
row_sz = width * height
for row in rows:
ctypes.memmove(pos, ctypes.addressof(row), row_sz)
pos += row_sz
return ans
def test_drawing(sz=32, family='monospace'):
from .render import join_cells, display_bitmap, render_cell, set_font_family
from kitty.config import defaults
opts = defaults._replace(font_family=family, font_size=sz)
width, height = set_font_family(opts)
from .render import display_bitmap, setup_for_testing
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu
width, height = setup_for_testing(family, sz)[1:]
space = bytearray(width * height)
def join_cells(cells):
cells = tuple(bytes(x) for x in cells)
return concat_cells(width, height, cells)
def render_chr(ch):
if ch in box_chars:
cell = bytearray(len(space))
render_box_char(ch, cell, width, height)
return cell
return space
pos = 0x2500
rows = []
space = render_cell()[0]
space_row = join_cells(width, height, *repeat(space, 32))
space_row = join_cells(repeat(space, 32))
for r in range(8):
row = []
for i in range(16):
row.append(render_cell(chr(pos))[0]), row.append(space)
pos += 1
rows.append(join_cells(width, height, *row))
rows.append(space_row)
width *= 32
im = join_rows(width, height, rows)
display_bitmap(im, width, height * len(rows))
try:
for r in range(10):
row = []
for i in range(16):
row.append(render_chr(chr(pos)))
row.append(space)
pos += 1
rows.append(join_cells(row))
rows.append(space_row)
rgb_data = b''.join(rows)
width *= 32
height *= len(rows)
assert len(rgb_data) == width * height * 3, '{} != {}'.format(len(rgb_data), width * height * 3)
display_bitmap(rgb_data, width, height)
finally:
set_send_sprite_to_gpu(None)

View File

@@ -201,18 +201,22 @@ def shape_string(text="abcd", family='monospace', size=11.0, dpi=96.0, path=None
set_send_sprite_to_gpu(None)
def test_render_string(text='Hello, world!', family='monospace', size=144.0, dpi=96.0):
def display_bitmap(rgb_data, width, height):
from tempfile import NamedTemporaryFile
from kitty.fast_data_types import concat_cells, current_fonts
from kitty.icat import detect_support, show
if not hasattr(test_render_string, 'detected') and not detect_support():
if not hasattr(display_bitmap, 'detected') and not detect_support():
raise SystemExit('Your terminal does not support the graphics protocol')
test_render_string.detected = True
display_bitmap.detected = True
with NamedTemporaryFile(delete=False) as f:
f.write(rgb_data)
show(f.name, width, height, 24)
def test_render_string(text='Hello, world!', family='monospace', size=144.0, dpi=96.0):
from kitty.fast_data_types import concat_cells, current_fonts
cell_width, cell_height, cells = render_string(text, family, size, dpi)
rgb_data = concat_cells(cell_width, cell_height, tuple(cells))
with NamedTemporaryFile(delete=False) as f:
f.write(rgb_data)
cf = current_fonts()
fonts = [cf['medium'].display_name()]
fonts.extend(f.display_name() for f in cf['fallback'])
@@ -221,7 +225,7 @@ def test_render_string(text='Hello, world!', family='monospace', size=144.0, dpi
print(msg)
except UnicodeEncodeError:
sys.stdout.buffer.write(msg.encode('utf-8') + b'\n')
show(f.name, cell_width * len(cells), cell_height, 24)
display_bitmap(rgb_data, cell_width * len(cells), cell_height)
print('\n')

View File

@@ -130,7 +130,8 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt
bool
set_size_for_face(PyObject *s, unsigned int desired_height, bool force) {
Face *self = (Face*)s;
FT_UInt w = (FT_UInt)(ceil(global_state.font_sz_in_pts * 64.0)), xdpi = (FT_UInt)global_state.logical_dpi_x, ydpi = (FT_UInt)global_state.logical_dpi_y;
FT_F26Dot6 w = (FT_F26Dot6)(ceil(global_state.font_sz_in_pts * 64.0));
FT_UInt xdpi = (FT_UInt)global_state.logical_dpi_x, ydpi = (FT_UInt)global_state.logical_dpi_y;
if (!force && (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi)) return true;
((Face*)self)->size_in_pts = global_state.font_sz_in_pts;
return set_font_size(self, w, w, xdpi, ydpi, desired_height);

16
kitty/keys.h generated
View File

@@ -569,9 +569,9 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 57: // LEFT
return "\x06\x1b\x5b\x31\x3b\x32\x44";
case 58: // DOWN
return "\x03\x1b\x5b\x42";
return "\x06\x1b\x5b\x31\x3b\x32\x42";
case 59: // UP
return "\x03\x1b\x5b\x41";
return "\x06\x1b\x5b\x31\x3b\x32\x41";
case 60: // PAGE_UP
return "\x04\x1b\x5b\x35\x7e";
case 61: // PAGE_DOWN
@@ -1767,9 +1767,9 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 57: // LEFT
return "\x06\x1b\x5b\x31\x3b\x32\x44";
case 58: // DOWN
return "\x03\x1b\x5b\x42";
return "\x06\x1b\x5b\x31\x3b\x32\x42";
case 59: // UP
return "\x03\x1b\x5b\x41";
return "\x06\x1b\x5b\x31\x3b\x32\x41";
case 60: // PAGE_UP
return "\x04\x1b\x5b\x35\x7e";
case 61: // PAGE_DOWN
@@ -2973,9 +2973,9 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 57: // LEFT
return "\x06\x1b\x5b\x31\x3b\x32\x44";
case 58: // DOWN
return "\x03\x1b\x4f\x42";
return "\x06\x1b\x5b\x31\x3b\x32\x42";
case 59: // UP
return "\x03\x1b\x4f\x41";
return "\x06\x1b\x5b\x31\x3b\x32\x41";
case 60: // PAGE_UP
return "\x04\x1b\x5b\x35\x7e";
case 61: // PAGE_DOWN
@@ -4171,9 +4171,9 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 57: // LEFT
return "\x06\x1b\x5b\x31\x3b\x32\x44";
case 58: // DOWN
return "\x03\x1b\x4f\x42";
return "\x06\x1b\x5b\x31\x3b\x32\x42";
case 59: // UP
return "\x03\x1b\x4f\x41";
return "\x06\x1b\x5b\x31\x3b\x32\x41";
case 60: // PAGE_UP
return "\x04\x1b\x5b\x35\x7e";
case 61: // PAGE_DOWN

View File

@@ -79,6 +79,8 @@ SHIFTED_KEYS = {
defines.GLFW_KEY_END: key_as_bytes('kEND'),
defines.GLFW_KEY_LEFT: key_as_bytes('kLFT'),
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
defines.GLFW_KEY_UP: key_as_bytes('kri'),
defines.GLFW_KEY_DOWN: key_as_bytes('kind'),
}
control_codes.update({
@@ -225,13 +227,6 @@ def get_shortcut(keymap, mods, key, scancode):
return keymap.get((mods & 0b1111, key))
def get_sent_data(send_text_map, key, scancode, mods, window, action):
if action in (defines.GLFW_PRESS, defines.GLFW_REPEAT):
m = keyboard_mode_name(window.screen)
keymap = send_text_map[m]
return keymap.get((mods & 0b1111, key))
def generate_key_table():
# To run this, use: python3 . -c "from kitty.keys import *; generate_key_table()"
import os

View File

@@ -31,6 +31,12 @@ font_size_delta 2
# 100% to reduce line height (but this might cause rendering artifacts).
adjust_line_height 0
# Change the sizes of the lines used for the box drawing unicode characters
# These values are in pts. They will be scaled by the monitor DPI to arrive at
# a pixel value. There must be four values corresponding to thin, normal, thick,
# and very thick lines;
box_drawing_scale 0.001, 1, 1.5, 2
# The foreground color
foreground #dddddd
@@ -298,8 +304,6 @@ map ctrl+shift+f11 toggle_fullscreen
# to the start of the line (same as pressing the Home key):
# map ctrl+alt+a send_text normal Word\x1b[H
# map ctrl+alt+a send_text application Word\x1bOH
# There is also a legacy syntax for send_text, which looks like:
# send_text all ctrl+alt+a some text
# Symbol mapping (special font for specified unicode code points). Map the
# specified unicode codepoints to a particular font. Useful if you need special

View File

@@ -268,7 +268,7 @@ linebuf_index(LineBuf* self, index_type top, index_type bottom) {
}
static PyObject*
index(LineBuf *self, PyObject *args) {
pyw_index(LineBuf *self, PyObject *args) {
#define index_doc "index(top, bottom) -> Scroll all lines in the range [top, bottom] by one upwards. After scrolling, bottom will be top."
unsigned int top, bottom;
if (!PyArg_ParseTuple(args, "II", &top, &bottom)) return NULL;
@@ -435,7 +435,7 @@ static PyMethodDef methods[] = {
METHOD(set_attribute, METH_VARARGS)
METHOD(set_continued, METH_VARARGS)
METHOD(dirty_lines, METH_NOARGS)
METHOD(index, METH_VARARGS)
{"index", (PyCFunction)pyw_index, METH_VARARGS, NULL},
METHOD(reverse_index, METH_VARARGS)
METHOD(insert_lines, METH_VARARGS)
METHOD(delete_lines, METH_VARARGS)

View File

@@ -161,7 +161,7 @@ url_end_at(Line *self, PyObject *x) {
static PyObject*
text_at(Line* self, Py_ssize_t xval) {
#define text_at_doc "[x] -> Return the text in the specified cell"
if (xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; }
if ((unsigned)xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; }
return line_text_at(self->cells[xval].ch, self->cells[xval].cc);
}

View File

@@ -26,6 +26,7 @@ from .fast_data_types import (
glfw_init, glfw_init_hint_string, glfw_swap_interval, glfw_terminate,
glfw_window_hint, install_sigchld_handler, set_logical_dpi, set_options
)
from .fonts.box_drawing import set_scale
from .layout import all_layouts
from .utils import (
color_as_int, detach, end_startup_notification, get_logical_dpi,
@@ -170,6 +171,7 @@ def initialize_window(window, opts, debug_gl=False):
def run_app(opts, args):
set_options(opts)
setup_opengl(opts)
set_scale(opts.box_drawing_scale)
load_cached_values()
if 'window-size' in cached_values and opts.remember_window_size:
ws = cached_values['window-size']

View File

@@ -13,13 +13,13 @@
extern PyTypeObject Screen_Type;
// utils {{{
static unsigned long pow10[] = {
static uint64_t pow10[] = {
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000
};
static inline unsigned long
static inline uint64_t
utoi(uint32_t *buf, unsigned int sz) {
unsigned long ans = 0;
uint64_t ans = 0;
uint32_t *p = buf;
// Ignore leading zeros
while(sz > 0) {
@@ -27,7 +27,7 @@ utoi(uint32_t *buf, unsigned int sz) {
else break;
}
if (sz < sizeof(pow10)/sizeof(pow10[0])) {
for (int i = sz-1, j=0; i >=0; i--, j++) {
for (int i = sz-1, j=0; i >= 0; i--, j++) {
ans += (p[i] - '0') * pow10[j];
}
}
@@ -1053,7 +1053,7 @@ extern uint32_t *latin1_charset;
static inline void
_parse_bytes(Screen *screen, uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
uint32_t prev = screen->utf8_state;
for (unsigned int i = 0; i < len; i++) {
for (unsigned int i = 0; i < (unsigned int)len; i++) {
if (screen->use_latin1) dispatch_unicode_char(screen, latin1_charset[buf[i]], dump_callback);
else {
switch (decode_utf8(&screen->utf8_state, &screen->utf8_codepoint, buf[i])) {

View File

@@ -436,7 +436,8 @@ screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const u
if (response != NULL) write_to_child(self, response, strlen(response));
if (x != self->cursor->x || y != self->cursor->y) {
if (self->cursor->x >= self->columns) { self->cursor->x = 0; self->cursor->y++; }
if (self->cursor->y > self->margin_bottom) { screen_scroll(self, self->cursor->y - self->margin_bottom); }
if (self->cursor->y > self->margin_bottom) screen_scroll(self, self->cursor->y - self->margin_bottom);
screen_ensure_bounds(self, false);
}
}
// }}}
@@ -701,7 +702,6 @@ screen_index(Screen *self) {
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--;

View File

@@ -170,16 +170,10 @@ PYWRAP1(set_options) {
OPT(select_by_word_characters)[i] = PyUnicode_READ(PyUnicode_KIND(chars), PyUnicode_DATA(chars), i);
}
OPT(select_by_word_characters_count) = PyUnicode_GET_LENGTH(chars);
Py_DECREF(chars);
GA(keymap); set_special_keys(ret);
Py_DECREF(ret); if (PyErr_Occurred()) return NULL;
GA(send_text_map);
dict_iter(ret) {
set_special_keys(value);
}}
Py_DECREF(ret); if (PyErr_Occurred()) return NULL;
Py_DECREF(chars);
PyObject *al = PyObject_GetAttrString(args, "adjust_line_height");
if (PyFloat_Check(al)) {

View File

@@ -190,6 +190,8 @@ string_capabilities = {
'kpp': r'\E[5~',
# Scroll backwards (reverse index)
'kri': r'\E[1;2A',
# scroll forwards (index)
'kind': r'\E[1;2B',
# Restore cursor
'rc': r'\E8',
# Reverse video
@@ -276,8 +278,6 @@ string_capabilities = {
# 'is2': r'\E[!p\E[?3;4l\E[4l\E>',
# # Enter/send key
# 'kent': r'\EOM',
# # scroll forwards
# 'kind': r'\E[1;2B',
# # reset2
# 'rs2': r'\E[!p\E[?3;4l\E[4l\E>',
}
@@ -350,6 +350,7 @@ termcap_aliases.update({
'kN': 'knp',
'kP': 'kpp',
'kR': 'kri',
'kF': 'kind',
'rc': 'rc',
'mr': 'rev',
'sr': 'ri',
@@ -400,7 +401,6 @@ termcap_aliases.update({
# 'mk': 'invis',
# 'is': 'is2',
# '@8': 'kent',
# 'kF': 'kind',
# 'r2': 'rs2',
})

Binary file not shown.

View File

@@ -85,6 +85,7 @@ xterm-kitty|KovIdTTY,
khlp=,
khome=\EOH,
kich1=\E[2~,
kind=\E[1;2B,
kmous=\E[M,
knp=\E[6~,
kpp=\E[5~,

Binary file not shown.