From a6a43a6a161e358eb3f7a9ec7b949149e029689a Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 2 Jul 2021 09:16:49 -0500 Subject: [PATCH 1/8] options: add 'allow_resize_csi' Signed-off-by: Patrick Williams --- kittens/query_terminal/main.py | 10 ++++++++++ kitty/options/definition.py | 9 +++++++++ kitty/options/parse.py | 3 +++ kitty/options/to-c-generated.h | 15 +++++++++++++++ kitty/options/types.py | 2 ++ kitty/state.h | 1 + 6 files changed, 40 insertions(+) diff --git a/kittens/query_terminal/main.py b/kittens/query_terminal/main.py index d1af60756cc..2105ab0840d 100644 --- a/kittens/query_terminal/main.py +++ b/kittens/query_terminal/main.py @@ -94,6 +94,16 @@ def get_result(opts: Options) -> str: return 'ask' if opts.allow_hyperlinks == 0b11 else ('yes' if opts.allow_hyperlinks else 'no') +@query +class AllowResizeCSI(Query): + name: str = 'allow_resize_csi' + help_text: str = 'yes or no' + + @staticmethod + def get_result(opts: Options) -> str: + return 'yes' if opts.allow_resize_csi else 'no' + + @query class FontFamily(Query): name: str = 'font_family' diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 73154de20c4..10856227821 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -2522,6 +2522,15 @@ ''' ) +opt('allow_resize_csi', 'no', + option_type='to_bool', ctype='bool', + long_text=''' +Process resize (CSI 8/9/10) escape sequences. If disabled resize escape +sequences are ignored. Otherwise, they are handled by resizing windows as +specified. +''' + ) + opt('term', 'xterm-kitty', long_text=''' The value of the TERM environment variable to set. Changing this can break many diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 93db1a6f897..f4908c4491a 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -52,6 +52,9 @@ def allow_hyperlinks(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: def allow_remote_control(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['allow_remote_control'] = allow_remote_control(val) + def allow_resize_csi(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + ans['allow_resize_csi'] = to_bool(val) + def background(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['background'] = to_color(val) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index 860c7d29552..c1843b25bcc 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -811,6 +811,19 @@ convert_from_opts_allow_hyperlinks(PyObject *py_opts, Options *opts) { Py_DECREF(ret); } +static void +convert_from_python_allow_resize_csi(PyObject *val, Options *opts) { + opts->allow_resize_csi = PyObject_IsTrue(val); +} + +static void +convert_from_opts_allow_resize_csi(PyObject *py_opts, Options *opts) { + PyObject *ret = PyObject_GetAttrString(py_opts, "allow_resize_csi"); + if (ret == NULL) return; + convert_from_python_allow_resize_csi(ret, opts); + Py_DECREF(ret); +} + static void convert_from_python_macos_option_as_alt(PyObject *val, Options *opts) { opts->macos_option_as_alt = PyLong_AsUnsignedLong(val); @@ -1028,6 +1041,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) { if (PyErr_Occurred()) return false; convert_from_opts_allow_hyperlinks(py_opts, opts); if (PyErr_Occurred()) return false; + convert_from_opts_allow_resize_csi(py_opts, opts); + if (PyErr_Occurred()) return false; convert_from_opts_macos_option_as_alt(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_hide_from_tasks(py_opts, opts); diff --git a/kitty/options/types.py b/kitty/options/types.py index 9555c1f69f7..caad75a59cf 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -50,6 +50,7 @@ 'adjust_line_height', 'allow_hyperlinks', 'allow_remote_control', + 'allow_resize_csi', 'background', 'background_image', 'background_image_layout', @@ -442,6 +443,7 @@ class Options: adjust_line_height: typing.Union[int, float] = 0 allow_hyperlinks: int = 1 allow_remote_control: str = 'n' + allow_resize_csi: bool = False background: Color = Color(red=0, green=0, blue=0) background_image: typing.Optional[str] = None background_image_layout: choices_for_background_image_layout = 'tiled' diff --git a/kitty/state.h b/kitty/state.h index 698695cc224..1a98fdc30fe 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -62,6 +62,7 @@ typedef struct { bool window_alert_on_bell; bool debug_keyboard; bool allow_hyperlinks; + bool allow_resize_csi; monotonic_t resize_debounce_time; MouseShape pointer_shape_when_grabbed; MouseShape default_pointer_shape; From 6b131106c9a64e7fb670b9bf7c3b22c4e81239eb Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 1 Jul 2021 14:35:30 -0500 Subject: [PATCH 2/8] glfw: add function to resize OS window Signed-off-by: Patrick Williams --- kitty/fast_data_types.pyi | 3 +++ kitty/glfw.c | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 97f45a577d2..18324f71c8a 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -747,6 +747,9 @@ def cocoa_window_id(os_window_id: int) -> int: pass +def resize_os_window(os_window_id: int, x: int, y: int) -> None: + pass + def swap_tabs(os_window_id: int, a: int, b: int) -> None: pass diff --git a/kitty/glfw.c b/kitty/glfw.c index 950c1ad341f..a2ecbd8fb63 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1254,6 +1254,26 @@ cocoa_window_id(PyObject UNUSED *self, PyObject *os_wid) { #endif } +static PyObject* +resize_os_window(PyObject UNUSED *self, PyObject UNUSED *args) { + PyObject *w_id; + int x,y; + + if(!PyArg_ParseTuple(args, "Oii", &w_id, &x, &y)) { + log_error("Unable to parse args."); + } + + OSWindow* w = find_os_window(w_id); + if (!w) { + log_error("Cannot find window."); + } + + glfwSetWindowSize(w->handle, x, y); + update_os_window_viewport(w, true); + + Py_RETURN_NONE; +} + static PyObject* get_primary_selection(PYNOARG) { if (glfwGetPrimarySelectionString) { @@ -1426,6 +1446,7 @@ static PyMethodDef module_methods[] = { METHODB(get_primary_selection, METH_NOARGS), METHODB(x11_display, METH_NOARGS), METHODB(x11_window_id, METH_O), + METHODB(resize_os_window, METH_VARARGS), METHODB(set_primary_selection, METH_VARARGS), #ifndef __APPLE__ METHODB(dbus_send_notification, METH_VARARGS), From e400be14eb1cb14942820f77cd8718cffb92a5a0 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 1 Jul 2021 23:41:39 -0500 Subject: [PATCH 3/8] window: wayland: set window geometry in resize Signed-off-by: Patrick Williams --- glfw/wl_window.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/glfw/wl_window.c b/glfw/wl_window.c index f7e329609ad..41679395393 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -872,6 +872,11 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) resizeFramebuffer(window); ensure_csd_resources(window); wl_surface_commit(window->wl.surface); + +#define geometry window->wl.decorations.geometry + debug("Setting window geometry: x=%d y=%d %dx%d\n", geometry.x, geometry.y, geometry.width, geometry.height); + xdg_surface_set_window_geometry(window->wl.xdg.surface, geometry.x, geometry.y, geometry.width, geometry.height); +#undef geometry } } From 9bcbe57b52566971bdb01946703912957e0f4f6f Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 1 Jul 2021 14:36:22 -0500 Subject: [PATCH 4/8] window: handle resize events from escape codes Signed-off-by: Patrick Williams --- kitty/tabs.py | 16 +++++++++++++++- kitty/window.py | 12 ++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/kitty/tabs.py b/kitty/tabs.py index 4a09b09f3db..6d992439f5d 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -21,7 +21,8 @@ from .fast_data_types import ( add_tab, attach_window, detach_window, get_boss, get_options, mark_tab_bar_dirty, next_window_id, remove_tab, remove_window, ring_bell, - set_active_tab, set_active_window, swap_tabs, sync_os_window_title + set_active_tab, set_active_window, swap_tabs, sync_os_window_title, + resize_os_window ) from .layout.base import Layout, Rect from .layout.interface import create_layout_object_for, evict_cached_layouts @@ -198,6 +199,19 @@ def title_changed(self, window: Window) -> None: if tm is not None: tm.title_changed(self) + def resize_from_window(self, window: Window, x: int, y: int) -> None: + if len(self.windows) != 1: + log_error('Unable to resize layout-ed windows.') + return + + if window is not self.active_window: + log_error('Unable to resize inactive window.') + return + + tm = self.tab_manager_ref() + if tm is not None: + resize_os_window(tm.os_window_id, x, y) + def on_bell(self, window: Window) -> None: self.mark_tab_bar_dirty() diff --git a/kitty/window.py b/kitty/window.py index f78c9aa3de5..41c769a5513 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -516,6 +516,18 @@ def set_geometry(self, new_geometry: WindowGeometry) -> None: set_window_render_data(self.os_window_id, self.tab_id, self.id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen, *g[:4]) self.update_effective_padding() + def resize_from_escape(self, cells: bool, x: int, y:int) -> None: + t = self.tabref() + if t is None: + return + + if cells: + cell_width, cell_height = cell_size_for_window(self.os_window_id) + x = x * cell_width + y = y * cell_height + + t.resize_from_window(self, x, y) + def contains(self, x: int, y: int) -> bool: g = self.geometry return g.left <= x <= g.right and g.top <= y <= g.bottom From 3ddcfdd52f2296cb2dca2843c2bcde6dfadfe191 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 1 Jul 2021 14:37:45 -0500 Subject: [PATCH 5/8] parser: add handler for 3 element CSIs Signed-off-by: Patrick Williams --- kitty/parser.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/kitty/parser.c b/kitty/parser.c index 056c2fc84ca..38b1b0a063d 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -109,8 +109,11 @@ _report_params(PyObject *dump_callback, const char *name, int *params, unsigned #define REPORT_COMMAND3(name, x, y) \ Py_XDECREF(PyObject_CallFunction(dump_callback, "sii", #name, (int)x, (int)y)); PyErr_Clear(); -#define GET_MACRO(_1,_2,_3,NAME,...) NAME -#define REPORT_COMMAND(...) GET_MACRO(__VA_ARGS__, REPORT_COMMAND3, REPORT_COMMAND2, REPORT_COMMAND1, SENTINEL)(__VA_ARGS__) +#define REPORT_COMMAND4(name, x, y, z) \ + Py_XDECREF(PyObject_CallFunction(dump_callback, "siii", #name, (int)x, (int)y, (int)z)); PyErr_Clear(); + +#define GET_MACRO(_1,_2,_3,_4,NAME,...) NAME +#define REPORT_COMMAND(...) GET_MACRO(__VA_ARGS__, REPORT_COMMAND4, REPORT_COMMAND3, REPORT_COMMAND2, REPORT_COMMAND1, SENTINEL)(__VA_ARGS__) #define REPORT_VA_COMMAND(...) Py_XDECREF(PyObject_CallFunction(dump_callback, __VA_ARGS__)); PyErr_Clear(); #define REPORT_DRAW(ch) \ @@ -717,6 +720,21 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { name(screen, p1, p2); \ break; +#define CALL_CSI_HANDLER3(name, defval1, defval2, defval3) \ + if (num_params > 3) { \ + REPORT_ERROR("CSI code %s has %u > 3 parameters", csi_letter(code), num_params); \ + break; \ + } \ + p1 = num_params > 0 ? params[0] : defval1; \ + p2 = num_params > 1 ? params[1] : defval2; \ + p3 = num_params > 2 ? params[2] : defval3; \ + NON_NEGATIVE_PARAM(p1); \ + NON_NEGATIVE_PARAM(p2); \ + NON_NEGATIVE_PARAM(p3); \ + REPORT_COMMAND(name, p1, p2, p3); \ + name(screen, p1, p2, p3); \ + break; + #define SET_MODE(func) \ p1 = start_modifier == '?' ? 5 : 0; \ for (i = 0; i < num_params; i++) { \ @@ -737,7 +755,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { char start_modifier = 0, end_modifier = 0; uint32_t *buf = screen->parser_buf, code = screen->parser_buf[screen->parser_buf_pos]; unsigned int num = screen->parser_buf_pos, start, i, num_params=0; - static int params[MAX_PARAMS] = {0}, p1, p2; + static int params[MAX_PARAMS] = {0}, p1, p2, p3; bool private; if (buf[0] == '>' || buf[0] == '<' || buf[0] == '?' || buf[0] == '!' || buf[0] == '=') { start_modifier = (char)screen->parser_buf[0]; From 80dd64c76661507290d8fc0079d2482b782a7f91 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 1 Jul 2021 14:38:25 -0500 Subject: [PATCH 6/8] handle CSI codes for resize Signed-off-by: Patrick Williams --- kitty/parser.c | 2 +- kitty/screen.c | 11 +++++++++++ kitty/screen.h | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/kitty/parser.c b/kitty/parser.c index 38b1b0a063d..b999c427b1b 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -894,7 +894,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { switch(params[0]) { case 4: case 8: - log_error("Escape codes to resize text area are not supported"); + CALL_CSI_HANDLER3(screen_escape_resize, 0, 25, 80); break; case 14: case 16: diff --git a/kitty/screen.c b/kitty/screen.c index 66960c16944..cf8eef7a905 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1576,6 +1576,17 @@ screen_report_size(Screen *self, unsigned int which) { } } +void +screen_escape_resize(Screen *self, unsigned int op, unsigned int lines, unsigned int cols) { + if (OPT(allow_resize_csi)) { + CALLBACK("resize_from_escape", "OII", + op == 8 ? Py_True : Py_False, + cols, lines + ); + } + return; +} + void screen_manipulate_title_stack(Screen *self, unsigned int op, unsigned int which) { CALLBACK("manipulate_title_stack", "OOO", diff --git a/kitty/screen.h b/kitty/screen.h index bce11231633..9156d8e55fa 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -227,6 +227,7 @@ bool screen_open_url(Screen*); void screen_dirty_sprite_positions(Screen *self); void screen_rescale_images(Screen *self); void screen_report_size(Screen *, unsigned int which); +void screen_escape_resize(Screen *, unsigned int which, unsigned int lines, unsigned int cols); void screen_manipulate_title_stack(Screen *, unsigned int op, unsigned int which); void screen_draw_overlay_text(Screen *self, const char *utf8_text); void screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how); From db60c80d565f64f4a4ef576622e7f1895a464441 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 2 Jul 2021 10:00:53 -0500 Subject: [PATCH 7/8] csi: add codes 88 and 89 Add support in the parser for codes 88 and 89, which are intended to be like 8 but only resize the os-window or layout-window respectively. Currently, these behave exactly like 8, but the parsing logic is in place to pick apart the behavioral aspects and pass it into window. Signed-off-by: Patrick Williams --- kitty/parser.c | 2 ++ kitty/screen.c | 10 ++++++++-- kitty/window.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/kitty/parser.c b/kitty/parser.c index b999c427b1b..8eddb97ae43 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -894,6 +894,8 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { switch(params[0]) { case 4: case 8: + case 88: + case 89: CALL_CSI_HANDLER3(screen_escape_resize, 0, 25, 80); break; case 14: diff --git a/kitty/screen.c b/kitty/screen.c index cf8eef7a905..58780e0d37f 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1578,9 +1578,15 @@ screen_report_size(Screen *self, unsigned int which) { void screen_escape_resize(Screen *self, unsigned int op, unsigned int lines, unsigned int cols) { + PyObject *cells, *os_window, *layout_window; + + cells = (op == 4) ? Py_False : Py_True; + os_window = (op == 89) ? Py_False : Py_True; + layout_window = (op == 88) ? Py_False : Py_True; + if (OPT(allow_resize_csi)) { - CALLBACK("resize_from_escape", "OII", - op == 8 ? Py_True : Py_False, + CALLBACK("resize_from_escape", "OOOII", + cells, os_window, layout_window, cols, lines ); } diff --git a/kitty/window.py b/kitty/window.py index 41c769a5513..2b1e80ff93b 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -516,7 +516,7 @@ def set_geometry(self, new_geometry: WindowGeometry) -> None: set_window_render_data(self.os_window_id, self.tab_id, self.id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen, *g[:4]) self.update_effective_padding() - def resize_from_escape(self, cells: bool, x: int, y:int) -> None: + def resize_from_escape(self, cells: bool, os_window: bool, layout_window: bool, x: int, y:int) -> None: t = self.tabref() if t is None: return From d3bc9e999f83e1a306c94daf9c0bc3ce7b673b8d Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 2 Jul 2021 15:25:06 -0500 Subject: [PATCH 8/8] window: handle os-window vs layout resizes Signed-off-by: Patrick Williams --- kitty/tabs.py | 26 ++++++++++++++++---------- kitty/window.py | 14 ++++++++++---- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/kitty/tabs.py b/kitty/tabs.py index 6d992439f5d..2886e6f1fb5 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -199,18 +199,24 @@ def title_changed(self, window: Window) -> None: if tm is not None: tm.title_changed(self) - def resize_from_window(self, window: Window, x: int, y: int) -> None: - if len(self.windows) != 1: - log_error('Unable to resize layout-ed windows.') - return + def resize_from_window(self, window: Window, os_window: bool, layout_window: bool, x: int, y: int, dx_cells: int, dy_cells: int) -> None: + if os_window: + # If there are multiple windows we can only resize if requested + # to modify layouts also. + if len(self.windows) != 1 and not layout_window: + return - if window is not self.active_window: - log_error('Unable to resize inactive window.') - return + tm = self.tab_manager_ref() + if tm is not None: + resize_os_window(tm.os_window_id, x, y) - tm = self.tab_manager_ref() - if tm is not None: - resize_os_window(tm.os_window_id, x, y) + # Tabs will resize as part of window resize event. + + elif layout_window: + if dx_cells: + self.resize_window_by(window.id, dx_cells, True) + if dy_cells: + self.resize_window_by(window.id, dy_cells, False) def on_bell(self, window: Window) -> None: self.mark_tab_bar_dirty() diff --git a/kitty/window.py b/kitty/window.py index 2b1e80ff93b..49f577fcae6 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -521,12 +521,18 @@ def resize_from_escape(self, cells: bool, os_window: bool, layout_window: bool, if t is None: return + cell_width, cell_height = cell_size_for_window(self.os_window_id) + if cells: - cell_width, cell_height = cell_size_for_window(self.os_window_id) - x = x * cell_width - y = y * cell_height + x, y = max(x, 10), max(y, 2) + cells_x, cells_y = x, y + x, y = x * cell_width, y * cell_height + else: + x, y = max(x, cell_width*10), max(y, cell_height*2) + cells_x, cells_y = int(x / cells_width), int(y / cells_height) - t.resize_from_window(self, x, y) + g = self.geometry + t.resize_from_window(self, os_window, layout_window, x, y, cells_x - g.xnum, cells_y - g.ynum) def contains(self, x: int, y: int) -> bool: g = self.geometry