diff --git a/docs/changelog.rst b/docs/changelog.rst index 8f000bd0c..a5d011570 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -72,6 +72,8 @@ Detailed list of changes - When running a shell for ``--hold`` set the env variable ``KITTY_HOLD=1`` to allow users to customize what happens (:disc:`6587`) +- When multiple confirmable close requests are made focus the existing close confirmation window instead of opening a new one for each request (:iss:`6601`) + 0.29.2 [2023-07-27] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/boss.py b/kitty/boss.py index 2498cbe8a..a32d5187f 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -942,7 +942,7 @@ def confirm( window: Optional[Window] = None, # the window associated with the confirmation confirm_on_cancel: bool = False, # on closing window confirm_on_accept: bool = True, # on pressing enter - ) -> None: + ) -> Window: result: bool = False def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None: @@ -952,10 +952,12 @@ def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None: def on_popup_overlay_removal(wid: int, boss: Boss) -> None: callback(result, *args) - self.run_kitten_with_metadata( + w = self.run_kitten_with_metadata( 'ask', ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n'], window=window, custom_callback=callback_, action_on_removal=on_popup_overlay_removal, default_data={'response': 'y' if confirm_on_cancel else 'n'}) + assert isinstance(w, Window) + return w def choose( self, msg: str, # can contain newlines and ANSI formatting @@ -1029,20 +1031,27 @@ def confirm_tab_close(self, tab: Tab) -> None: tm = tab.tab_manager_ref() if tm is not None: tm.set_active_tab(tab) - self.confirm(ngettext('Are you sure you want to close this tab, it has one window running?', + if tab.confirm_close_window_id and tab.confirm_close_window_id in self.window_id_map: + w = self.window_id_map[tab.confirm_close_window_id] + if w in tab: + tab.set_active_window(w) + return + w = self.confirm(ngettext('Are you sure you want to close this tab, it has one window running?', 'Are you sure you want to close this tab, it has {} windows running?', num).format(num), self.handle_close_tab_confirmation, tab.id, window=tab.active_window, ) + tab.confirm_close_window_id = w.id def handle_close_tab_confirmation(self, confirmed: bool, tab_id: int) -> None: - if not confirmed: - return for tab in self.all_tabs: if tab.id == tab_id: + tab.confirm_close_window_id = 0 break else: return + if not confirmed: + return self.close_tab_no_confirm(tab) def close_tab_no_confirm(self, tab: Tab) -> None: @@ -1619,15 +1628,25 @@ def confirm_os_window_close(self, os_window_id: int) -> None: self.mark_os_window_for_close(os_window_id) return if tm is not None: - w = tm.active_window - self.confirm( + if tm.confirm_close_window_id and tm.confirm_close_window_id in self.window_id_map: + cw = self.window_id_map[tm.confirm_close_window_id] + ctab = cw.tabref() + if ctab is not None and ctab in tm and cw in ctab: + tm.set_active_tab(ctab) + ctab.set_active_window(cw) + return + w = self.confirm( ngettext('Are you sure you want to close this OS window, it has one window running?', 'Are you sure you want to close this OS window, it has {} windows running', num).format(num), self.handle_close_os_window_confirmation, os_window_id, - window=w, + window=tm.active_window, ) + tm.confirm_close_window_id = w.id def handle_close_os_window_confirmation(self, confirmed: bool, os_window_id: int) -> None: + tm = self.os_window_map.get(os_window_id) + if tm is not None: + tm.confirm_close_window_id = 0 if confirmed: self.mark_os_window_for_close(os_window_id) else: @@ -1646,6 +1665,8 @@ def on_os_window_closed(self, os_window_id: int, viewport_width: int, viewport_h if action is not None: action() + quit_confirmation_window_id: int = 0 + @ac('win', 'Quit, closing all windows') def quit(self, *args: Any) -> None: tm = self.active_tab @@ -1658,17 +1679,29 @@ def quit(self, *args: Any) -> None: set_application_quit_request(IMPERATIVE_CLOSE_REQUESTED) return if current_application_quit_request() == CLOSE_BEING_CONFIRMED: + if self.quit_confirmation_window_id and self.quit_confirmation_window_id in self.window_id_map: + w = self.window_id_map[self.quit_confirmation_window_id] + tab = w.tabref() + if tab is not None: + ctm = tab.tab_manager_ref() + if ctm is not None and tab in ctm and w in tab: + focus_os_window(ctm.os_window_id) + ctm.set_active_tab(tab) + tab.set_active_window(w) + return return assert tm is not None - self.confirm( + w = self.confirm( ngettext('Are you sure you want to quit kitty, it has one window running?', 'Are you sure you want to quit kitty, it has {} windows running?', num).format(num), self.handle_quit_confirmation, window=tm.active_window, ) + self.quit_confirmation_window_id = w.id set_application_quit_request(CLOSE_BEING_CONFIRMED) def handle_quit_confirmation(self, confirmed: bool) -> None: + self.quit_confirmation_window_id = 0 set_application_quit_request(IMPERATIVE_CLOSE_REQUESTED if confirmed else NO_CLOSE_REQUESTED) def notify_on_os_window_death(self, address: str) -> None: diff --git a/kitty/glfw.c b/kitty/glfw.c index 33ce3a3c8..f00cc9344 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -266,11 +266,9 @@ window_pos_callback(GLFWwindow* window, int x UNUSED, int y UNUSED) { static void window_close_callback(GLFWwindow* window) { if (!set_callback_window(window)) return; - if (global_state.callback_os_window->close_request == NO_CLOSE_REQUESTED) { - global_state.callback_os_window->close_request = CONFIRMABLE_CLOSE_REQUESTED; - global_state.has_pending_closes = true; - request_tick_callback(); - } + global_state.callback_os_window->close_request = CONFIRMABLE_CLOSE_REQUESTED; + global_state.has_pending_closes = true; + request_tick_callback(); glfwSetWindowShouldClose(window, false); global_state.callback_os_window = NULL; } diff --git a/kitty/tabs.py b/kitty/tabs.py index 79aed620a..5f5f274bb 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -124,6 +124,7 @@ class Tab: # {{{ active_bg: Optional[int] = None inactive_fg: Optional[int] = None inactive_bg: Optional[int] = None + confirm_close_window_id: int = 0 def __init__( self, @@ -848,6 +849,8 @@ def make_active(self) -> None: class TabManager: # {{{ + confirm_close_window_id: int = 0 + def __init__(self, os_window_id: int, args: CLIOptions, wm_class: str, wm_name: str, startup_session: Optional[SessionType] = None): self.os_window_id = os_window_id self.wm_class = wm_class