From 0f256e2724cdee213f05a0e6d1bb32c23b09885c Mon Sep 17 00:00:00 2001 From: mcrmck Date: Thu, 5 Mar 2026 01:42:49 -0500 Subject: [PATCH] Double-click window title bar to rename --- kitty/boss.py | 4 ++++ kitty/mouse.c | 20 +++++++++++++++++--- kitty/tabs.py | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index 0d3c97013..4c22e1c0c 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1385,6 +1385,10 @@ class Boss: run_update_check(get_options().update_check_interval * 60 * 60) self.update_check_started = True + def handle_window_title_bar_mouse(self, os_window_id: int, window_id: int, button: int, modifiers: int, action: int) -> None: + if tm := self.os_window_map.get(os_window_id): + tm.handle_window_title_bar_mouse(window_id, button, modifiers, action) + def handle_tab_bar_mouse(self, os_window_id: int, x: float, y: float, button: int, modifiers: int, action: int) -> None: if tm := self.os_window_map.get(os_window_id): tm.handle_tab_bar_mouse(x, y, button, modifiers, action) diff --git a/kitty/mouse.c b/kitty/mouse.c index 32c2db1e1..813996e14 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -890,6 +890,14 @@ HANDLER(handle_event) { } } +static void +handle_window_title_bar_mouse(Window *w, int button, int modifiers, int action) { + OSWindow *osw = global_state.callback_os_window; + if (osw && button > -1) { + call_boss(handle_window_title_bar_mouse, "KKiii", osw->id, w->id, button, modifiers, action); + } +} + static void handle_tab_bar_mouse(int button, int modifiers, int action) { set_currently_hovered_window(0, modifiers); @@ -1035,9 +1043,11 @@ focus_in_event(void) { void update_mouse_pointer_shape(void) { mouse_cursor_shape = TEXT_POINTER; - MouseRegion r = mouse_region(false, false); + MouseRegion r = mouse_region(false, true); if (r.in_tab_bar) { mouse_cursor_shape = POINTER_POINTER; + } else if (r.in_title_bar) { + mouse_cursor_shape = POINTER_POINTER; } else if (r.window) { if (handle_scrollbar_mouse(r.window, -1, MOVE, 0)) { mouse_cursor_shape = scrollbar_drag_mouse_cursor; @@ -1075,7 +1085,7 @@ enter_event(int modifiers) { MouseRegion r = mouse_region(false, false); Window *w = r.window; set_currently_hovered_window(w ? w->id : 0, modifiers); - if (!w || r.in_tab_bar) return; + if (!w || r.in_tab_bar || r.in_title_bar) return; if (handle_scrollbar_mouse(w, -1, MOVE, modifiers)) return; @@ -1250,13 +1260,17 @@ mouse_event(const int button, int modifiers, int action) { } return; } - MouseRegion r = mouse_region(true, false); + MouseRegion r = mouse_region(true, true); set_currently_hovered_window(w ? w->id : 0, modifiers); if (r.in_tab_bar || global_state.tab_being_dragged.id) { mouse_cursor_shape = POINTER_POINTER; handle_tab_bar_mouse(button, modifiers, action); debug("handled by tab bar\n"); + } else if (r.in_title_bar && r.window) { + mouse_cursor_shape = POINTER_POINTER; + handle_window_title_bar_mouse(r.window, button, modifiers, action); + debug("handled by window title bar\n"); } else if (r.window_border) { debug("window border: %s window id: %llu\n", border_name(r.window_border), w ? w->id : 0); if (r.window_border & LEFT_EDGE) { diff --git a/kitty/tabs.py b/kitty/tabs.py index 7cb3bdf07..42cb4134d 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -1126,6 +1126,7 @@ class TabManager: # {{{ self.wm_class = wm_class self.created_in_session_name = startup_session.session_name if startup_session else '' self.recent_mouse_events: Deque[TabMouseEvent] = deque() + self.recent_title_bar_mouse_events: Deque[TabMouseEvent] = deque() self.wm_name = wm_name self.args = args self.tab_bar_hidden = get_options().tab_bar_style == 'hidden' @@ -1750,6 +1751,26 @@ class TabManager: # {{{ if len(self.recent_mouse_events) > 5: self.recent_mouse_events.popleft() + def handle_window_title_bar_mouse(self, window_id: int, button: int, modifiers: int, action: int) -> None: + now = monotonic() + if button == GLFW_MOUSE_BUTTON_LEFT and action == GLFW_RELEASE and len(self.recent_title_bar_mouse_events) > 2: + ci = get_click_interval() + prev, prev2 = self.recent_title_bar_mouse_events[-1], self.recent_title_bar_mouse_events[-2] + if ( + prev.button == button and prev2.button == button and + prev.action == GLFW_PRESS and prev2.action == GLFW_RELEASE and + prev.tab_id == window_id and prev2.tab_id == window_id and + now - prev.at <= ci and now - prev2.at <= 2 * ci + ): # double click on window title bar + w = get_boss().window_id_map.get(window_id) + if w is not None: + w.set_window_title() + self.recent_title_bar_mouse_events.clear() + return + self.recent_title_bar_mouse_events.append(TabMouseEvent(button, modifiers, action, now, window_id)) + if len(self.recent_title_bar_mouse_events) > 5: + self.recent_title_bar_mouse_events.popleft() + def update_progress(self) -> None: self.num_of_windows_with_progress = 0 self.total_progress = 0