From 707cb3721214a7385acec049da40c0e511cf1249 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 18 May 2020 16:06:40 +0530 Subject: [PATCH] Have the confirm on close also apply to quitting kitty Use a dedicated API for handling quit requests on macOS rather than a quit canary. Also create a mappable "quit" action for all platforms. --- docs/changelog.rst | 2 ++ glfw/cocoa_init.m | 12 +++++---- glfw/cocoa_platform.h | 3 +++ glfw/glfw.py | 2 ++ kitty/boss.py | 34 ++++++++++++++++++----- kitty/child-monitor.c | 31 ++++++++++----------- kitty/config_data.py | 2 ++ kitty/fast_data_types.pyi | 9 +++++++ kitty/glfw-wrapper.c | 2 ++ kitty/glfw-wrapper.h | 5 ++++ kitty/glfw.c | 57 ++++++++++++++++----------------------- kitty/state.c | 17 +++++++++++- kitty/state.h | 4 +-- 13 files changed, 116 insertions(+), 64 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 162fbc59f..8ce33c635 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,6 +15,8 @@ To update |kitty|, :doc:`follow the instructions `. - Add an option :opt:`confirm_on_os_window_close` to ask for confirmation when closing an OS window with multiple kitty windows. +- Add a new mappable ``quit`` action to quit kitty completely. + - Fix marks using different colors with regexes using only a single color (:pull:`2663`) diff --git a/glfw/cocoa_init.m b/glfw/cocoa_init.m index 5252614b7..1b8dfb325 100644 --- a/glfw/cocoa_init.m +++ b/glfw/cocoa_init.m @@ -424,11 +424,7 @@ display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFla - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { (void)sender; - _GLFWwindow* window; - - for (window = _glfw.windowListHead; window; window = window->next) - _glfwInputWindowCloseRequest(window); - + if (_glfw.ns.quitRequestedCallback) _glfw.ns.quitRequestedCallback(); return NSTerminateCancel; } @@ -589,6 +585,12 @@ GLFWAPI GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunch return previous; } +GLFWAPI GLFWcocoaaplicationquitrequestedfun glfwSetApplicationQuitRequestedCallback(GLFWcocoaaplicationquitrequestedfun callback) { + GLFWcocoaaplicationquitrequestedfun ret = _glfw.ns.quitRequestedCallback; + _glfw.ns.quitRequestedCallback = callback; + return ret; +} + int _glfwPlatformInit(void) { @autoreleasepool { diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index bcad82fce..f47fa12de 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -69,6 +69,7 @@ typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long) typedef bool (* GLFWapplicationshouldhandlereopenfun)(int); typedef void (* GLFWapplicationwillfinishlaunchingfun)(void); typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); +typedef void (* GLFWcocoaaplicationquitrequestedfun)(void); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef VkFlags VkMacOSSurfaceCreateFlagsMVK; @@ -188,6 +189,8 @@ typedef struct _GLFWlibraryNS double restoreCursorPosX, restoreCursorPosY; // The window whose disabled cursor mode is active _GLFWwindow* disabledCursorWindow; + // The application quit requested callback + GLFWcocoaaplicationquitrequestedfun quitRequestedCallback; struct { CFBundleRef bundle; diff --git a/glfw/glfw.py b/glfw/glfw.py index fefd18ce1..050740002 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -202,6 +202,7 @@ def generate_wrappers(glfw_header: str) -> None: GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback) GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback) + GLFWcocoaaplicationquitrequestedfun glfwSetApplicationQuitRequestedCallback(GLFWcocoaaplicationquitrequestedfun callback) void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char* cocoa_key, size_t key_sz, int* cocoa_mods) void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) void* glfwGetX11Display(void) @@ -242,6 +243,7 @@ typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long); typedef bool (* GLFWapplicationshouldhandlereopenfun)(int); typedef void (* GLFWapplicationwillfinishlaunchingfun)(void); typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); +typedef void (* GLFWcocoaaplicationquitrequestedfun)(void); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); diff --git a/kitty/boss.py b/kitty/boss.py index 95fd4cf34..ce94c6345 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -27,12 +27,14 @@ from .constants import ( appname, config_dir, is_macos, kitty_exe, supports_primary_selection ) from .fast_data_types import ( - NO_CLOSE_REQUESTED, ChildMonitor, background_opacity_of, - change_background_opacity, change_os_window_state, cocoa_set_menubar_title, - create_os_window, current_os_window, destroy_global_data, focus_os_window, - get_clipboard_string, global_font_size, mark_os_window_for_close, - os_window_font_size, patch_global_colors, safe_pipe, set_background_image, - set_boss, set_clipboard_string, set_in_sequence_mode, thread_write, + CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, + ChildMonitor, background_opacity_of, change_background_opacity, + change_os_window_state, cocoa_set_menubar_title, create_os_window, + current_application_quit_request, current_os_window, destroy_global_data, + focus_os_window, get_clipboard_string, global_font_size, + mark_os_window_for_close, os_window_font_size, patch_global_colors, + safe_pipe, set_application_quit_request, set_background_image, set_boss, + set_clipboard_string, set_in_sequence_mode, thread_write, toggle_fullscreen, toggle_maximized ) from .keys import get_shortcut, shortcut_matches @@ -761,6 +763,26 @@ class Boss: if action is not None: action() + def quit(self, *args: Any) -> None: + tm = self.active_tab + if not self.opts.confirm_os_window_close or tm is None: + set_application_quit_request(IMPERATIVE_CLOSE_REQUESTED) + return + if current_application_quit_request() == CLOSE_BEING_CONFIRMED: + return + num = 0 + for q in self.os_window_map.values(): + num += q.number_of_windows + self._run_kitten('ask', ['--type=yesno', '--message', _( + 'Are you sure you want to quit kitty, it has {} windows running?').format(num)], + window=tm.active_window, + custom_callback=self.handle_quit_confirmation + ) + set_application_quit_request(CLOSE_BEING_CONFIRMED) + + def handle_quit_confirmation(self, data: Dict[str, Any], *a: Any) -> None: + set_application_quit_request(IMPERATIVE_CLOSE_REQUESTED if data['response'] == 'y' else NO_CLOSE_REQUESTED) + def notify_on_os_window_death(self, address: str) -> None: import socket s = socket.socket(family=socket.AF_UNIX) diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index a66ab2163..b5a395310 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -346,7 +346,9 @@ parse_input(ChildMonitor *self) { } if (UNLIKELY(kill_signal_received)) { - global_state.terminate = true; + global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED; + global_state.has_pending_closes = true; + request_tick_callback(); } else { count = self->count; for (size_t i = 0; i < count; i++) { @@ -861,6 +863,12 @@ close_os_window(ChildMonitor *self, OSWindow *os_window) { static inline bool process_pending_closes(ChildMonitor *self) { + if (global_state.quit_request == CONFIRMABLE_CLOSE_REQUESTED) { + call_boss(quit, ""); + } + if (global_state.quit_request == IMPERATIVE_CLOSE_REQUESTED) { + close_all_windows(); + } bool has_open_windows = false; for (size_t w = global_state.num_os_windows; w > 0; w--) { OSWindow *os_window = global_state.os_windows + w - 1; @@ -886,10 +894,10 @@ process_pending_closes(ChildMonitor *self) { global_state.has_pending_closes = false; #ifdef __APPLE__ if (!OPT(macos_quit_when_last_window_closed)) { - if (!has_open_windows && !application_quit_requested()) has_open_windows = true; + if (!has_open_windows && global_state.quit_request != IMPERATIVE_CLOSE_REQUESTED) has_open_windows = true; } #endif - return has_open_windows; + return !has_open_windows; } #ifdef __APPLE__ @@ -950,23 +958,16 @@ process_global_state(void *data) { cocoa_pending_actions = 0; } #endif - if (global_state.terminate) { - global_state.terminate = false; - close_all_windows(); -#ifdef __APPLE__ - request_application_quit(); -#endif - } report_reaped_pids(); - bool has_open_windows = true; - if (global_state.has_pending_closes) has_open_windows = process_pending_closes(self); - if (has_open_windows) { + bool should_quit = false; + if (global_state.has_pending_closes) should_quit = process_pending_closes(self); + if (should_quit) { + stop_main_loop(); + } else { if (maximum_wait >= 0) { if (maximum_wait == 0) request_tick_callback(); else state_check_timer_enabled = true; } - } else { - stop_main_loop(); } update_main_loop_timer(state_check_timer, MAX(0, maximum_wait), state_check_timer_enabled); } diff --git a/kitty/config_data.py b/kitty/config_data.py index e76094619..eab9b5122 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -781,6 +781,8 @@ Note that this does not currently work on Wayland. o('confirm_os_window_close', 0, option_type=positive_int, long_text=_(''' Ask for confirmation when closing an OS window that has at least this number of kitty windows in it. A value of zero disables confirmation. +This confirmation also applies to requests to quit the entire application (all +OS windows, via the quite action). ''')) # }}} diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index f7457aa3a..50f1bb4db 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -12,6 +12,7 @@ from kitty.options_stub import Options KITTY_VCS_REV: str NO_CLOSE_REQUESTED: int IMPERATIVE_CLOSE_REQUESTED: int +CLOSE_BEING_CONFIRMED: int ERROR_PREFIX: str GLSL_VERSION: int GLFW_IBEAM_CURSOR: int @@ -712,6 +713,14 @@ def mark_os_window_for_close(os_window_id: int, cr_type: int = 2) -> bool: pass +def set_application_quit_request(cr_type: int = 2) -> None: + pass + + +def current_application_quit_request() -> int: + pass + + def global_font_size(val: float = -1.) -> float: pass diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 3833977ee..e0e3eb848 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -386,6 +386,8 @@ load_glfw(const char* path) { *(void **) (&glfwSetApplicationWillFinishLaunching_impl) = dlsym(handle, "glfwSetApplicationWillFinishLaunching"); + *(void **) (&glfwSetApplicationQuitRequestedCallback_impl) = dlsym(handle, "glfwSetApplicationQuitRequestedCallback"); + *(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent"); *(void **) (&glfwCocoaRequestRenderFrame_impl) = dlsym(handle, "glfwCocoaRequestRenderFrame"); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index b3a4a05b5..156e2ecb0 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1586,6 +1586,7 @@ typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long); typedef bool (* GLFWapplicationshouldhandlereopenfun)(int); typedef void (* GLFWapplicationwillfinishlaunchingfun)(void); typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); +typedef void (* GLFWcocoaaplicationquitrequestedfun)(void); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); @@ -2094,6 +2095,10 @@ typedef GLFWapplicationwillfinishlaunchingfun (*glfwSetApplicationWillFinishLaun GFW_EXTERN glfwSetApplicationWillFinishLaunching_func glfwSetApplicationWillFinishLaunching_impl; #define glfwSetApplicationWillFinishLaunching glfwSetApplicationWillFinishLaunching_impl +typedef GLFWcocoaaplicationquitrequestedfun (*glfwSetApplicationQuitRequestedCallback_func)(GLFWcocoaaplicationquitrequestedfun); +GFW_EXTERN glfwSetApplicationQuitRequestedCallback_func glfwSetApplicationQuitRequestedCallback_impl; +#define glfwSetApplicationQuitRequestedCallback glfwSetApplicationQuitRequestedCallback_impl + typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, char*, size_t, int*); GFW_EXTERN glfwGetCocoaKeyEquivalent_func glfwGetCocoaKeyEquivalent_impl; #define glfwGetCocoaKeyEquivalent glfwGetCocoaKeyEquivalent_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 9f4d14842..2eb1e659e 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -161,14 +161,6 @@ window_close_callback(GLFWwindow* window) { global_state.callback_os_window = NULL; } -#ifdef __APPLE__ -static void -application_quit_canary_close_requested(GLFWwindow *window UNUSED) { - global_state.has_pending_closes = true; - request_tick_callback(); -} -#endif - static void window_occlusion_callback(GLFWwindow *window, bool occluded UNUSED) { if (!set_callback_window(window)) return; @@ -340,6 +332,16 @@ drop_callback(GLFWwindow *w, const char *mime, const char *data, size_t sz) { #undef RETURN } +#ifdef __APPLE__ +static void +application_quit_requested_callback(void) { + if (global_state.quit_request == NO_CLOSE_REQUESTED) { + global_state.has_pending_closes = true; + global_state.quit_request = CONFIRMABLE_CLOSE_REQUESTED; + request_tick_callback(); + } +} +#endif // }}} void @@ -457,6 +459,8 @@ toggle_maximized_for_os_window(OSWindow *w) { #ifdef __APPLE__ +static GLFWwindow *apple_preserve_common_context = NULL; + static int filter_option(int key UNUSED, int mods, unsigned int native_key UNUSED, unsigned long flags) { if ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) { @@ -466,8 +470,6 @@ filter_option(int key UNUSED, int mods, unsigned int native_key UNUSED, unsigned return 0; } -static GLFWwindow *application_quit_canary = NULL; - static bool on_application_reopen(int has_visible_windows) { if (has_visible_windows) return true; @@ -526,8 +528,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true); glfwSetApplicationShouldHandleReopen(on_application_reopen); glfwSetApplicationWillFinishLaunching(cocoa_create_global_menu); + glfwSetApplicationQuitRequestedCallback(application_quit_requested_callback); #endif - } #ifndef __APPLE__ @@ -547,15 +549,13 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { // The temp window is used to get the DPI. glfwWindowHint(GLFW_VISIBLE, false); GLFWwindow *common_context = global_state.num_os_windows ? global_state.os_windows[0].handle : NULL; -#ifdef __APPLE__ - if (is_first_window && !application_quit_canary) { - application_quit_canary = glfwCreateWindow(100, 200, "quit_canary", NULL, NULL); - glfwSetWindowCloseCallback(application_quit_canary, application_quit_canary_close_requested); - } - if (!common_context) common_context = application_quit_canary; -#endif - GLFWwindow *temp_window = NULL; +#ifdef __APPLE__ + if (!apple_preserve_common_context) { + apple_preserve_common_context = glfwCreateWindow(100, 200, "kitty", NULL, common_context); + } + if (!common_context) common_context = apple_preserve_common_context; +#endif if (!global_state.is_wayland) { // On Wayland windows dont get a content scale until they receive an enterEvent anyway // which wont happen until the event loop ticks, so using a temp window is useless. @@ -758,21 +758,6 @@ focus_os_window(OSWindow *w, bool also_raise) { } } -#ifdef __APPLE__ -bool -application_quit_requested() { - return !application_quit_canary || glfwWindowShouldClose(application_quit_canary); -} - -void -request_application_quit() { - if (application_quit_canary) { - global_state.has_pending_closes = true; - glfwSetWindowShouldClose(application_quit_canary, true); - } -} -#endif - // Global functions {{{ static void error_callback(int error, const char* description) { @@ -1170,6 +1155,10 @@ run_main_loop(tick_callback_fun cb, void* cb_data) { void stop_main_loop(void) { +#ifdef __APPLE__ + if (apple_preserve_common_context) glfwDestroyWindow(apple_preserve_common_context); + apple_preserve_common_context = NULL; +#endif glfwStopMainLoop(); } diff --git a/kitty/state.c b/kitty/state.c index 5581f801d..30090a195 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -462,7 +462,6 @@ mark_os_window_for_close(OSWindow* w, CloseRequest cr) { - // Python API {{{ #define PYWRAP0(name) static PyObject* py##name(PYNOARG) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) @@ -834,6 +833,19 @@ PYWRAP1(mark_os_window_for_close) { Py_RETURN_FALSE; } +PYWRAP1(set_application_quit_request) { + CloseRequest cr = IMPERATIVE_CLOSE_REQUESTED; + PA("|i", &cr); + global_state.quit_request = cr; + global_state.has_pending_closes = true; + request_tick_callback(); + Py_RETURN_NONE; +} + +PYWRAP0(current_application_quit_request) { + return Py_BuildValue("i", global_state.quit_request); +} + PYWRAP1(focus_os_window) { id_type os_window_id; int also_raise = 1; @@ -1141,6 +1153,8 @@ static PyMethodDef module_methods[] = { MW(cell_size_for_window, METH_VARARGS), MW(os_window_has_background_image, METH_VARARGS), MW(mark_os_window_for_close, METH_VARARGS), + MW(set_application_quit_request, METH_VARARGS), + MW(current_application_quit_request, METH_NOARGS), MW(set_titlebar_color, METH_VARARGS), MW(focus_os_window, METH_VARARGS), MW(mark_tab_bar_dirty, METH_O), @@ -1192,6 +1206,7 @@ init_state(PyObject *module) { PyModule_AddObject(module, "Region", (PyObject *) &RegionType); PyModule_AddIntConstant(module, "IMPERATIVE_CLOSE_REQUESTED", IMPERATIVE_CLOSE_REQUESTED); PyModule_AddIntConstant(module, "NO_CLOSE_REQUESTED", NO_CLOSE_REQUESTED); + PyModule_AddIntConstant(module, "CLOSE_BEING_CONFIRMED", CLOSE_BEING_CONFIRMED); if (Py_AtExit(finalize) != 0) { PyErr_SetString(PyExc_RuntimeError, "Failed to register the state at exit handler"); return false; diff --git a/kitty/state.h b/kitty/state.h index 74e9aa7f1..7a6107feb 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -197,7 +197,6 @@ typedef struct { OSWindow *os_windows; size_t num_os_windows, capacity; OSWindow *callback_os_window; - bool terminate; bool is_wayland; bool has_render_frames; bool debug_rendering, debug_font_fallback; @@ -207,6 +206,7 @@ typedef struct { double font_sz_in_pts; struct { double x, y; } default_dpi; id_type active_drag_in_window; + CloseRequest quit_request; } GlobalState; extern GlobalState global_state; @@ -264,8 +264,6 @@ typedef enum { NEW_TAB_WITH_WD = 8 } CocoaPendingAction; void set_cocoa_pending_action(CocoaPendingAction action, const char*); -bool application_quit_requested(void); -void request_application_quit(void); #endif void request_frame_render(OSWindow *w); void request_tick_callback(void);