From a8693e45efc0b56b01cab2b46563dc32dc30774f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 21 Apr 2025 13:39:16 +0530 Subject: [PATCH] Nicer error message when running panel kitten on a compositor that does not support layer shell --- glfw/glfw.py | 1 + glfw/wl_window.c | 2 ++ kitty/fast_data_types.pyi | 1 + kitty/glfw-wrapper.c | 3 +++ kitty/glfw-wrapper.h | 4 ++++ kitty/glfw.c | 16 ++++++++++++++++ kitty/main.py | 5 +++++ 7 files changed, 32 insertions(+) diff --git a/glfw/glfw.py b/glfw/glfw.py index 34665dd6b..b31a41f71 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -323,6 +323,7 @@ def generate_wrappers(glfw_header: str) -> None: void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data) bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color) void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) + bool glfwWaylandIsLayerShellSupported(void) bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle) bool glfwWaylandBeep(GLFWwindow *handle) GLFWLayerShellConfig* glfwWaylandLayerShellConfig(GLFWwindow *handle) diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 749bfbc68..018105366 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -2834,6 +2834,8 @@ GLFWAPI GLFWLayerShellConfig* glfwWaylandLayerShellConfig(GLFWwindow *handle) { return &((_GLFWwindow*)handle)->wl.layer_shell.config; } +GLFWAPI bool glfwWaylandIsLayerShellSupported(void) { return _glfw.wl.zwlr_layer_shell_v1 != NULL; } + GLFWAPI bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle) { return handle != NULL && ((_GLFWwindow*)handle)->wl.window_fully_created; } void diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index c5c09133b..552b82144 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1715,6 +1715,7 @@ def base64_decode_into(src: Union[str, ReadableBuffer], output: WriteableBuffer) def cocoa_recreate_global_menu() -> None: ... def cocoa_clear_global_shortcuts() -> None: ... def update_pointer_shape(os_window_id: int) -> None: ... +def is_layer_shell_supported() -> bool: ... def os_window_focus_counters() -> Dict[int, int]: ... def find_in_memoryview(buf: Union[bytes, memoryview, bytearray], chr: int) -> int: ... @overload diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 2e12750a8..a7bad3107 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -485,6 +485,9 @@ load_glfw(const char* path) { *(void **) (&glfwWaylandRedrawCSDWindowTitle_impl) = dlsym(handle, "glfwWaylandRedrawCSDWindowTitle"); if (glfwWaylandRedrawCSDWindowTitle_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwWaylandIsLayerShellSupported_impl) = dlsym(handle, "glfwWaylandIsLayerShellSupported"); + if (glfwWaylandIsLayerShellSupported_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwWaylandIsWindowFullyCreated_impl) = dlsym(handle, "glfwWaylandIsWindowFullyCreated"); if (glfwWaylandIsWindowFullyCreated_impl == NULL) dlerror(); // clear error indicator diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 248260bab..3b39f4dbd 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -2340,6 +2340,10 @@ typedef void (*glfwWaylandRedrawCSDWindowTitle_func)(GLFWwindow*); GFW_EXTERN glfwWaylandRedrawCSDWindowTitle_func glfwWaylandRedrawCSDWindowTitle_impl; #define glfwWaylandRedrawCSDWindowTitle glfwWaylandRedrawCSDWindowTitle_impl +typedef bool (*glfwWaylandIsLayerShellSupported_func)(void); +GFW_EXTERN glfwWaylandIsLayerShellSupported_func glfwWaylandIsLayerShellSupported_impl; +#define glfwWaylandIsLayerShellSupported glfwWaylandIsLayerShellSupported_impl + typedef bool (*glfwWaylandIsWindowFullyCreated_func)(GLFWwindow*); GFW_EXTERN glfwWaylandIsWindowFullyCreated_func glfwWaylandIsWindowFullyCreated_impl; #define glfwWaylandIsWindowFullyCreated glfwWaylandIsWindowFullyCreated_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index f1b97dae7..c0486f2ef 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1211,6 +1211,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { &get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &optional_window_state, &load_programs, &optional_x, &optional_y, &disallow_override_title, &layer_shell_config)) return NULL; bool is_layer_shell = false; if (layer_shell_config && layer_shell_config != Py_None && global_state.is_wayland) { + if (!glfwWaylandIsLayerShellSupported()) { + PyErr_SetString(PyExc_RuntimeError, "The Wayland compositor does not support the layer shell protocol."); + return NULL; + } is_layer_shell = true; } else { if (optional_window_state && optional_window_state != Py_None) { if (!PyLong_Check(optional_window_state)) { PyErr_SetString(PyExc_TypeError, "window_state must be an int"); return NULL; } window_state = (int) PyLong_AsLong(optional_window_state); } @@ -2407,6 +2411,17 @@ make_x11_window_a_dock_window(PyObject *self UNUSED, PyObject *args UNUSED) { Py_RETURN_NONE; } +static PyObject* +is_layer_shell_supported(PyObject *self UNUSED, PyObject *args UNUSED) { +#ifdef __APPLE__ + Py_RETURN_FALSE; +#else + if (!global_state.is_wayland) Py_RETURN_FALSE; + return Py_NewRef(glfwWaylandIsLayerShellSupported() ? Py_True : Py_False); +#endif +} + + // Boilerplate {{{ static PyMethodDef module_methods[] = { @@ -2428,6 +2443,7 @@ static PyMethodDef module_methods[] = { METHODB(x11_display, METH_NOARGS), METHODB(wayland_compositor_data, METH_NOARGS), METHODB(get_click_interval, METH_NOARGS), + METHODB(is_layer_shell_supported, METH_NOARGS), METHODB(x11_window_id, METH_O), METHODB(make_x11_window_a_dock_window, METH_VARARGS), METHODB(strip_csi, METH_O), diff --git a/kitty/main.py b/kitty/main.py index 99c0b7083..9b4dbc313 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -39,6 +39,7 @@ from .fast_data_types import ( free_font_data, glfw_init, glfw_terminate, + is_layer_shell_supported, load_png_data, mask_kitty_signals_process_wide, run_at_exit_cleanup_functions, @@ -229,6 +230,10 @@ def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = (), else: global_shortcuts = {} set_window_icon() + if _is_panel_kitten and is_wayland() and not is_layer_shell_supported(): + from .debug_config import compositor_name + raise SystemExit( + f'Cannot create panels as the layer shell protocol is not supported by the compositor: {compositor_name()}') with cached_values_for(run_app.cached_values_name) as cached_values: startup_sessions = tuple(create_sessions(opts, args, default_session=opts.startup_session))