Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OS-window resizing #3788

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions glfw/wl_window.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
10 changes: 10 additions & 0 deletions kittens/query_terminal/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
3 changes: 3 additions & 0 deletions kitty/fast_data_types.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 21 additions & 0 deletions kitty/glfw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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),
Expand Down
9 changes: 9 additions & 0 deletions kitty/options/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions kitty/options/parse.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions kitty/options/to-c-generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions kitty/options/types.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 24 additions & 4 deletions kitty/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down Expand Up @@ -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++) { \
Expand All @@ -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];
Expand Down Expand Up @@ -876,7 +894,9 @@ 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");
case 88:
case 89:
CALL_CSI_HANDLER3(screen_escape_resize, 0, 25, 80);
break;
case 14:
case 16:
Expand Down
17 changes: 17 additions & 0 deletions kitty/screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,23 @@ 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", "OOOII",
cells, os_window, layout_window,
cols, lines
);
}
return;
}

void
screen_manipulate_title_stack(Screen *self, unsigned int op, unsigned int which) {
CALLBACK("manipulate_title_stack", "OOO",
Expand Down
1 change: 1 addition & 0 deletions kitty/screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions kitty/state.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
22 changes: 21 additions & 1 deletion kitty/tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -198,6 +199,25 @@ def title_changed(self, window: Window) -> None:
if tm is not None:
tm.title_changed(self)

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

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()

Expand Down
18 changes: 18 additions & 0 deletions kitty/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,24 @@ 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, os_window: bool, layout_window: bool, x: int, y:int) -> None:
t = self.tabref()
if t is None:
return

cell_width, cell_height = cell_size_for_window(self.os_window_id)

if cells:
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)

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
return g.left <= x <= g.right and g.top <= y <= g.bottom
Expand Down