Add an option to control dragging to resize windows

This commit is contained in:
Kovid Goyal 2026-02-26 19:07:47 +05:30
parent ccc0ce5ceb
commit 32d8ebfbb3
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
7 changed files with 50 additions and 17 deletions

View file

@ -164,6 +164,9 @@ Detailed list of changes
- Allow dragging tabs in the tab bar to re-order, move to another OS Window or
detach (:pull:`9296`)
- Allow dragging window borders to resize kitty windows in all the different
layouts, controlled by :opt:`window_drag_tolerance` (:pull:`9447`)
- choose-files kitten: Fix JXL image preview not working (:iss:`9323`)
- Fix tab bar rendering glitches when using :opt:`tab_bar_filter` in some

View file

@ -254,9 +254,9 @@ contains_mouse(Window *w) {
}
static void
border_contains_mouse(BorderRect *br, int tolerance, bool *horizontal, bool *vertical) {
border_contains_mouse(BorderRect *br, double tolerance, bool *horizontal, bool *vertical) {
double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
if ((int)br->px.left - tolerance <= x && x < br->px.right + tolerance && (int)br->px.top - tolerance <= y && y < br->px.bottom + tolerance) {
if ((int)br->px.left - tolerance <= x && x < (int)br->px.right + tolerance && (int)br->px.top - tolerance <= y && y < (int)br->px.bottom + tolerance) {
if (br->px.right - br->px.left > br->px.bottom - br->px.top) *horizontal = true; else *vertical = true;
}
}
@ -906,13 +906,15 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar, int *window_border)
(tab_bar.bottom > central.bottom && w->mouse_y >= central.bottom)
) *in_tab_bar = true;
}
if (in_central && global_state.callback_os_window->num_tabs > 0) {
if (in_central && w->num_tabs > 0) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
if (window_border) {
bool horizontal = false, vertical = false;
double dpi = (w->fonts_data->logical_dpi_x + w->fonts_data->logical_dpi_y) / 2.;
double tolerance = ((long)round((OPT(window_drag_tolerance) * (dpi / 72.0))));
for (unsigned i = 0; i < t->border_rects.num_border_rects && !(horizontal && vertical); i++) {
BorderRect *br = t->border_rects.rect_buf + i;
if (br->is_actual_border) border_contains_mouse(br, 0, &horizontal, &vertical);
if (br->is_actual_border) border_contains_mouse(br, tolerance, &horizontal, &vertical);
}
*window_border = 0;
if (horizontal) *window_border |= 1;
@ -1173,9 +1175,6 @@ mouse_event(const int button, int modifiers, int action) {
mouse_cursor_shape = POINTER_POINTER;
handle_tab_bar_mouse(button, modifiers, action);
debug("handled by tab bar\n");
} else if (w) {
debug("grabbed: %d\n", w->render_data.screen->modes.mouse_tracking_mode != 0);
handle_event(w, button, modifiers, window_idx);
} else if (window_border) {
debug("window border: %d\n", window_border);
w = window_for_event(&window_idx, &in_tab_bar, NULL);
@ -1191,6 +1190,9 @@ mouse_event(const int button, int modifiers, int action) {
if (r == NULL) { PyErr_Print(); return; }
if (PyObject_IsTrue(r)) global_state.active_drag_resize = w->id;
}
} else if (w) {
debug("grabbed: %d\n", w->render_data.screen->modes.mouse_tracking_mode != 0);
handle_event(w, button, modifiers, window_idx);
} else if (button == GLFW_MOUSE_BUTTON_LEFT && global_state.callback_os_window->mouse_button_pressed[button]) {
// initial click, clamp it to the closest window
w = closest_window_for_event(&window_idx);
@ -1200,7 +1202,10 @@ mouse_event(const int button, int modifiers, int action) {
handle_event(w, button, modifiers, window_idx);
clamp_to_window = false;
} else debug("no window for event\n");
} else debug("\n");
} else {
mouse_cursor_shape = DEFAULT_POINTER;
debug("\n");
}
if (mouse_cursor_shape != old_cursor) set_mouse_cursor(mouse_cursor_shape);
}

View file

@ -1457,6 +1457,13 @@
Note that if you want confirmation when closing individual windows,
you can map the :ac:`close_window_with_confirmation` action.
''')
opt('window_drag_tolerance', '2', option_type='float', ctype='double', long_text='''
Control dragging window borders to resize kitty windows. This is the tolerance in pts
for the region around window borders where pressing the left mouse button
will start the dragging of window borders. Use a large negative value such as -200 to disable
dragging of borders.
''')
egr() # }}}
@ -3847,9 +3854,7 @@
'''
)
opt('map_timeout', '0.0',
option_type='positive_float', ctype='time',
long_text='''
opt('map_timeout', '0.0', option_type='positive_float', long_text='''
The default timeout (in seconds) for multi-key mappings and modal keyboard modes.
If you press the first key(s) of a multi-key mapping and don't press the next
key within this timeout, the mapping is cancelled and the mode is exited. A value
@ -3862,8 +3867,7 @@
# This mode will have a 5 second timeout (overrides the global 2 second timeout)
map --new-mode resize --timeout 5.0 kitty_mod+r
'''
)
''')
opt('+action_alias', 'launch_tab launch --type=tab --cwd=current',
option_type='action_alias',

View file

@ -1060,9 +1060,6 @@ def kitten_alias(self, val: str, ans: dict[str, typing.Any]) -> None:
def kitty_mod(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['kitty_mod'] = to_modifiers(val)
def map_timeout(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['map_timeout'] = positive_float(val)
def linux_bell_theme(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['linux_bell_theme'] = str(val)
@ -1123,6 +1120,9 @@ def macos_traditional_fullscreen(self, val: str, ans: dict[str, typing.Any]) ->
def macos_window_resizable(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['macos_window_resizable'] = to_bool(val)
def map_timeout(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['map_timeout'] = positive_float(val)
def mark1_background(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['mark1_background'] = to_color(val)
@ -1472,6 +1472,9 @@ def window_alert_on_bell(self, val: str, ans: dict[str, typing.Any]) -> None:
def window_border_width(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['window_border_width'] = window_border_width(val)
def window_drag_tolerance(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['window_drag_tolerance'] = float(val)
def window_logo_alpha(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['window_logo_alpha'] = unit_float(val)

View file

@ -980,6 +980,19 @@ convert_from_opts_resize_in_steps(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_window_drag_tolerance(PyObject *val, Options *opts) {
opts->window_drag_tolerance = PyFloat_AsDouble(val);
}
static void
convert_from_opts_window_drag_tolerance(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "window_drag_tolerance");
if (ret == NULL) return;
convert_from_python_window_drag_tolerance(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_tab_bar_edge(PyObject *val, Options *opts) {
opts->tab_bar_edge = PyLong_AsLong(val);
@ -1535,6 +1548,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_resize_in_steps(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_window_drag_tolerance(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_tab_bar_edge(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_tab_bar_margin_height(py_opts, opts);

View file

@ -488,6 +488,7 @@
'wheel_scroll_multiplier',
'window_alert_on_bell',
'window_border_width',
'window_drag_tolerance',
'window_logo_alpha',
'window_logo_path',
'window_logo_position',
@ -573,7 +574,6 @@ class Options:
input_delay: int = 3
italic_font: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='auto', axes=(), variable_name=None, features=(), created_from_string='auto')
kitty_mod: int = 5
map_timeout: float = 0.0
linux_bell_theme: str = '__custom'
linux_display_server: choices_for_linux_display_server = 'auto'
listen_on: str = 'none'
@ -589,6 +589,7 @@ class Options:
macos_titlebar_color: int = 0
macos_traditional_fullscreen: bool = False
macos_window_resizable: bool = True
map_timeout: float = 0
mark1_background: Color = Color(152, 211, 203)
mark1_foreground: Color = Color(0, 0, 0)
mark2_background: Color = Color(242, 220, 211)
@ -679,6 +680,7 @@ class Options:
wheel_scroll_multiplier: float = 5.0
window_alert_on_bell: bool = True
window_border_width: tuple[float, str] = (0.5, 'pt')
window_drag_tolerance: float = 2.0
window_logo_alpha: float = 0.5
window_logo_path: str | None = None
window_logo_position: choices_for_window_logo_position = 'bottom-right'

View file

@ -141,6 +141,7 @@ typedef struct Options {
struct { float thickness; int unit; } underline_exclusion;
float box_drawing_scale[4];
double momentum_scroll;
double window_drag_tolerance;
} Options;
typedef struct WindowLogoRenderData {