From 88f4c829eb4ab4d4412da463199781733e4bc33b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 May 2025 15:29:37 +0530 Subject: [PATCH] Improve handling of output names Now can use panel --output-name list to list available outputs. Also, --output-name works on macOS --- glfw/cocoa_window.m | 15 +++++++++++++++ glfw/glfw3.h | 3 ++- glfw/internal.h | 2 +- glfw/monitor.c | 15 +++++++++++++-- glfw/wl_monitor.c | 24 ++++++++++++------------ glfw/wl_platform.h | 1 - glfw/wl_window.c | 2 +- kittens/quick_access_terminal/main.go | 6 +++--- kittens/quick_access_terminal/main.py | 3 ++- kitty/fast_data_types.pyi | 1 + kitty/glfw-wrapper.c | 3 +++ kitty/glfw-wrapper.h | 6 +++++- kitty/glfw.c | 18 ++++++++++++++++++ kitty/main.py | 22 ++++++++++++++++++++++ kitty/simple_cli_definitions.py | 3 ++- 15 files changed, 100 insertions(+), 24 deletions(-) diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 1f5707b0c..6d7f99dbf 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1945,6 +1945,17 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) return &window->ns.layer_shell.config; } +static NSScreen* +screen_for_name(const char *name) { + int count = 0; + GLFWmonitor **monitors = glfwGetMonitors(&count); + for (int i = 0; i < count; i++) { + const char *q = glfwGetMonitorName(monitors[i]); + if (q && strcmp(q, name) == 0) return ((_GLFWmonitor*)monitors[i])->ns.screen; + } + return NULL; +} + bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) { #define config window->ns.layer_shell.config @@ -1975,6 +1986,10 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) // HACK: Changing the style mask can cause the first responder to be cleared [nswindow makeFirstResponder:window->ns.view]; NSScreen *screen = screen_for_window_center(window); + if (config.output_name[0]) { + NSScreen *q = screen_for_name(config.output_name); + if (q) screen = q; + } unsigned cell_width, cell_height; double left_edge_spacing, top_edge_spacing, right_edge_spacing, bottom_edge_spacing; float xscale = (float)config.expected.xscale, yscale = (float)config.expected.yscale; _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 989e48b5b..53ac074f2 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1314,7 +1314,7 @@ typedef struct GLFWLayerShellConfig { int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; } previous; bool was_toggled_to_fullscreen; - char output_name[64]; + char output_name[128]; GLFWFocusPolicy focus_policy; unsigned x_size_in_cells, x_size_in_pixels; unsigned y_size_in_cells, y_size_in_pixels; @@ -2385,6 +2385,7 @@ GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* monitor, float* xscale, flo * @ingroup monitor */ GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* monitor); +GLFWAPI const char* glfwGetMonitorDescription(GLFWmonitor* monitor); /*! @brief Sets the user pointer of the specified monitor. * diff --git a/glfw/internal.h b/glfw/internal.h index 0d080204d..f476d650f 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -490,7 +490,7 @@ struct _GLFWwindow // struct _GLFWmonitor { - char* name; + const char *name, *description; void* userPointer; // Physical dimensions in millimeters. diff --git a/glfw/monitor.c b/glfw/monitor.c index 423d2c83b..c3c10534a 100644 --- a/glfw/monitor.c +++ b/glfw/monitor.c @@ -186,7 +186,8 @@ void _glfwFreeMonitor(_GLFWmonitor* monitor) _glfwFreeGammaArrays(&monitor->currentRamp); free(monitor->modes); - free(monitor->name); + free((void*)monitor->name); + free((void*)monitor->description); free(monitor); } @@ -393,9 +394,19 @@ GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* handle) assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); - return monitor->name; + return monitor->name ? monitor->name : ""; } +GLFWAPI const char* glfwGetMonitorDescription(GLFWmonitor* handle) +{ + _GLFWmonitor* monitor = (_GLFWmonitor*) handle; + assert(monitor != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + return monitor->description ? monitor->description : ""; +} + + GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* handle, void* pointer) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; diff --git a/glfw/wl_monitor.c b/glfw/wl_monitor.c index 6d96e1333..f58cb3efb 100644 --- a/glfw/wl_monitor.c +++ b/glfw/wl_monitor.c @@ -41,20 +41,15 @@ static void outputHandleGeometry(void* data, int32_t physicalWidth, int32_t physicalHeight, int32_t subpixel UNUSED, - const char* make, - const char* model, + const char* make UNUSED, + const char* model UNUSED, int32_t transform UNUSED) { struct _GLFWmonitor *monitor = data; - char name[1024]; - monitor->wl.x = x; monitor->wl.y = y; monitor->widthMM = physicalWidth; monitor->heightMM = physicalHeight; - - snprintf(name, sizeof(name), "%s %s", make, model); - monitor->name = _glfw_strdup(name); } static void outputHandleMode(void* data, @@ -105,15 +100,20 @@ static void outputHandleName(void* data, struct wl_output* output UNUSED, const char* name) { struct _GLFWmonitor *monitor = data; - if (name) strncpy(monitor->wl.friendly_name, name, sizeof(monitor->wl.friendly_name)-1); + if (name) { + if (monitor->name) free((void*)monitor->name); + monitor->name = _glfw_strdup(name); + } } static void outputHandleDescription(void* data, struct wl_output* output UNUSED, const char* description) { struct _GLFWmonitor *monitor = data; - if (description) strncpy(monitor->wl.description, description, sizeof(monitor->wl.description)-1); - + if (description) { + if (monitor->description) free((void*)monitor->description); + monitor->description = _glfw_strdup(description); + } } static const struct wl_output_listener outputListener = { @@ -142,8 +142,8 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version) return; } - // The actual name of this output will be set in the geometry handler. - monitor = _glfwAllocMonitor(NULL, 0, 0); + // The actual name of this output will be set in the handlers. + monitor = _glfwAllocMonitor("unnamed", 0, 0); output = wl_registry_bind(_glfw.wl.registry, name, diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 7990641aa..0b4a3a9bf 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -406,7 +406,6 @@ typedef struct _GLFWmonitorWayland { struct wl_output* output; uint32_t name; - char friendly_name[64], description[64]; int currentMode; int x; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 4ff1c2266..0363c1f0f 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -971,7 +971,7 @@ find_output_by_name(const char* name) { if (!name || !name[0]) return NULL; for (int i = 0; i < _glfw.monitorCount; i++) { _GLFWmonitor *m = _glfw.monitors[i]; - if (strcmp(m->wl.friendly_name, name) == 0) return m->wl.output; + if (strcmp(m->name, name) == 0) return m->wl.output; } return NULL; } diff --git a/kittens/quick_access_terminal/main.go b/kittens/quick_access_terminal/main.go index 4fe3329a9..05abe8a04 100644 --- a/kittens/quick_access_terminal/main.go +++ b/kittens/quick_access_terminal/main.go @@ -66,9 +66,9 @@ func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) { argv = append(argv, fmt.Sprintf("--override=background_opacity=%f", conf.Background_opacity)) if runtime.GOOS != "darwin" { argv = append(argv, fmt.Sprintf("--app-id=%s", conf.App_id)) - if conf.Output_name != "" { - argv = append(argv, fmt.Sprintf("--output-name=%s", conf.Output_name)) - } + } + if conf.Output_name != "" { + argv = append(argv, fmt.Sprintf("--output-name=%s", conf.Output_name)) } argv = append(argv, fmt.Sprintf("--focus-policy=%s", conf.Focus_policy)) if conf.Start_as_hidden { diff --git a/kittens/quick_access_terminal/main.py b/kittens/quick_access_terminal/main.py index e1c4a11ba..40028636d 100644 --- a/kittens/quick_access_terminal/main.py +++ b/kittens/quick_access_terminal/main.py @@ -71,9 +71,10 @@ opt('output_name', '', long_text=''' -On Wayland or X11, the panel can only be displayed on a single monitor (output) at a time. This allows +The panel can only be displayed on a single monitor (output) at a time. This allows you to specify which output is used, by name. If not specified the compositor will choose an output automatically, typically the last output the user interacted with or the primary monitor. +You can get a list of available outputs by running: :code:`kitten panel --output-name list`. ''') opt('start_as_hidden', 'no', option_type='to_bool', diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 538366162..b3ef93e2d 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1570,6 +1570,7 @@ def set_os_window_pos(os_window_id: int, x: int, y: int) -> None: def get_all_processes() -> Tuple[int, ...]: pass +def get_monitor_names() -> tuple[tuple[str, str], ...]: ... def num_users() -> int: pass diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index ea14ef658..2ff52d057 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -89,6 +89,9 @@ load_glfw(const char* path) { *(void **) (&glfwGetMonitorName_impl) = dlsym(handle, "glfwGetMonitorName"); if (glfwGetMonitorName_impl == NULL) fail("Failed to load glfw function glfwGetMonitorName with error: %s", dlerror()); + *(void **) (&glfwGetMonitorDescription_impl) = dlsym(handle, "glfwGetMonitorDescription"); + if (glfwGetMonitorDescription_impl == NULL) fail("Failed to load glfw function glfwGetMonitorDescription with error: %s", dlerror()); + *(void **) (&glfwSetMonitorUserPointer_impl) = dlsym(handle, "glfwSetMonitorUserPointer"); if (glfwSetMonitorUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetMonitorUserPointer with error: %s", dlerror()); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 8ce2a2733..a485b3b04 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1052,7 +1052,7 @@ typedef struct GLFWLayerShellConfig { int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; } previous; bool was_toggled_to_fullscreen; - char output_name[64]; + char output_name[128]; GLFWFocusPolicy focus_policy; unsigned x_size_in_cells, x_size_in_pixels; unsigned y_size_in_cells, y_size_in_pixels; @@ -1817,6 +1817,10 @@ typedef const char* (*glfwGetMonitorName_func)(GLFWmonitor*); GFW_EXTERN glfwGetMonitorName_func glfwGetMonitorName_impl; #define glfwGetMonitorName glfwGetMonitorName_impl +typedef const char* (*glfwGetMonitorDescription_func)(GLFWmonitor*); +GFW_EXTERN glfwGetMonitorDescription_func glfwGetMonitorDescription_impl; +#define glfwGetMonitorDescription glfwGetMonitorDescription_impl + typedef void (*glfwSetMonitorUserPointer_func)(GLFWmonitor*, void*); GFW_EXTERN glfwSetMonitorUserPointer_func glfwSetMonitorUserPointer_impl; #define glfwSetMonitorUserPointer glfwSetMonitorUserPointer_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 1059a54dd..cf8bd2d21 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -2143,6 +2143,23 @@ get_monitor_workarea(PYNOARG) { return Py_NewRef(result); } +static PyObject* +get_monitor_names(PYNOARG) { + int count = 0; + GLFWmonitor **monitors = glfwGetMonitors(&count); + if (count <= 0 || !monitors) return PyTuple_New(0); + RAII_PyObject(result, PyTuple_New(count)); if (!result) return NULL; + for (int i = 0; i < count; i++) { + const char *name = glfwGetMonitorName(monitors[i]); + const char *description = glfwGetMonitorDescription(monitors[i]); + PyObject *x = Py_BuildValue("ss", name, description); + if (!x) return NULL; + PyTuple_SET_ITEM(result, i, x); + } + return Py_NewRef(result); +} + + static PyObject* primary_monitor_content_scale(PYNOARG) { GLFWmonitor* monitor = glfwGetPrimaryMonitor(); @@ -2643,6 +2660,7 @@ static PyMethodDef module_methods[] = { {"glfw_get_system_color_theme", (PyCFunction)glfw_get_system_color_theme, METH_VARARGS, ""}, {"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""}, {"glfw_get_monitor_workarea", (PyCFunction)get_monitor_workarea, METH_NOARGS, ""}, + {"glfw_get_monitor_names", (PyCFunction)get_monitor_names, METH_NOARGS, ""}, {"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/main.py b/kitty/main.py index 8f51c9a46..317e986e9 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -38,6 +38,7 @@ SingleKey, create_os_window, free_font_data, + glfw_get_monitor_names, glfw_get_monitor_workarea, glfw_init, glfw_terminate, @@ -220,9 +221,30 @@ def is_panel_kitten() -> bool: return _is_panel_kitten +def list_monitors() -> None: + monitor_names = glfw_get_monitor_names() + has_descriptions = False + for (name, desc) in monitor_names: + if desc: + has_descriptions = True + break + isatty = sys.stdout.isatty() + for (name, desc) in monitor_names: + if isatty: + name = f'\x1b[32m{name}\x1b[39m' # ]] + print(name) + if desc: + print(f'\t{desc}') + if has_descriptions: + print() + + def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = (), talk_fd: int = -1) -> None: global _is_panel_kitten _is_panel_kitten = run_app.cached_values_name == 'panel' + if _is_panel_kitten and run_app.layer_shell_config.output_name == 'list': + list_monitors() + return if is_macos: global_shortcuts = set_cocoa_global_shortcuts(opts) if opts.macos_custom_beam_cursor: diff --git a/kitty/simple_cli_definitions.py b/kitty/simple_cli_definitions.py index b6051dc31..dd3f17d43 100644 --- a/kitty/simple_cli_definitions.py +++ b/kitty/simple_cli_definitions.py @@ -639,9 +639,10 @@ def build_panel_cli_spec(defaults: dict[str, str]) -> str: --output-name -On Wayland or X11, the panel can only be displayed on a single monitor (output) at a time. This allows +The panel can only be displayed on a single monitor (output) at a time. This allows you to specify which output is used, by name. If not specified the compositor will choose an output automatically, typically the last output the user interacted with or the primary monitor. +Use the special value :code:`list` to get a list of available outputs. --class --app-id