From e45548077eefaaeb63591349062d5f2c72499132 Mon Sep 17 00:00:00 2001 From: tranzystorekk Date: Wed, 1 Apr 2026 17:26:46 +0200 Subject: [PATCH 01/71] CI: Void: use wlroots0.20-devel distro package --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 483e7bc9..6130686b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,7 +121,7 @@ jobs: xbps-install -Syu xbps-install -y git meson gcc clang pkg-config scdoc \ cairo-devel glib-devel libpng-devel librsvg-devel libxml2-devel \ - pango-devel wlroots0.19-devel gdb bash xorg-server-xwayland \ + pango-devel wlroots0.20-devel gdb bash xorg-server-xwayland \ dejavu-fonts-ttf libsfdo-devel foot hwids # These builds are executed on all runners From c9b4da2ce21534e52e0da69956c5c5912f828694 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Thu, 2 Apr 2026 19:32:36 +0200 Subject: [PATCH 02/71] osd-thumbnail: handle buffer allocation failure Fixes: #3489 --- src/cycle/osd-thumbnail.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index aa853987..623708a0 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -85,6 +85,10 @@ render_thumb(struct output *output, struct view *view) struct wlr_buffer *buffer = wlr_allocator_create_buffer(server.allocator, view->current.width, view->current.height, &output->wlr_output->swapchain->format); + if (!buffer) { + wlr_log(WLR_ERROR, "failed to allocate buffer for thumbnail"); + return NULL; + } struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass( server.renderer, buffer, NULL); render_node(pass, &view->content_tree->node, 0, 0); From a893b1ab50312ff453025deb894ad0a42acdda7c Mon Sep 17 00:00:00 2001 From: Jens Peters Date: Sat, 4 Apr 2026 07:14:37 +0200 Subject: [PATCH 03/71] input: fix tablet tool tilt motion Tilt axis are not relative. --- src/input/tablet.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/input/tablet.c b/src/input/tablet.c index 58587490..82d5e801 100644 --- a/src/input/tablet.c +++ b/src/input/tablet.c @@ -431,8 +431,6 @@ handle_tablet_tool_axis(struct wl_listener *listener, void *data) */ tool->dx = 0; tool->dy = 0; - tool->tilt_x = 0; - tool->tilt_y = 0; if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_X) { tool->x = ev->x; From 90c38e2681190e8fe52d7ae309ced59eb233603d Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Tue, 7 Apr 2026 13:11:19 -0400 Subject: [PATCH 04/71] xdg: set initial position a hair earlier to fix window rules Partially reverts: 3f223fe5b08c2ae780cb703fbc16ebf487e5fa69 ("xdg: unify initial positioning logic") --- src/xdg.c | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/xdg.c b/src/xdg.c index 5092ab7a..211a340d 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -121,7 +121,7 @@ set_fullscreen_from_request(struct view *view, view_set_fullscreen(view, requested->fullscreen); } -/* Called from commit handler and updates view->pending.x/y directly */ +/* Called from map/commit handler and updates view->pending.x/y directly */ static void set_initial_position(struct view *view) { @@ -263,11 +263,10 @@ handle_commit(struct wl_listener *listener, void *data) bool update_required = false; /* - * The pending size will be empty in two cases: - * (1) when the view is first mapped - * (2) when leaving fullscreen or un-maximizing, if the view - * was initially fullscreen/maximized and the natural - * geometry isn't known yet + * If we didn't know the natural size when leaving fullscreen or + * unmaximizing, then the pending size will be 0x0. In this case, + * the pending x/y is also unset and we still need to position + * the window. */ if (wlr_box_empty(&view->pending) && !wlr_box_empty(&size)) { view->pending.width = size.width; @@ -821,6 +820,29 @@ handle_map(struct wl_listener *listener, void *data) } else { view_set_ssd_mode(view, LAB_SSD_MODE_NONE); } + + /* + * Set initial "pending" dimensions. "Current" + * dimensions remain zero until handle_commit(). + * Note: this must be done before view_impl_map() + * for window rules to work correctly. + */ + if (wlr_box_empty(&view->pending)) { + struct wlr_xdg_surface *xdg_surface = + xdg_surface_from_view(view); + view->pending.width = xdg_surface->geometry.width; + view->pending.height = xdg_surface->geometry.height; + set_initial_position(view); + } + + /* + * Set initial "current" position directly before + * calling view_moved() to reduce flicker + */ + view->current.x = view->pending.x; + view->current.y = view->pending.y; + + view_moved(view); } view_impl_map(view); From afaed4af6376317bd9ce139c83c502c41adc3880 Mon Sep 17 00:00:00 2001 From: daniel <193309918+danielfrrrr@users.noreply.github.com> Date: Wed, 8 Apr 2026 05:37:35 -0300 Subject: [PATCH 05/71] config: remove the underline from the XF86 keys to match the actual key names in xkbcommon-keysyms.h --- README.md | 10 +++++----- docs/rc.xml.all | 10 +++++----- include/config/default-bindings.h | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 379de77a..ec00083d 100644 --- a/README.md +++ b/README.md @@ -217,11 +217,11 @@ If you have not created an rc.xml config file, default bindings will be: | `super`-`mouse-right` | resize window | `super`-`arrow` | resize window to fill half the output | `alt`-`space` | show the window menu -| `XF86_AudioLowerVolume` | amixer sset Master 5%- -| `XF86_AudioRaiseVolume` | amixer sset Master 5%+ -| `XF86_AudioMute` | amixer sset Master toggle -| `XF86_MonBrightnessUp` | brightnessctl set +10% -| `XF86_MonBrightnessDown` | brightnessctl set 10%- +| `XF86AudioLowerVolume` | amixer sset Master 5%- +| `XF86AudioRaiseVolume` | amixer sset Master 5%+ +| `XF86AudioMute` | amixer sset Master toggle +| `XF86MonBrightnessUp` | brightnessctl set +10% +| `XF86MonBrightnessDown` | brightnessctl set 10%- A root-menu can be opened by clicking on the desktop. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index bbec9d0b..6d543b46 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -291,19 +291,19 @@ - + - + - + - + - + diff --git a/include/config/default-bindings.h b/include/config/default-bindings.h index 9d42d237..f2042c28 100644 --- a/include/config/default-bindings.h +++ b/include/config/default-bindings.h @@ -84,35 +84,35 @@ static struct key_combos { .value = "no", }, }, { - .binding = "XF86_AudioLowerVolume", + .binding = "XF86AudioLowerVolume", .action = "Execute", .attributes[0] = { .name = "command", .value = "amixer sset Master 5%-", }, }, { - .binding = "XF86_AudioRaiseVolume", + .binding = "XF86AudioRaiseVolume", .action = "Execute", .attributes[0] = { .name = "command", .value = "amixer sset Master 5%+", }, }, { - .binding = "XF86_AudioMute", + .binding = "XF86AudioMute", .action = "Execute", .attributes[0] = { .name = "command", .value = "amixer sset Master toggle", }, }, { - .binding = "XF86_MonBrightnessUp", + .binding = "XF86MonBrightnessUp", .action = "Execute", .attributes[0] = { .name = "command", .value = "brightnessctl set +10%", }, }, { - .binding = "XF86_MonBrightnessDown", + .binding = "XF86MonBrightnessDown", .action = "Execute", .attributes[0] = { .name = "command", From 3ec20044b5d435a801c4c79b5b7b09619a64cd9e Mon Sep 17 00:00:00 2001 From: Manuel Barrio Linares Date: Tue, 2 Dec 2025 17:44:54 -0300 Subject: [PATCH 06/71] server: add support for color-management-v1 and color-representation-manager-v1 --- src/server.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/server.c b/src/server.c index fd48efed..78aa26a7 100644 --- a/src/server.c +++ b/src/server.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -285,6 +287,8 @@ allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, "xdg_wm_dialog_v1", /* plus */ "wp_alpha_modifier_v1", + "wp_color_manager_v1", + "wp_color_representation_manager_v1", "wp_linux_drm_syncobj_manager_v1", "zxdg_exporter_v1", "zxdg_exporter_v2", @@ -592,6 +596,47 @@ server_init(void) * | output->layer_tree[0] | background layer surfaces (e.g. swaybg) */ + if (server.renderer->features.input_color_transform) { + const enum wp_color_manager_v1_render_intent render_intents[] = { + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL, + }; + size_t transfer_functions_len = 0; + enum wp_color_manager_v1_transfer_function *transfer_functions = + wlr_color_manager_v1_transfer_function_list_from_renderer( + server.renderer, &transfer_functions_len); + + size_t primaries_len = 0; + enum wp_color_manager_v1_primaries *primaries = + wlr_color_manager_v1_primaries_list_from_renderer( + server.renderer, &primaries_len); + + struct wlr_color_manager_v1 *cm = wlr_color_manager_v1_create( + server.wl_display, 2, &(struct wlr_color_manager_v1_options){ + .features = { + .parametric = true, + .set_mastering_display_primaries = true, + }, + .render_intents = render_intents, + .render_intents_len = ARRAY_SIZE(render_intents), + .transfer_functions = transfer_functions, + .transfer_functions_len = transfer_functions_len, + .primaries = primaries, + .primaries_len = primaries_len, + }); + + free(transfer_functions); + free(primaries); + + if (cm) { + wlr_scene_set_color_manager_v1(server.scene, cm); + } else { + wlr_log(WLR_ERROR, "unable to create color manager"); + } + } + + wlr_color_representation_manager_v1_create_with_renderer( + server.wl_display, 1, server.renderer); + server.workspace_tree = lab_wlr_scene_tree_create(&server.scene->tree); server.xdg_popup_tree = lab_wlr_scene_tree_create(&server.scene->tree); #if HAVE_XWAYLAND From c4fd916fd3aa13cb21d8de08a7da8f1bbde5246e Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 31 Mar 2026 10:01:21 +0200 Subject: [PATCH 07/71] Translation updates from weblate Co-authored-by: ButterflyOfFire Co-authored-by: Weblate Translate-URL: https://translate.lxqt-project.org/projects/labwc/labwc/kab/ Translation: Labwc/labwc --- po/LINGUAS | 2 +- po/kab.po | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 po/kab.po diff --git a/po/LINGUAS b/po/LINGUAS index 33f036c9..70114b72 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1 +1 @@ -ar ca cs da de el es et eu fa fi fr gl he hr hu id it ja ka kk ko lt ms nl pa pl pt pt_BR ru sk sv tr uk vi zh_CN zh_TW +ar ca cs da de el es et eu fa fi fr gl he hr hu id it ja ka kab kk ko lt ms nl pa pl pt pt_BR ru sk sv tr uk vi zh_CN zh_TW diff --git a/po/kab.po b/po/kab.po new file mode 100644 index 00000000..44961f47 --- /dev/null +++ b/po/kab.po @@ -0,0 +1,80 @@ +# Labwc pot file +# Copyright (C) 2024 +# This file is distributed under the same license as the labwc package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: labwc\n" +"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" +"POT-Creation-Date: 2024-09-19 21:09+1000\n" +"PO-Revision-Date: 2026-03-31 08:01+0000\n" +"Last-Translator: ButterflyOfFire \n" +"Language-Team: Kabyle \n" +"Language: kab\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.2.1\n" + +#: src/menu/menu.c:1016 +msgid "Go there..." +msgstr "" + +#: src/menu/menu.c:1034 +msgid "Terminal" +msgstr "Ixf" + +#: src/menu/menu.c:1040 +msgid "Reconfigure" +msgstr "" + +#: src/menu/menu.c:1042 +msgid "Exit" +msgstr "Ffeɣ" + +#: src/menu/menu.c:1056 +msgid "Minimize" +msgstr "" + +#: src/menu/menu.c:1058 +msgid "Maximize" +msgstr "" + +#: src/menu/menu.c:1060 +msgid "Fullscreen" +msgstr "Agdil aččuran" + +#: src/menu/menu.c:1062 +msgid "Roll Up/Down" +msgstr "" + +#: src/menu/menu.c:1064 +msgid "Decorations" +msgstr "" + +#: src/menu/menu.c:1066 +msgid "Always on Top" +msgstr "" + +#: src/menu/menu.c:1071 +msgid "Move Left" +msgstr "" + +#: src/menu/menu.c:1078 +msgid "Move Right" +msgstr "" + +#: src/menu/menu.c:1083 +msgid "Always on Visible Workspace" +msgstr "" + +#: src/menu/menu.c:1086 +msgid "Workspace" +msgstr "" + +#: src/menu/menu.c:1089 +msgid "Close" +msgstr "Mdel" From c8bb3381a4fdd5d303e3a173d4216eaa283fdce8 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Mon, 6 Apr 2026 21:57:24 +0100 Subject: [PATCH 08/71] Add key state OSD for debugging Add action `DebugToggleKeyStateIndicator` to show in the top-left corner the pressed, bound, pressed-sent keys as well as modifiers. This should help fault find issues like #2771 and #3238 Based-on: #3262 Co-authored-by: @tokyo4j --- docs/labwc-actions.5.scd | 4 + include/input/key-state.h | 5 ++ src/action.c | 7 +- src/input/key-state.c | 161 ++++++++++++++++++++++++++++++++++++++ src/input/keyboard.c | 5 ++ 5 files changed, 181 insertions(+), 1 deletion(-) diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index fe468cd1..a70c0086 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -436,6 +436,10 @@ Actions are used in menus and keyboard/mouse bindings. If used as the only action for a binding: clear an earlier defined binding. +** + Toggle visibility of key-state on-screen display (OSD). Note: This is for + debugging purposes only. + # CONDITIONAL ACTIONS Actions that execute other actions. Used in keyboard/mouse bindings. diff --git a/include/input/key-state.h b/include/input/key-state.h index d9971b74..81374b6c 100644 --- a/include/input/key-state.h +++ b/include/input/key-state.h @@ -5,12 +5,17 @@ #include #include +struct seat; + /* * All keycodes in these functions are (Linux) libinput evdev scancodes which is * what 'wlr_keyboard' uses (e.g. 'seat->keyboard_group->keyboard->keycodes'). * Note: These keycodes are different to XKB scancodes by a value of 8. */ +void key_state_indicator_update(struct seat *seat); +void key_state_indicator_toggle(void); + /** * key_state_pressed_sent_keycodes - generate array of pressed+sent keys * Note: The array is generated by subtracting any bound keys from _all_ pressed diff --git a/src/action.c b/src/action.c index 66fa9b20..2cfa6be6 100644 --- a/src/action.c +++ b/src/action.c @@ -21,6 +21,7 @@ #include "cycle.h" #include "debug.h" #include "input/keyboard.h" +#include "input/key-state.h" #include "labwc.h" #include "magnifier.h" #include "menu/menu.h" @@ -133,7 +134,8 @@ struct action_arg_list { X(ZOOM_IN, "ZoomIn") \ X(ZOOM_OUT, "ZoomOut") \ X(WARP_CURSOR, "WarpCursor") \ - X(HIDE_CURSOR, "HideCursor") + X(HIDE_CURSOR, "HideCursor") \ + X(DEBUG_TOGGLE_KEY_STATE_INDICATOR, "DebugToggleKeyStateIndicator") /* * Will expand to: @@ -1570,6 +1572,9 @@ run_action(struct view *view, struct action *action, case ACTION_TYPE_HIDE_CURSOR: cursor_set_visible(&server.seat, false); break; + case ACTION_TYPE_DEBUG_TOGGLE_KEY_STATE_INDICATOR: + key_state_indicator_toggle(); + break; case ACTION_TYPE_INVALID: wlr_log(WLR_ERROR, "Not executing unknown action"); break; diff --git a/src/input/key-state.c b/src/input/key-state.c index 492f7b0f..6d089891 100644 --- a/src/input/key-state.c +++ b/src/input/key-state.c @@ -4,10 +4,171 @@ #include #include #include +#include +#include +#include +#include "config/rcxml.h" +#include "common/buf.h" #include "common/set.h" +#include "input/keyboard.h" +#include "labwc.h" +#include "scaled-buffer/scaled-font-buffer.h" static struct lab_set pressed, bound, pressed_sent; +static bool show_debug_indicator; +static struct indicator_state { + struct wlr_scene_tree *tree; + struct scaled_font_buffer *sfb_pressed; + struct scaled_font_buffer *sfb_bound; + struct scaled_font_buffer *sfb_pressed_sent; + struct scaled_font_buffer *sfb_modifiers; + struct xkb_keymap *keymap; +} indicator_state; + +static const char * +keycode_to_keyname(struct xkb_keymap *keymap, uint32_t keycode) +{ + const xkb_keysym_t *syms; + int syms_len = xkb_keymap_key_get_syms_by_level(keymap, keycode + 8, 0, 0, &syms); + if (!syms_len) { + return NULL; + } + + static char buf[256]; + if (!xkb_keysym_get_name(syms[0], buf, sizeof(buf))) { + return NULL; + } + + return buf; +} + +static const char * +modifier_to_name(uint32_t modifier) +{ + switch (modifier) { + case WLR_MODIFIER_SHIFT: + return "S"; + case WLR_MODIFIER_CAPS: + return "caps"; + case WLR_MODIFIER_CTRL: + return "C"; + case WLR_MODIFIER_ALT: + return "A"; + case WLR_MODIFIER_MOD2: + return "numlock"; + case WLR_MODIFIER_MOD3: + return "H"; + case WLR_MODIFIER_LOGO: + return "W"; + case WLR_MODIFIER_MOD5: + return "M"; + default: + return "?"; + } +} + +static void +init_indicator(struct indicator_state *state) +{ + state->tree = wlr_scene_tree_create(&server.scene->tree); + wlr_scene_node_set_enabled(&state->tree->node, false); + + state->sfb_pressed = scaled_font_buffer_create(state->tree); + wlr_scene_node_set_position(&state->sfb_pressed->scene_buffer->node, 0, 0); + state->sfb_bound = scaled_font_buffer_create(state->tree); + wlr_scene_node_set_position(&state->sfb_bound->scene_buffer->node, 0, 20); + state->sfb_pressed_sent = scaled_font_buffer_create(state->tree); + wlr_scene_node_set_position(&state->sfb_pressed_sent->scene_buffer->node, 0, 40); + state->sfb_modifiers = scaled_font_buffer_create(state->tree); + wlr_scene_node_set_position(&state->sfb_modifiers->scene_buffer->node, 0, 60); + + struct xkb_rule_names rules = { 0 }; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + state->keymap = xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); +} + +static void +update_key_indicator_callback(void *data) +{ + struct seat *seat = data; + uint32_t all_modifiers = keyboard_get_all_modifiers(seat); + float black[4] = {0, 0, 0, 1}; + float white[4] = {1, 1, 1, 1}; + + if (!indicator_state.tree) { + init_indicator(&indicator_state); + } + + if (show_debug_indicator) { + wlr_scene_node_set_enabled(&indicator_state.tree->node, true); + } else { + wlr_scene_node_set_enabled(&indicator_state.tree->node, false); + return; + } + + struct buf buf = BUF_INIT; + + buf_add(&buf, "pressed="); + for (int i = 0; i < pressed.size; i++) { + const char *keyname = keycode_to_keyname(indicator_state.keymap, + pressed.values[i]); + buf_add_fmt(&buf, "%s (%d), ", keyname, pressed.values[i]); + } + scaled_font_buffer_update(indicator_state.sfb_pressed, buf.data, + -1, &rc.font_osd, black, white); + + buf_clear(&buf); + buf_add(&buf, "bound="); + for (int i = 0; i < bound.size; i++) { + const char *keyname = keycode_to_keyname(indicator_state.keymap, + bound.values[i]); + buf_add_fmt(&buf, "%s (%d), ", keyname, bound.values[i]); + } + scaled_font_buffer_update(indicator_state.sfb_bound, buf.data, + -1, &rc.font_osd, black, white); + + buf_clear(&buf); + buf_add(&buf, "pressed_sent="); + for (int i = 0; i < pressed_sent.size; i++) { + const char *keyname = keycode_to_keyname(indicator_state.keymap, + pressed_sent.values[i]); + buf_add_fmt(&buf, "%s (%d), ", keyname, pressed_sent.values[i]); + } + scaled_font_buffer_update(indicator_state.sfb_pressed_sent, buf.data, -1, + &rc.font_osd, black, white); + + buf_clear(&buf); + buf_add(&buf, "modifiers="); + for (int i = 0; i <= 7; i++) { + uint32_t mod = 1 << i; + if (all_modifiers & mod) { + buf_add_fmt(&buf, "%s, ", modifier_to_name(mod)); + } + } + buf_add_fmt(&buf, "(%d)", all_modifiers); + scaled_font_buffer_update(indicator_state.sfb_modifiers, buf.data, -1, + &rc.font_osd, black, white); + + buf_reset(&buf); +} + +void +key_state_indicator_update(struct seat *seat) +{ + if (!show_debug_indicator) { + return; + } + wl_event_loop_add_idle(server.wl_event_loop, + update_key_indicator_callback, seat); +} + +void +key_state_indicator_toggle(void) +{ + show_debug_indicator = !show_debug_indicator; +} + static void report(struct lab_set *key_set, const char *msg) { diff --git a/src/input/keyboard.c b/src/input/keyboard.c index 2d4bde6e..9d8e840d 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -139,6 +139,8 @@ handle_modifiers(struct wl_listener *listener, void *data) struct seat *seat = keyboard->base.seat; struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; + key_state_indicator_update(seat); + if (server.input_mode == LAB_INPUT_STATE_MOVE) { /* Any change to the modifier state re-enable region snap */ seat->region_prevent_snap = false; @@ -622,6 +624,9 @@ handle_key(struct wl_listener *listener, void *data) struct seat *seat = keyboard->base.seat; struct wlr_keyboard_key_event *event = data; struct wlr_seat *wlr_seat = seat->wlr_seat; + + key_state_indicator_update(seat); + idle_manager_notify_activity(seat->wlr_seat); /* any new press/release cancels current keybind repeat */ From 8b32422b93d0389bb2cad42376890b4270fddad6 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Fri, 3 Apr 2026 20:38:21 +0200 Subject: [PATCH 09/71] rcxml: allow to restrict privileged interfaces --- docs/labwc-config.5.scd | 46 +++++++++++++++++++++++++++++++++++++++ include/config/rcxml.h | 3 +++ src/config/rcxml.c | 48 +++++++++++++++++++++++++++++++++++++++++ src/server.c | 42 ++++++------------------------------ 4 files changed, 104 insertions(+), 35 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 09f7b3eb..061e57ad 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -1445,6 +1445,52 @@ situation. Whether to apply a bilinear filter to the magnified image, or just to use nearest-neighbour. Default is true - bilinear filtered. +## PRIVILEGED INTERFACES + +Labwc supports a small set of privileged wayland interfaces. All of these +interfaces are enabled by default for applications unless they are running +via a sandbox environment supporting the security-context-v1 protocol. + +Security conscious users are suggested to use a sandbox framework to run +potentially untrusted applications as it additionally limits access to the +filesystem (including labwc configuration) and other services like dbus. + +In addition to that, privileged protocols can be restricted for non-sandboxed +clients by defining a `` block: + +``` + + zwlr_layer_shell_v1 + zwlr_virtual_pointer_manager_v1 + +``` + +** + Name of the interface that should be allowed. + +This is the full list of interfaces that can be controlled with this mechanism: + +- `wp_drm_lease_device_v1` +- `zwlr_gamma_control_manager_v1` +- `zwlr_output_manager_v1` +- `zwlr_output_power_manager_v1` +- `zwp_input_method_manager_v2` +- `zwlr_virtual_pointer_manager_v1` +- `zwp_virtual_keyboard_manager_v1` +- `zwlr_export_dmabuf_manager_v1` +- `zwlr_screencopy_manager_v1` +- `ext_data_control_manager_v1` +- `zwlr_data_control_manager_v1` +- `wp_security_context_manager_v1` +- `ext_idle_notifier_v1` +- `zwlr_foreign_toplevel_manager_v1` +- `ext_foreign_toplevel_list_v1` +- `ext_session_lock_manager_v1` +- `zwlr_layer_shell_v1` +- `ext_workspace_manager_v1` +- `ext_image_copy_capture_manager_v1` +- `ext_output_image_capture_source_manager_v1` + ## ENVIRONMENT VARIABLES *XCURSOR_PATH* diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 517cd907..8a2c606c 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -76,6 +76,7 @@ struct rcxml { enum tearing_mode allow_tearing; bool auto_enable_outputs; bool reuse_output_mode; + uint32_t allowed_interfaces; bool xwayland_persistence; bool primary_selection; char *prompt_command; @@ -225,4 +226,6 @@ void rcxml_finish(void); */ void append_parsed_actions(xmlNode *node, struct wl_list *list); +uint32_t parse_privileged_interface(const char *name); + #endif /* LABWC_RCXML_H */ diff --git a/src/config/rcxml.c b/src/config/rcxml.c index a3eeed63..9884d15e 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -94,6 +94,43 @@ parse_window_type(const char *type) } } +uint32_t +parse_privileged_interface(const char *name) +{ + static const char * const ifaces[] = { + "wp_drm_lease_device_v1", + "zwlr_gamma_control_manager_v1", + "zwlr_output_manager_v1", + "zwlr_output_power_manager_v1", + "zwp_input_method_manager_v2", + "zwlr_virtual_pointer_manager_v1", + "zwp_virtual_keyboard_manager_v1", + "zwlr_export_dmabuf_manager_v1", + "zwlr_screencopy_manager_v1", + "ext_data_control_manager_v1", + "zwlr_data_control_manager_v1", + "wp_security_context_manager_v1", + "ext_idle_notifier_v1", + "zwlr_foreign_toplevel_manager_v1", + "ext_foreign_toplevel_list_v1", + "ext_session_lock_manager_v1", + "zwlr_layer_shell_v1", + "ext_workspace_manager_v1", + "ext_image_copy_capture_manager_v1", + "ext_output_image_capture_source_manager_v1", + }; + + static_assert(ARRAY_SIZE(ifaces) <= 32, + "return type too small for amount of privileged protocols"); + + for (size_t i = 0; i < ARRAY_SIZE(ifaces); i++) { + if (!strcmp(name, ifaces[i])) { + return 1 << i; + } + } + return 0; +} + /* * Openbox/labwc comparison * @@ -1377,6 +1414,16 @@ entry(xmlNode *node, char *nodename, char *content) rc.mag_increment = MAX(0, rc.mag_increment); } else if (!strcasecmp(nodename, "useFilter.magnifier")) { set_bool(content, &rc.mag_filter); + } else if (!strcasecmp(nodename, "privilegedInterfaces")) { + rc.allowed_interfaces = 0; + } else if (!strcasecmp(nodename, "allow.privilegedInterfaces")) { + uint32_t iface_id = parse_privileged_interface(content); + if (iface_id) { + rc.allowed_interfaces |= iface_id; + } else { + wlr_log(WLR_ERROR, "invalid value for " + ""); + } } return false; @@ -1459,6 +1506,7 @@ rcxml_init(void) rc.allow_tearing = LAB_TEARING_DISABLED; rc.auto_enable_outputs = true; rc.reuse_output_mode = false; + rc.allowed_interfaces = UINT32_MAX; rc.xwayland_persistence = false; rc.primary_selection = true; diff --git a/src/server.c b/src/server.c index 78aa26a7..e4b27026 100644 --- a/src/server.c +++ b/src/server.c @@ -212,39 +212,6 @@ handle_drm_lease_request(struct wl_listener *listener, void *data) } #endif -static bool -protocol_is_privileged(const struct wl_interface *iface) -{ - static const char * const rejected[] = { - "wp_drm_lease_device_v1", - "zwlr_gamma_control_manager_v1", - "zwlr_output_manager_v1", - "zwlr_output_power_manager_v1", - "zwp_input_method_manager_v2", - "zwlr_virtual_pointer_manager_v1", - "zwp_virtual_keyboard_manager_v1", - "zwlr_export_dmabuf_manager_v1", - "zwlr_screencopy_manager_v1", - "ext_data_control_manager_v1", - "zwlr_data_control_manager_v1", - "wp_security_context_manager_v1", - "ext_idle_notifier_v1", - "zwlr_foreign_toplevel_manager_v1", - "ext_foreign_toplevel_list_v1", - "ext_session_lock_manager_v1", - "zwlr_layer_shell_v1", - "ext_workspace_manager_v1", - "ext_image_copy_capture_manager_v1", - "ext_output_image_capture_source_manager_v1", - }; - for (size_t i = 0; i < ARRAY_SIZE(rejected); i++) { - if (!strcmp(iface->name, rejected[i])) { - return true; - } - } - return false; -} - static bool allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, const struct wl_interface *iface) @@ -327,6 +294,11 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo } #endif + uint32_t iface_id = parse_privileged_interface(iface->name); + if (iface_id && !(iface_id & rc.allowed_interfaces)) { + return false; + } + /* Do not allow security_context_manager_v1 to clients with a security context attached */ const struct wlr_security_context_v1_state *security_context = wlr_security_context_manager_v1_lookup_client( @@ -342,11 +314,11 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo /* * TODO: The following call is basically useless right now * and should be replaced with - * assert(allow || protocol_is_privileged(iface)); + * assert(allow || iface_id); * This ensures that our lists are in sync with what * protocols labwc supports. */ - if (!allow && !protocol_is_privileged(iface)) { + if (!allow && !iface_id) { wlr_log(WLR_ERROR, "Blocking unknown protocol %s", iface->name); } else if (!allow) { wlr_log(WLR_DEBUG, "Blocking %s for security context %s->%s->%s", From da37e97a450d5e094a58dcd34c729c937ba01b30 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Mon, 6 Apr 2026 22:05:37 +0100 Subject: [PATCH 10/71] Add action `ToggleShowDesktop` --- docs/labwc-actions.5.scd | 7 ++++ include/show-desktop.h | 8 +++++ include/view.h | 1 + src/action.c | 5 +++ src/desktop.c | 3 ++ src/meson.build | 1 + src/show-desktop.c | 78 ++++++++++++++++++++++++++++++++++++++++ src/workspaces.c | 3 ++ 8 files changed, 106 insertions(+) create mode 100644 include/show-desktop.h create mode 100644 src/show-desktop.c diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index a70c0086..de1dc00d 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -424,6 +424,13 @@ Actions are used in menus and keyboard/mouse bindings. Toggle the screen magnifier on or off at the last magnification level used. +** + Minimize all windows in the current workspace so that the desktop becomes + visible. On calling the action again the hidden windows are unminimized, + provided that - since the initial `ShowDesktop` - (a) no windows have been + unminimized; (b) workspaces have not been switched; and (c) no new + applications have been started. + **++ ** Increase or decrease the magnification level for the screen magnifier. diff --git a/include/show-desktop.h b/include/show-desktop.h new file mode 100644 index 00000000..cc86bcb4 --- /dev/null +++ b/include/show-desktop.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_SHOW_DESKTOP_H +#define LABWC_SHOW_DESKTOP_H + +void show_desktop_toggle(void); +void show_desktop_reset(void); + +#endif /* LABWC_SHOW_DESKTOP_H */ diff --git a/include/view.h b/include/view.h index c2764f94..da7aef02 100644 --- a/include/view.h +++ b/include/view.h @@ -184,6 +184,7 @@ struct view { enum ssd_preference ssd_preference; bool shaded; bool minimized; + bool was_minimized_by_show_desktop_action; enum view_axis maximized; bool fullscreen; bool tearing_hint; diff --git a/src/action.c b/src/action.c index 2cfa6be6..34435e02 100644 --- a/src/action.c +++ b/src/action.c @@ -28,6 +28,7 @@ #include "output.h" #include "output-virtual.h" #include "regions.h" +#include "show-desktop.h" #include "ssd.h" #include "theme.h" #include "translate.h" @@ -133,6 +134,7 @@ struct action_arg_list { X(TOGGLE_MAGNIFY, "ToggleMagnify") \ X(ZOOM_IN, "ZoomIn") \ X(ZOOM_OUT, "ZoomOut") \ + X(TOGGLE_SHOW_DESKTOP, "ToggleShowDesktop") \ X(WARP_CURSOR, "WarpCursor") \ X(HIDE_CURSOR, "HideCursor") \ X(DEBUG_TOGGLE_KEY_STATE_INDICATOR, "DebugToggleKeyStateIndicator") @@ -1562,6 +1564,9 @@ run_action(struct view *view, struct action *action, case ACTION_TYPE_ZOOM_OUT: magnifier_set_scale(MAGNIFY_DECREASE); break; + case ACTION_TYPE_TOGGLE_SHOW_DESKTOP: + show_desktop_toggle(); + break; case ACTION_TYPE_WARP_CURSOR: { const char *to = action_get_str(action, "to", "output"); const char *x = action_get_str(action, "x", "center"); diff --git a/src/desktop.c b/src/desktop.c index 28bf4464..4c7870f1 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -14,6 +14,7 @@ #include "layers.h" #include "node.h" #include "output.h" +#include "show-desktop.h" #include "ssd.h" #include "view.h" #include "workspaces.h" @@ -113,6 +114,8 @@ desktop_focus_view(struct view *view, bool raise) */ struct view *dialog = view_get_modal_dialog(view); set_or_offer_focus(dialog ? dialog : view); + + show_desktop_reset(); } /* TODO: focus layer-shell surfaces also? */ diff --git a/src/meson.build b/src/meson.build index 40ec3170..05163cfa 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,7 @@ labwc_sources = files( 'seat.c', 'server.c', 'session-lock.c', + 'show-desktop.c', 'snap-constraints.c', 'snap.c', 'tearing.c', diff --git a/src/show-desktop.c b/src/show-desktop.c new file mode 100644 index 00000000..efddf2de --- /dev/null +++ b/src/show-desktop.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "show-desktop.h" +#include +#include "common/array.h" +#include "config/types.h" +#include "labwc.h" +#include "view.h" + +static bool is_showing_desktop; + +static void +minimize_views(struct wl_array *views, bool minimize) +{ + struct view **view; + wl_array_for_each_reverse(view, views) { + view_minimize(*view, minimize); + } +} + +static void +show(void) +{ + static struct wl_array views; + wl_array_init(&views); + + /* Build array first as minimize changes server.views */ + struct view *view; + for_each_view(view, &server.views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { + if (view->minimized) { + continue; + } + view->was_minimized_by_show_desktop_action = true; + array_add(&views, view); + } + minimize_views(&views, true); + is_showing_desktop = true; + + wl_array_release(&views); +} + +static void +restore(void) +{ + static struct wl_array views; + wl_array_init(&views); + + struct view *view; + for_each_view(view, &server.views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { + if (view->was_minimized_by_show_desktop_action) { + array_add(&views, view); + } + } + minimize_views(&views, false); + show_desktop_reset(); + + wl_array_release(&views); +} + +void +show_desktop_toggle(void) +{ + if (is_showing_desktop) { + restore(); + } else { + show(); + } +} + +void +show_desktop_reset(void) +{ + is_showing_desktop = false; + + struct view *view; + for_each_view(view, &server.views, LAB_VIEW_CRITERIA_NONE) { + view->was_minimized_by_show_desktop_action = false; + } +} diff --git a/src/workspaces.c b/src/workspaces.c index 7ff471a5..a1ed9112 100644 --- a/src/workspaces.c +++ b/src/workspaces.c @@ -21,6 +21,7 @@ #include "input/keyboard.h" #include "labwc.h" #include "output.h" +#include "show-desktop.h" #include "theme.h" #include "view.h" @@ -495,6 +496,8 @@ workspaces_switch_to(struct workspace *target, bool update_focus) desktop_update_top_layer_visibility(); wlr_ext_workspace_handle_v1_set_active(target->ext_workspace, true); + + show_desktop_reset(); } void From 4c7a9960a400bd9bd5052483e008b776eee35cfc Mon Sep 17 00:00:00 2001 From: yuiiio Date: Tue, 14 Apr 2026 12:02:20 +0900 Subject: [PATCH 11/71] osd-thumbnail: fix failed to get client texture Use wlr_client_buffer->texture directly instead of wlr_texture_from_buffer. All buffers in content_tree are wlr_client_buffer. wlr_texture_from_buffer calls client_buffer_begin_data_ptr_access which fails when client_buffer->source == NULL (client released buffer early, e.g. wl_shm foot-terminal). --- src/cycle/osd-thumbnail.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index 623708a0..defedf09 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include "config/rcxml.h" @@ -46,8 +47,12 @@ render_node(struct wlr_render_pass *pass, if (!scene_buffer->buffer) { break; } - struct wlr_texture *texture = wlr_texture_from_buffer( - server.renderer, scene_buffer->buffer); + struct wlr_texture *texture = NULL; + struct wlr_client_buffer *client_buffer = + wlr_client_buffer_get(scene_buffer->buffer); + if (client_buffer) { + texture = client_buffer->texture; + } if (!texture) { break; } @@ -62,7 +67,6 @@ render_node(struct wlr_render_pass *pass, }, .transform = scene_buffer->transform, }); - wlr_texture_destroy(texture); break; } case WLR_SCENE_NODE_RECT: From c5ea41e87698a868e7280a52af0bd7da2968e2b1 Mon Sep 17 00:00:00 2001 From: Yannis Drougas Date: Wed, 9 Apr 2025 08:26:59 +0100 Subject: [PATCH 12/71] Add overrideInhibition to keyboard.keybind --- docs/labwc-config.5.scd | 8 +++++++- include/config/keybind.h | 1 + src/config/rcxml.c | 3 +++ src/input/keyboard.c | 6 ++++-- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 061e57ad..ac94d54c 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -760,7 +760,8 @@ References: Stores the keyboard layout either globally or per window and restores it when switching back to the window. Default is global. -** +** Define a *key* binding in the format *modifier-key*, where supported modifiers are: - S (shift) @@ -808,6 +809,11 @@ References: *allowWhenLocked* [yes|no] Make this keybind work even if the screen is locked. Default is no. + *overrideInhibition* [yes|no] + Make this keybind work even if the view inhibits keybinds. Default is no. + This can be used to prevent W-Tab and similar keybinds from being + delivered to Virtual Machines, VNC clients or nested compositors. + *onRelease* [yes|no] When yes, fires the keybind action when the key or key combination is released, rather than first pressed. This is useful to diff --git a/include/config/keybind.h b/include/config/keybind.h index e3c9e21b..d02de387 100644 --- a/include/config/keybind.h +++ b/include/config/keybind.h @@ -23,6 +23,7 @@ struct keybind { struct wl_list actions; /* struct action.link */ struct wl_list link; /* struct rcxml.keybinds */ bool on_release; + bool override_inhibition; }; /** diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 9884d15e..20c236b4 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -602,6 +602,7 @@ fill_keybind(xmlNode *node) lab_xml_get_bool(node, "onRelease", &keybind->on_release); lab_xml_get_bool(node, "layoutDependent", &keybind->use_syms_only); lab_xml_get_bool(node, "allowWhenLocked", &keybind->allow_when_locked); + lab_xml_get_bool(node, "overrideInhibition", &keybind->override_inhibition); append_parsed_actions(node, &keybind->actions); } @@ -1706,6 +1707,8 @@ deduplicate_key_bindings(void) wl_list_remove(¤t->link); keybind_destroy(current); cleared++; + } else if (actions_contain_toggle_keybinds(¤t->actions)) { + current->override_inhibition = true; } } if (replaced) { diff --git a/src/input/keyboard.c b/src/input/keyboard.c index 9d8e840d..98be5d11 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -203,8 +203,10 @@ match_keybinding_for_sym(uint32_t modifiers, if (modifiers ^ keybind->modifiers) { continue; } - if (view_inhibits_actions(server.active_view, &keybind->actions)) { - continue; + if (!(keybind->override_inhibition)) { + if (view_inhibits_actions(server.active_view, &keybind->actions)) { + continue; + } } if (sym == XKB_KEY_NoSymbol) { /* Use keycodes */ From 6df6a092bad7ec76a86ecda3810425412bd571cc Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Tue, 14 Apr 2026 21:55:16 +0100 Subject: [PATCH 13/71] NEWS.md: update notes for 0.9.7 --- NEWS.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index d721ed3b..ac001850 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,8 @@ The format is based on [Keep a Changelog] | Date | All Changes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| -| 2026-03-31 | [unreleased] | 0.20.0 | 27402 | +| 2026-04-17 | [unreleased] | 0.20.0 | 27753 | +| 2026-04-17 | [0.9.7] | 0.19.2 | 29277 | | 2026-03-15 | [0.9.6] | 0.19.2 | 29271 | | 2026-03-04 | [0.9.5] | 0.19.2 | 29251 | | 2026-02-27 | [0.9.4] | 0.19.2 | 29225 | @@ -44,6 +45,7 @@ The format is based on [Keep a Changelog] | 2021-03-05 | [0.1.0] | 0.12.0 | 4627 | [unreleased]: NEWS.md#unreleased +[0.9.7]: NEWS.md#097---2026-04-17 [0.9.6]: NEWS.md#096---2026-03-15 [0.9.5]: NEWS.md#095---2026-03-04 [0.9.4]: NEWS.md#094---2026-02-27 @@ -117,8 +119,20 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: The codebase has been ported to wlroots 0.20 [#2956] @Consolatis +Note to maintainers: +- libinput >=1.26 is required in support of tablet tool pressure range + configuration. + ### Added +- Add `overrideInhibition` option to `` [#3507] @drougas +- Add action `ToggleShowDesktop` to hide/unhide windows [#3500] @johanmalm +- Add `` config option so that privileged protocols can be + restricted [#3493] @xi +- Add action `DebugToggleKeyStateIndicator` to show a key-state on-screen + display (OSD) for debugging. [#3499] @johanmalm @tokyo4j +- Add support for `color-management-v1` and `color-representation-manager-v1` + protocols [#3469] @ManuLinares - Add configuration option `` to enable tablet tool pressure range libinput settings [#2916] @jp7677 - Add `wl_fixes` interface [#2956] @kode54 @@ -144,19 +158,41 @@ The codebase has been ported to wlroots 0.20 [#2956] @Consolatis compositor and, unlike DRM outputs, these cannot be reconnected after being destroyed. [#3440] @marler8997 - Allow policy-based placement to apply when an initially-maximized/fullscreen - view is restored to floating geometry. [#3387] @jlindgren90 + view is restored to floating geometry. [#3387] [#3502] @jlindgren90 ### Changed - Drop cosmic-workspace protocol [#3031] @tokyo4j +## 0.9.7 - 2026-04-17 + +[0.9.7-commits] + +This is a small bug fix release. + +``` + 0.9.6--------0.9.7 <--- v0.9 branch with bug-fixes only + / + / +0.9.4--------0.9.5-------- <-- master (built with wlroots-0.20) +``` + +## Fixed + +- Fix intermittent failed-to-get-texture issue with some clients (e.g. foot) + when using the window-switcher in the thumbnail mode. [#3511] @yuiiio +- Fix tablet tool tilt motion. [#3494] @jp7677 +- Handle window-switcher buffer allocation failure when in 'thumbnail' mode. + This is believed to be very unlikely to happen, but has been reported by one + user and is believed to be GPU driver related. [#3490] @Consolatis + ## 0.9.6 - 2026-03-15 [0.9.6-commits] This is an earlier-than-usual release containing bug fixes only. It has been -done on a separate branch (0.9.5-maintenance) to avoid the inclusion of -refactoring and new features. +done on a separate branch (v0.9) to avoid the inclusion of refactoring and new +features. ``` 0.9.6 <--- bug-fixes only @@ -2682,6 +2718,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ [unreleased-commits]: https://github.com/labwc/labwc/compare/0.9.5...HEAD +[0.9.7-commits]: https://github.com/labwc/labwc/compare/0.9.6...0.9.7 [0.9.6-commits]: https://github.com/labwc/labwc/compare/0.9.5...0.9.6 [0.9.5-commits]: https://github.com/labwc/labwc/compare/0.9.4...0.9.5 [0.9.4-commits]: https://github.com/labwc/labwc/compare/0.9.3...0.9.4 @@ -3232,3 +3269,12 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3445]: https://github.com/labwc/labwc/pull/3445 [#3446]: https://github.com/labwc/labwc/pull/3446 [#3450]: https://github.com/labwc/labwc/pull/3450 +[#3469]: https://github.com/labwc/labwc/pull/3469 +[#3490]: https://github.com/labwc/labwc/pull/3490 +[#3493]: https://github.com/labwc/labwc/pull/3493 +[#3494]: https://github.com/labwc/labwc/pull/3494 +[#3499]: https://github.com/labwc/labwc/pull/3499 +[#3500]: https://github.com/labwc/labwc/pull/3500 +[#3502]: https://github.com/labwc/labwc/pull/3502 +[#3507]: https://github.com/labwc/labwc/pull/3507 +[#3511]: https://github.com/labwc/labwc/pull/3511 From 5c582bda09de01f1bd172f83b1f5620923ec8c8b Mon Sep 17 00:00:00 2001 From: daniel <193309918+danielfrrrr@users.noreply.github.com> Date: Wed, 1 Apr 2026 06:49:47 -0300 Subject: [PATCH 14/71] config: migrate from amixer to pactl --- README.md | 6 +++--- docs/labwc-config.5.scd | 2 +- docs/rc.xml.all | 6 +++--- include/config/default-bindings.h | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ec00083d..9cc81e88 100644 --- a/README.md +++ b/README.md @@ -217,9 +217,9 @@ If you have not created an rc.xml config file, default bindings will be: | `super`-`mouse-right` | resize window | `super`-`arrow` | resize window to fill half the output | `alt`-`space` | show the window menu -| `XF86AudioLowerVolume` | amixer sset Master 5%- -| `XF86AudioRaiseVolume` | amixer sset Master 5%+ -| `XF86AudioMute` | amixer sset Master toggle +| `XF86AudioLowerVolume` | pactl set-sink-volume @DEFAULT_SINK@ -5% +| `XF86AudioRaiseVolume` | pactl set-sink-volume @DEFAULT_SINK@ +5% +| `XF86AudioMute` | pactl set-sink-mute @DEFAULT_SINK@ toggle | `XF86MonBrightnessUp` | brightnessctl set +10% | `XF86MonBrightnessDown` | brightnessctl set 10%- diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index ac94d54c..32943eb7 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -851,7 +851,7 @@ overrideInhibition="">* A-Space - show window menu ``` - Audio and MonBrightness keys are also bound to amixer and + Audio and MonBrightness keys are also bound to pactl and brightnessctl, respectively. ** diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 6d543b46..9ddd69a8 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -292,13 +292,13 @@ - + - + - + diff --git a/include/config/default-bindings.h b/include/config/default-bindings.h index f2042c28..a49f60f4 100644 --- a/include/config/default-bindings.h +++ b/include/config/default-bindings.h @@ -88,21 +88,21 @@ static struct key_combos { .action = "Execute", .attributes[0] = { .name = "command", - .value = "amixer sset Master 5%-", + .value = "pactl set-sink-volume @DEFAULT_SINK@ -5%", }, }, { .binding = "XF86AudioRaiseVolume", .action = "Execute", .attributes[0] = { .name = "command", - .value = "amixer sset Master 5%+", + .value = "pactl set-sink-volume @DEFAULT_SINK@ +5%", }, }, { .binding = "XF86AudioMute", .action = "Execute", .attributes[0] = { .name = "command", - .value = "amixer sset Master toggle", + .value = "pactl set-sink-mute @DEFAULT_SINK@ toggle", }, }, { .binding = "XF86MonBrightnessUp", From 5f668a82eedfda31482d7e7eb6c717d014333900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 17 Apr 2026 18:52:01 +0200 Subject: [PATCH 15/71] interactive: allow resize on fully maximized views Modifier+right-drag resize was silently ignored on fully maximized views because of an early-return guard in interactive_begin(). The axis-specific un-maximization logic introduced in #3043 already handles partial maximization correctly; extend that to the VIEW_AXIS_BOTH case so both axes are cleared while keeping the current geometry as the starting point of the resize. Move already permits dragging maximized views, so this also removes an asymmetry between the move and resize paths. Fixes: #3524 --- src/interactive.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/interactive.c b/src/interactive.c index f4d8eea1..9214ba30 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -123,11 +123,12 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) cursor_shape = LAB_CURSOR_GRAB; break; case LAB_INPUT_STATE_RESIZE: { - if (view->shaded || view->fullscreen || - view->maximized == VIEW_AXIS_BOTH) { + if (view->shaded || view->fullscreen) { /* - * We don't allow resizing while shaded, - * fullscreen or maximized in both directions. + * We don't allow resizing while shaded or fullscreen. + * Maximized views are handled below by un-maximizing + * the axes being resized while keeping the current + * geometry as the starting point. */ return; } @@ -141,9 +142,9 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) } /* - * If tiled or maximized in only one direction, reset - * tiled state and un-maximize the relevant axes, but - * keep the same geometry as the starting point. + * If tiled or maximized, reset tiled state and un-maximize + * the axes that are being resized, but keep the same + * geometry as the starting point. */ enum view_axis maximized = view->maximized; if (server.resize_edges & LAB_EDGES_LEFT_RIGHT) { From 20540d76f9976988ccf2dc1ecfeb5e2fb65fe18b Mon Sep 17 00:00:00 2001 From: elviosak <33790211+elviosak@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:04:43 -0300 Subject: [PATCH 16/71] fix docs typo --- docs/labwc-actions.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index de1dc00d..fb616340 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -514,7 +514,7 @@ Actions that execute other actions. Used in keyboard/mouse bindings. "right-occupied" directions will not wrap. *tiled* [up|right|down|left|up-left|up-right|down-left|down-right|center|any] - Whether the client is tiled (snapped) along the the + Whether the client is tiled (snapped) along the indicated screen edge. *tiled_region* From 7d264c907fd23f4fa6ba2a190a4e250590d58a59 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 26 Apr 2026 18:56:03 +0900 Subject: [PATCH 17/71] src/cycle/cycle.c: fix typo --- src/cycle/cycle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 77195176..2649f1e5 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -167,7 +167,7 @@ cycle_begin(enum lab_cycle_dir direction, struct view *active_view = server.active_view; if (active_view && active_view->cycle_link.next) { - /* Select the active view it's in the cycle list */ + /* Select the active view if it's in the cycle list */ server.cycle.selected_view = active_view; } else { /* Otherwise, select the first view in the cycle list */ From 5c7bfe3c67d27934f345d50239bc7084a03193d3 Mon Sep 17 00:00:00 2001 From: diniamo Date: Sat, 25 Apr 2026 20:44:02 +0200 Subject: [PATCH 18/71] Add onbutton scrollMethod, scrollButton --- docs/labwc-config.5.scd | 12 +++++++++--- docs/rc.xml.all | 3 ++- include/config/libinput.h | 1 + src/config/libinput.c | 1 + src/config/rcxml.c | 10 ++++++++++ src/seat.c | 10 ++++++++++ 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 32943eb7..6f198532 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -1140,6 +1140,7 @@ Note: To rotate touch events with output rotation, use the libinput + 1.0 @@ -1244,19 +1245,24 @@ Note: To rotate touch events with output rotation, use the libinput The default method depends on the touchpad hardware. -** [none|twofinger|edge] - Configure the method by which physical movements on a touchpad are - mapped to scroll events. +** [none|twofinger|edge|onbutton] + Configure the method by which physical movements are mapped to scroll events. The scroll methods available are: - *twofinger* - Scroll by two fingers being placed on the surface of the touchpad, then moving those fingers vertically or horizontally. - *edge* - Scroll by moving a single finger along the right edge (vertical scroll) or bottom edge (horizontal scroll). + - *onbutton* - Scroll by pressing a button. - *none* - No scroll events will be produced. The default method depends on the touchpad hardware. +** [button] + Set the button used for the *onbutton* scroll method. + + *button* is the decimal form of a value from `linux/input-event-codes.h`. + ** [yes|no|disabledOnExternalMouse] Optionally enable or disable sending any device events. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 9ddd69a8..61ea121a 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -592,7 +592,7 @@ - accelProfile [flat|adaptive] - tapButtonMap [lrm|lmr] - clickMethod [none|buttonAreas|clickfinger] - - scrollMethod [twoFinger|edge|none] + - scrollMethod [twoFinger|edge|onbutton|none] - sendEventsMode [yes|no|disabledOnExternalMouse] - calibrationMatrix [six float values split by space] - scrollFactor [float] @@ -618,6 +618,7 @@ + 1.0 diff --git a/include/config/libinput.h b/include/config/libinput.h index 80b3fc10..077bc011 100644 --- a/include/config/libinput.h +++ b/include/config/libinput.h @@ -31,6 +31,7 @@ struct libinput_category { int dwt; /* -1 or libinput_config_dwt_state */ int click_method; /* -1 or libinput_config_click_method */ int scroll_method; /* -1 or libinput_config_scroll_method */ + int scroll_button; /* -1 or a button from linux/input_event_codes.h */ int send_events_mode; /* -1 or libinput_config_send_events_mode */ bool have_calibration_matrix; double scroll_factor; diff --git a/src/config/libinput.c b/src/config/libinput.c index 1209d267..59bf469e 100644 --- a/src/config/libinput.c +++ b/src/config/libinput.c @@ -25,6 +25,7 @@ libinput_category_init(struct libinput_category *l) l->dwt = -1; l->click_method = -1; l->scroll_method = -1; + l->scroll_button = -1; l->send_events_mode = -1; l->have_calibration_matrix = false; l->scroll_factor = 1.0; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 20c236b4..e3187af2 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -891,9 +891,19 @@ fill_libinput_category(xmlNode *node) } else if (!strcasecmp(content, "twofinger")) { category->scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + } else if (!strcasecmp(content, "onbutton")) { + category->scroll_method = + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; } else { wlr_log(WLR_ERROR, "invalid scrollMethod"); } + } else if (!strcasecmp(key, "scrollButton")) { + int button = atoi(content); + if (button != 0) { + category->scroll_button = button; + } else { + wlr_log(WLR_ERROR, "invalid scrollButton"); + } } else if (!strcasecmp(key, "sendEventsMode")) { category->send_events_mode = get_send_events_mode(content); diff --git a/src/seat.c b/src/seat.c index 93d108be..fe85dc26 100644 --- a/src/seat.c +++ b/src/seat.c @@ -328,6 +328,16 @@ configure_libinput(struct wlr_input_device *wlr_input_device) libinput_device_config_scroll_set_method(libinput_dev, dc->scroll_method); } + libinput_device_config_scroll_set_button(libinput_dev, + libinput_device_config_scroll_get_default_button(libinput_dev)); + if (dc->scroll_button < 0) { + wlr_log(WLR_INFO, "scroll button not configured"); + } else { + wlr_log(WLR_INFO, "scroll button configured (%d)", + dc->scroll_button); + libinput_device_config_scroll_set_button(libinput_dev, dc->scroll_button); + } + libinput_device_config_send_events_set_mode(libinput_dev, libinput_device_config_send_events_get_default_mode(libinput_dev)); if ((dc->send_events_mode != LIBINPUT_CONFIG_SEND_EVENTS_ENABLED From d8119cb3546db179d289ebd1dd26e0eb596740ad Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Mon, 20 Apr 2026 15:53:34 +0200 Subject: [PATCH 19/71] build: ship labwc-session.target systemd user unit Add a small systemd user target modelled on miracle-wm-session.target. It binds to graphical-session.target and orders after graphical-session-pre.target, so systemd user services declaring WantedBy=graphical-session.target (panels, portals, notification daemons, ...) start and stop in sync with a labwc session. Installed into $systemduserunitdir when the systemd dependency is available at configure time; on systems without systemd the install is skipped and labwc's runtime activation of the target fails gracefully. --- data/labwc-session.target | 6 ++++++ meson.build | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 data/labwc-session.target diff --git a/data/labwc-session.target b/data/labwc-session.target new file mode 100644 index 00000000..e71f20e5 --- /dev/null +++ b/data/labwc-session.target @@ -0,0 +1,6 @@ +[Unit] +Description=labwc session +Documentation=man:labwc(1) man:systemd.special(7) +BindsTo=graphical-session.target +Wants=graphical-session-pre.target +After=graphical-session-pre.target diff --git a/meson.build b/meson.build index 2c2d8e57..e2d2f585 100644 --- a/meson.build +++ b/meson.build @@ -211,6 +211,15 @@ install_data('data/labwc.desktop', install_dir: get_option('datadir') / 'wayland install_data('data/labwc-portals.conf', install_dir: get_option('datadir') / 'xdg-desktop-portal') +# Install labwc-session.target so that systemd user services with +# WantedBy=graphical-session.target start under labwc. Labwc activates +# this target itself in src/config/session.c on autostart. +systemd = dependency('systemd', required: false) +if systemd.found() + install_data('data/labwc-session.target', + install_dir: systemd.get_variable('systemduserunitdir')) +endif + icons = ['labwc-symbolic.svg', 'labwc.svg'] foreach icon : icons icon_path = join_paths('data', icon) From af277b09ed465691fff2915663b6f1dad7de62a9 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 21 Apr 2026 16:31:30 +0200 Subject: [PATCH 20/71] docs: document labwc-session.target integration Describe the shipped labwc-session.target in labwc(1) SESSION MANAGEMENT and add commented-out systemctl start/stop lines to the example autostart and shutdown files. Users on systemd-based distros can uncomment these to pull in graphical-session.target when labwc starts and tear it down cleanly on exit, without labwc itself mandating any specific init system. --- docs/autostart | 8 ++++++++ docs/labwc.1.scd | 19 +++++++++++++++++++ docs/shutdown | 8 ++++++++ 3 files changed, 35 insertions(+) diff --git a/docs/autostart b/docs/autostart index b045ed82..17fcc270 100644 --- a/docs/autostart +++ b/docs/autostart @@ -1,5 +1,13 @@ # Example autostart file +# When running under systemd, uncomment the systemctl line below to pull in +# graphical-session.target via labwc-session.target. This lets systemd user +# services declaring WantedBy=graphical-session.target (panels, portals, +# notification daemons, etc.) start in sync with the labwc session. Enable +# individual services with: systemctl --user enable +# +# systemctl --user --no-block start labwc-session.target + # Set background color. swaybg -c '#113344' >/dev/null 2>&1 & diff --git a/docs/labwc.1.scd b/docs/labwc.1.scd index 2dab30a5..31f28c19 100644 --- a/docs/labwc.1.scd +++ b/docs/labwc.1.scd @@ -118,6 +118,25 @@ this is accomplished by setting the session variables to empty strings. For systemd, the command `systemctl --user unset-environment` will be invoked to actually remove the variables from the activation environment. +A systemd user unit named `labwc-session.target` is also shipped alongside +the compositor for users who want to integrate labwc with systemd. It binds +to the standard `graphical-session.target`, so systemd user services can +start and stop in sync with the labwc session when they declare a WantedBy +or PartOf relationship to that target. Labwc does not activate the target +itself; users opt in by adding lines like the following to their +*autostart* and *shutdown* files: + +``` +systemctl --user --no-block start labwc-session.target +systemctl --user stop graphical-session.target +``` + +The example *autostart* and *shutdown* files shipped with labwc include +these commented out. To have a user service automatically started with +the session, enable it so the corresponding symlink under the +graphical-session.target.wants directory exists, for example by running +"systemctl --user enable dms.service". + # ENVIRONMENT VARIABLES Set the environment variables listed below to enable specific debug options. diff --git a/docs/shutdown b/docs/shutdown index feed6508..a036ff53 100644 --- a/docs/shutdown +++ b/docs/shutdown @@ -3,3 +3,11 @@ # This file is executed as a shell script when labwc is preparing to terminate # itself. # For further details see labwc-config(5). + +# When running under systemd, uncomment the systemctl line below to tear down +# graphical-session.target (which cascades to labwc-session.target via +# BindsTo, and to any service declaring PartOf=graphical-session.target). +# Running synchronously here ensures those services are stopped before the +# Wayland socket goes away, avoiding "Broken pipe" failures on teardown. +# +# systemctl --user stop graphical-session.target From 70e3173f9993ef002ec8a4e8e809a7a8be612629 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Fri, 24 Apr 2026 10:27:55 +0200 Subject: [PATCH 21/71] build: add systemd-session feature option Let distributors opt out of installing labwc-session.target at configure time. Default is 'auto' (install if the systemd pkg-config file is present), 'enabled' forces it on, 'disabled' skips it entirely. --- meson.build | 8 +++++--- meson_options.txt | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index e2d2f585..7e345ba6 100644 --- a/meson.build +++ b/meson.build @@ -212,9 +212,11 @@ install_data('data/labwc.desktop', install_dir: get_option('datadir') / 'wayland install_data('data/labwc-portals.conf', install_dir: get_option('datadir') / 'xdg-desktop-portal') # Install labwc-session.target so that systemd user services with -# WantedBy=graphical-session.target start under labwc. Labwc activates -# this target itself in src/config/session.c on autostart. -systemd = dependency('systemd', required: false) +# WantedBy=graphical-session.target can be started and stopped in sync +# with a labwc session (see labwc(1) SESSION MANAGEMENT for the opt-in +# autostart/shutdown snippet). +systemd_feat = get_option('systemd-session') +systemd = dependency('systemd', required: systemd_feat) if systemd.found() install_data('data/labwc-session.target', install_dir: systemd.get_variable('systemduserunitdir')) diff --git a/meson_options.txt b/meson_options.txt index a47efa86..e059220d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,6 +4,7 @@ option('svg', type: 'feature', value: 'enabled', description: 'Enable svg window option('icon', type: 'feature', value: 'enabled', description: 'Enable window icons') option('labnag', type: 'feature', value: 'auto', description: 'Build labnag notification daemon') option('nls', type: 'feature', value: 'auto', description: 'Enable native language support') +option('systemd-session', type: 'feature', value: 'auto', description: 'Install labwc-session.target systemd user unit') option('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer') option('test', type: 'feature', value: 'disabled', description: 'Run tests') option('sections', type: 'feature', value: 'disabled', description: 'Show unused functions') From 7b3f37725fac33affe6d013c69f9b59bb140d5c8 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 15 Apr 2026 14:34:37 +0200 Subject: [PATCH 22/71] rcxml: add raiseOnFocusDelay option Add a new element accepting an integer in milliseconds. The default of 0 preserves the current behavior (raise immediately when raiseOnFocus is enabled). The new field is carried as uint32_t raise_on_focus_delay_ms on struct rcxml. This commit only adds the parser and default; the actual delay logic follows in a subsequent commit. --- include/config/rcxml.h | 1 + src/config/rcxml.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 8a2c606c..3ef7bd67 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -90,6 +90,7 @@ struct rcxml { bool focus_follow_mouse; bool focus_follow_mouse_requires_movement; bool raise_on_focus; + uint32_t raise_on_focus_delay_ms; /* theme */ char *theme_name; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index e3187af2..d0bb76db 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1195,6 +1195,9 @@ entry(xmlNode *node, char *nodename, char *content) set_bool(content, &rc.focus_follow_mouse_requires_movement); } else if (!strcasecmp(nodename, "raiseOnFocus.focus")) { set_bool(content, &rc.raise_on_focus); + } else if (!strcasecmp(nodename, "raiseOnFocusDelay.focus")) { + long val = strtol(content, NULL, 10); + rc.raise_on_focus_delay_ms = val > 0 ? (uint32_t)val : 0; } else if (!strcasecmp(nodename, "doubleClickTime.mouse")) { long doubleclick_time_parsed = strtol(content, NULL, 10); if (doubleclick_time_parsed > 0) { @@ -1530,6 +1533,7 @@ rcxml_init(void) rc.focus_follow_mouse = false; rc.focus_follow_mouse_requires_movement = true; rc.raise_on_focus = false; + rc.raise_on_focus_delay_ms = 0; rc.doubleclick_time = 500; From fe6ea66b8221ac77edba84e9efe30ef417553804 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 15 Apr 2026 14:35:27 +0200 Subject: [PATCH 23/71] server: add pending auto-raise state + decl Add two fields to struct server: struct view *pending_auto_raise_view; struct wl_event_source *pending_auto_raise_timer; and forward-declare desktop_cancel_pending_auto_raise() in labwc.h. The state is a single 'slot' (at most one view/timer pending) since a new focus change supersedes any previous pending raise. This commit just reserves the state and the public API; the behaviour is implemented in the following commit. --- include/labwc.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/labwc.h b/include/labwc.h index 511fdd65..dc4c1311 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -150,6 +150,11 @@ struct seat { struct server { struct wl_display *wl_display; struct wl_event_loop *wl_event_loop; /* Can be used for timer events */ + + /* Pending auto-raise timer (used when rc.raise_on_focus_delay_ms > 0) */ + struct view *pending_auto_raise_view; + struct wl_event_source *pending_auto_raise_timer; + struct wlr_renderer *renderer; struct wlr_allocator *allocator; struct wlr_backend *backend; @@ -343,6 +348,13 @@ void xdg_shell_finish(void); */ void desktop_focus_view(struct view *view, bool raise); +/** + * desktop_cancel_pending_auto_raise() - cancel any pending delayed auto-raise + * (from raiseOnFocusDelay). Called when a view is being destroyed, on config + * reload, or when a new focus change with raise=false supersedes the pending. + */ +void desktop_cancel_pending_auto_raise(void); + /** * desktop_focus_view_or_surface() - like desktop_focus_view() but can * also focus other (e.g. xwayland-unmanaged) surfaces From 28908adfbecb5415aa99b21c4edf00f225e67feb Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 15 Apr 2026 14:36:14 +0200 Subject: [PATCH 24/71] desktop: implement delayed auto-raise timer logic When raiseOnFocus is enabled and raiseOnFocusDelay is > 0, defer view_move_to_front() until a wl_event_loop timer elapses instead of raising immediately. The pending view/timer pair lives on struct server so that at most one raise can be pending per compositor. Semantics: delay_ms == 0 : immediate raise (preserves prior behavior) delay_ms > 0, hover A : schedule raise of A after delay_ms another hover before : timer is reset and the new view becomes expiry the pending one (brief flyby hovers do not stack up raises) focus change, raise=false: pending raise is cancelled This is most useful with followMouse to avoid brief passes of the cursor stacking up z-order changes. --- src/desktop.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/desktop.c b/src/desktop.c index 4c7870f1..966bdaab 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -9,6 +9,7 @@ #include #include #include "common/scene-helpers.h" +#include "config/rcxml.h" #include "dnd.h" #include "labwc.h" #include "layers.h" @@ -65,6 +66,49 @@ set_or_offer_focus(struct view *view) } } +static int +handle_auto_raise_timer(void *data) +{ + (void)data; + struct view *view = server.pending_auto_raise_view; + server.pending_auto_raise_view = NULL; + + if (view && view->mapped) { + view_move_to_front(view); + } + return 0; /* ignored per wl_event_loop docs */ +} + +void +desktop_cancel_pending_auto_raise(void) +{ + server.pending_auto_raise_view = NULL; + if (server.pending_auto_raise_timer) { + /* Disarm by setting to 0 ms */ + wl_event_source_timer_update(server.pending_auto_raise_timer, 0); + } +} + +static void +schedule_auto_raise(struct view *view) +{ + if (rc.raise_on_focus_delay_ms == 0) { + /* Immediate raise — preserves original behavior */ + desktop_cancel_pending_auto_raise(); + view_move_to_front(view); + return; + } + + server.pending_auto_raise_view = view; + if (!server.pending_auto_raise_timer) { + server.pending_auto_raise_timer = + wl_event_loop_add_timer(server.wl_event_loop, + handle_auto_raise_timer, NULL); + } + wl_event_source_timer_update(server.pending_auto_raise_timer, + rc.raise_on_focus_delay_ms); +} + void desktop_focus_view(struct view *view, bool raise) { @@ -104,7 +148,13 @@ desktop_focus_view(struct view *view, bool raise) } if (raise) { - view_move_to_front(view); + schedule_auto_raise(view); + } else { + /* + * A new focus change without a raise supersedes any + * pending auto-raise from a previous focus event. + */ + desktop_cancel_pending_auto_raise(); } /* From ecc55656868a4f7e9350d7487ef91b33c259a125 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 15 Apr 2026 14:36:33 +0200 Subject: [PATCH 25/71] view: cancel pending auto-raise on view destroy The pending_auto_raise_view pointer would become dangling if the view it references is destroyed before the timer fires. Clear it in view_destroy() alongside the existing active_view cleanup. --- src/view.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/view.c b/src/view.c index 1d43fcfa..9b6604ad 100644 --- a/src/view.c +++ b/src/view.c @@ -2521,6 +2521,10 @@ view_destroy(struct view *view) server.active_view = NULL; } + if (server.pending_auto_raise_view == view) { + desktop_cancel_pending_auto_raise(); + } + if (server.session_lock_manager->last_active_view == view) { server.session_lock_manager->last_active_view = NULL; } From 9661ed4285e7f7298851a45c0770cd2b9c9839b9 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 15 Apr 2026 14:36:56 +0200 Subject: [PATCH 26/71] server: cancel pending auto-raise on config reload If the user disables raiseOnFocus or lowers raiseOnFocusDelay while a raise is queued, the queued raise should not fire against the new config. Cancel it in reload_config_and_theme() before rereading the rc.xml. --- src/server.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server.c b/src/server.c index e4b27026..c792d8b4 100644 --- a/src/server.c +++ b/src/server.c @@ -89,6 +89,12 @@ reload_config_and_theme(void) /* Avoid UAF when dialog client is used during reconfigure */ action_prompts_destroy(); + /* + * Cancel any pending auto-raise before reloading config in case the + * raiseOnFocusDelay option was disabled or changed. + */ + desktop_cancel_pending_auto_raise(); + scaled_buffer_invalidate_sharing(); rcxml_finish(); rcxml_read(rc.config_file); From 6237e26a1d44ca88e27b09c41619df51de6aae81 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 15 Apr 2026 14:37:29 +0200 Subject: [PATCH 27/71] docs: document raiseOnFocusDelay Add the new option to labwc-config(5) and the example rc.xml.all. --- docs/labwc-config.5.scd | 7 +++++++ docs/rc.xml.all | 2 ++ 2 files changed, 9 insertions(+) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 6f198532..8ea88d8f 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -493,6 +493,13 @@ this is for compatibility with Openbox. ** [yes|no] Raise window to top when focused. Default is no. +** [milliseconds] + When raiseOnFocus is enabled, delay the actual raise by this many + milliseconds. Default is 0 (raise immediately). A subsequent focus + change before the timer elapses restarts or cancels the pending raise. + Useful together with followMouse to avoid brief passes of the cursor + stacking up z-order changes. + ## WINDOW SNAPPING Windows may be "snapped" to an edge or user-defined region of an output when diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 61ea121a..9755530b 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -158,6 +158,8 @@ no yes no + + 0 From e61d58e54dd7b4c751f312ab86d305e0a7b77488 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sat, 18 Apr 2026 20:54:28 +0200 Subject: [PATCH 28/71] desktop: only apply raise-on-focus delay to sloppy-focus The raise_on_focus_delay is meant to dampen z-order churn from focus-follows-mouse cursor passes. Applying it to every focus change meant explicit actions (alt-tab cycle finish, Focus action, xdg/xwayland activation, view map, etc.) also waited for the delay before raising, which felt laggy. Route all non-sloppy-focus callers through an immediate raise and keep the timer-based raise only for desktop_focus_view_or_surface(), which is the sloppy-focus entry point. --- src/desktop.c | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/desktop.c b/src/desktop.c index 966bdaab..57ef9e3c 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -90,15 +90,8 @@ desktop_cancel_pending_auto_raise(void) } static void -schedule_auto_raise(struct view *view) +schedule_delayed_auto_raise(struct view *view) { - if (rc.raise_on_focus_delay_ms == 0) { - /* Immediate raise — preserves original behavior */ - desktop_cancel_pending_auto_raise(); - view_move_to_front(view); - return; - } - server.pending_auto_raise_view = view; if (!server.pending_auto_raise_timer) { server.pending_auto_raise_timer = @@ -109,8 +102,15 @@ schedule_auto_raise(struct view *view) rc.raise_on_focus_delay_ms); } -void -desktop_focus_view(struct view *view, bool raise) +/* + * The raise_on_focus_delay is only meant to dampen z-order churn from + * focus-follows-mouse cursor passes. Explicit focus changes (alt-tab, + * Focus action, xdg/xwayland activation, etc.) should raise immediately. + * allow_delay is therefore only set when the caller is the sloppy-focus + * path in desktop_focus_view_or_surface(). + */ +static void +desktop_focus_view_internal(struct view *view, bool raise, bool allow_delay) { assert(view); /* @@ -147,14 +147,17 @@ desktop_focus_view(struct view *view, bool raise) workspaces_switch_to(view->workspace, /*update_focus*/ false); } + /* + * A new focus change supersedes any pending auto-raise from a + * previous focus event, regardless of whether we raise now. + */ + desktop_cancel_pending_auto_raise(); if (raise) { - schedule_auto_raise(view); - } else { - /* - * A new focus change without a raise supersedes any - * pending auto-raise from a previous focus event. - */ - desktop_cancel_pending_auto_raise(); + if (allow_delay && rc.raise_on_focus_delay_ms > 0) { + schedule_delayed_auto_raise(view); + } else { + view_move_to_front(view); + } } /* @@ -168,6 +171,12 @@ desktop_focus_view(struct view *view, bool raise) show_desktop_reset(); } +void +desktop_focus_view(struct view *view, bool raise) +{ + desktop_focus_view_internal(view, raise, /*allow_delay*/ false); +} + /* TODO: focus layer-shell surfaces also? */ void desktop_focus_view_or_surface(struct seat *seat, struct view *view, @@ -175,7 +184,7 @@ desktop_focus_view_or_surface(struct seat *seat, struct view *view, { assert(view || surface); if (view) { - desktop_focus_view(view, raise); + desktop_focus_view_internal(view, raise, /*allow_delay*/ true); #if HAVE_XWAYLAND } else { struct wlr_xwayland_surface *xsurface = From de3870246a06ed083db71d9069cd1bbcc6fa3aa3 Mon Sep 17 00:00:00 2001 From: stormshadow <190884359+st0rm-shad0w@users.noreply.github.com> Date: Fri, 24 Apr 2026 05:42:11 +0530 Subject: [PATCH 29/71] labnag: remove +1 offset from button Y-position calculation It essentially removes an awkward top margin from the button on the main labnag bar. --- clients/labnag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/labnag.c b/clients/labnag.c index 82b6e8cd..5abd989e 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -447,7 +447,7 @@ render_button(cairo_t *cairo, struct nag *nag, struct button *button, } button->x = *x - border - text_width - padding * 2 + 1; - button->y = (int)(ideal_height - text_height) / 2 - padding + 1; + button->y = (int)(ideal_height - text_height) / 2 - padding; button->width = text_width + padding * 2; button->height = text_height + padding * 2; From 91d89f71ce8bd46e7a6b149a1e1006cce29350b8 Mon Sep 17 00:00:00 2001 From: stormshadow <190884359+st0rm-shad0w@users.noreply.github.com> Date: Fri, 24 Apr 2026 06:16:23 +0530 Subject: [PATCH 30/71] labnag: add details border color and margin options Adds --details-border-color and --details-margin command line options to configure the border color and margin of the details pane. --- clients/labnag.c | 37 ++++++++++++++++++++++++++++++++----- docs/labnag.1.scd | 6 ++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/clients/labnag.c b/clients/labnag.c index 5abd989e..5fe5164f 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -46,6 +46,7 @@ struct conf { uint32_t button_text; uint32_t button_background; uint32_t details_background; + uint32_t details_border_color; uint32_t background; uint32_t text; uint32_t button_border; @@ -60,6 +61,7 @@ struct conf { ssize_t button_gap_close; ssize_t button_margin_right; ssize_t button_padding; + ssize_t details_margin; }; struct pointer { @@ -343,8 +345,9 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) uint32_t width = nag->width; int border = nag->conf->details_border_thickness; + int margin = nag->conf->details_margin; int padding = nag->conf->message_padding; - int decor = padding + border; + int decor = margin + border; nag->details.x = decor; nag->details.y = y + decor; @@ -401,6 +404,8 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) nag->details.visible_lines = pango_layout_get_line_count(layout); + int border_rect_height = nag->details.height + 2 * border; + if (show_buttons) { nag->details.button_up.x = nag->details.x + nag->details.width; nag->details.button_up.y = nag->details.y; @@ -416,6 +421,11 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) render_details_scroll_button(cairo, nag, &nag->details.button_down); } + cairo_set_source_u32(cairo, nag->conf->details_border_color); + cairo_rectangle(cairo, margin, nag->details.y - border, + nag->details.width + 2 * border, border_rect_height); + cairo_fill(cairo); + cairo_set_source_u32(cairo, nag->conf->details_background); cairo_rectangle(cairo, nag->details.x, nag->details.y, nag->details.width, nag->details.height); @@ -1464,14 +1474,16 @@ conf_init(struct conf *conf) conf->keyboard_focus = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; conf->bar_border_thickness = 2; conf->message_padding = 8; - conf->details_border_thickness = 3; conf->button_border_thickness = 3; conf->button_gap = 20; conf->button_gap_close = 15; conf->button_margin_right = 2; conf->button_padding = 3; conf->button_background = 0x680A0AFF; + conf->details_margin = 11; + conf->details_border_thickness = 3; conf->details_background = 0x680A0AFF; + conf->details_border_color = 0x680A0AFF; conf->background = 0x900000FF; conf->text = 0xFFFFFFFF; conf->button_text = 0xFFFFFFFF; @@ -1551,16 +1563,18 @@ nag_parse_options(int argc, char **argv, struct nag *nag, TO_COLOR_BORDER_BOTTOM, TO_COLOR_BUTTON_BG, TO_COLOR_DETAILS, + TO_COLOR_DETAILS_BORDER, TO_COLOR_TEXT, TO_COLOR_BUTTON_TEXT, TO_THICK_BAR_BORDER, TO_PADDING_MESSAGE, - TO_THICK_DET_BORDER, + TO_THICK_DETAILS_BORDER, TO_THICK_BTN_BORDER, TO_GAP_BTN, TO_GAP_BTN_DISMISS, TO_MARGIN_BTN_RIGHT, TO_PADDING_BTN, + TO_MARGIN_DETAILS, }; static const struct option opts[] = { @@ -1587,8 +1601,10 @@ nag_parse_options(int argc, char **argv, struct nag *nag, {"button-text-color", required_argument, NULL, TO_COLOR_BUTTON_TEXT}, {"border-bottom-size", required_argument, NULL, TO_THICK_BAR_BORDER}, {"message-padding", required_argument, NULL, TO_PADDING_MESSAGE}, - {"details-border-size", required_argument, NULL, TO_THICK_DET_BORDER}, + {"details-border-size", required_argument, NULL, TO_THICK_DETAILS_BORDER}, {"details-background-color", required_argument, NULL, TO_COLOR_DETAILS}, + {"details-border-color", required_argument, NULL, TO_COLOR_DETAILS_BORDER}, + {"details-margin", required_argument, NULL, TO_MARGIN_DETAILS}, {"button-border-size", required_argument, NULL, TO_THICK_BTN_BORDER}, {"button-gap", required_argument, NULL, TO_GAP_BTN}, {"button-dismiss-gap", required_argument, NULL, TO_GAP_BTN_DISMISS}, @@ -1633,6 +1649,9 @@ nag_parse_options(int argc, char **argv, struct nag *nag, " --details-border-size size Thickness for the details border.\n" " --details-background-color RRGGBB[AA]\n" " Details background color.\n" + " --details-border-color RRGGBB[AA]\n" + " Details border color.\n" + " --details-margin margin Margin for the details.\n" " --button-border-size size Thickness for the button border.\n" " --button-gap gap Size of the gap between buttons\n" " --button-dismiss-gap gap Size of the gap for dismiss button.\n" @@ -1769,6 +1788,11 @@ nag_parse_options(int argc, char **argv, struct nag *nag, fprintf(stderr, "Invalid details background color: %s\n", optarg); } break; + case TO_COLOR_DETAILS_BORDER: + if (!parse_color(optarg, &conf->details_border_color)) { + fprintf(stderr, "Invalid details border color: %s\n", optarg); + } + break; case TO_COLOR_TEXT: /* Text color */ if (!parse_color(optarg, &conf->text)) { fprintf(stderr, "Invalid text color: %s\n", optarg); @@ -1785,7 +1809,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, case TO_PADDING_MESSAGE: /* Message padding */ conf->message_padding = strtol(optarg, NULL, 0); break; - case TO_THICK_DET_BORDER: /* Details border thickness */ + case TO_THICK_DETAILS_BORDER: /* Details border thickness */ conf->details_border_thickness = strtol(optarg, NULL, 0); break; case TO_THICK_BTN_BORDER: /* Button border thickness */ @@ -1803,6 +1827,9 @@ nag_parse_options(int argc, char **argv, struct nag *nag, case TO_PADDING_BTN: /* Padding for the button text */ conf->button_padding = strtol(optarg, NULL, 0); break; + case TO_MARGIN_DETAILS: + conf->details_margin = strtol(optarg, NULL, 0); + break; default: /* Help or unknown flag */ fprintf(c == 'h' ? stdout : stderr, "%s", usage); return LAB_EXIT_FAILURE; diff --git a/docs/labnag.1.scd b/docs/labnag.1.scd index 6d4b959f..bf237d00 100644 --- a/docs/labnag.1.scd +++ b/docs/labnag.1.scd @@ -95,6 +95,12 @@ _labnag_ [options...] *--details-border-size* Set the thickness for the details border. +*--details-border-color* + Set the color of the details border. + +*--details-margin* + Set the margin for the details. + *--button-border-size* Set the thickness for the button border. From e209de3eb19c560cf5e0eb16ba7523941b12b67a Mon Sep 17 00:00:00 2001 From: stormshadow <190884359+st0rm-shad0w@users.noreply.github.com> Date: Fri, 24 Apr 2026 07:40:01 +0530 Subject: [PATCH 31/71] labnag: separate details scroll button styling from regular buttons Use details-specific border thickness and color config options instead of regular button options. Adjust padding for a more compact look on details scroll buttons. --- clients/labnag.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clients/labnag.c b/clients/labnag.c index 5fe5164f..99c44d59 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -302,10 +302,10 @@ render_details_scroll_button(cairo_t *cairo, struct nag *nag, get_text_size(cairo, nag->conf->font_description, &text_width, &text_height, NULL, 1, true, "%s", button->text); - int border = nag->conf->button_border_thickness; - int padding = nag->conf->button_padding; + int border = nag->conf->details_border_thickness; + int padding = (nag->conf->button_padding / 3) + 2; - cairo_set_source_u32(cairo, nag->conf->details_background); + cairo_set_source_u32(cairo, nag->conf->details_border_color); cairo_rectangle(cairo, button->x, button->y, button->width, button->height); cairo_fill(cairo); @@ -333,8 +333,8 @@ get_detailed_scroll_button_width(cairo_t *cairo, struct nag *nag) NULL, 1, true, "%s", nag->details.button_down.text); int text_width = up_width > down_width ? up_width : down_width; - int border = nag->conf->button_border_thickness; - int padding = nag->conf->button_padding; + int border = nag->conf->details_border_thickness; + int padding = (nag->conf->button_padding / 3) + 2; return text_width + border * 2 + padding * 2; } From ccdef5e854dd59d9bc67e7f983f21d79d1729504 Mon Sep 17 00:00:00 2001 From: stormshadow <190884359+st0rm-shad0w@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:58:06 +0530 Subject: [PATCH 32/71] labnag: fix details scroll button group height calculation Previously the scroll button group height was shorter than intended as it was calculated using details.height instead of the border dimensions. Calculate button heights using border_rect_height to properly fill the bordered region. --- clients/labnag.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clients/labnag.c b/clients/labnag.c index 99c44d59..a3a526c5 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -375,7 +375,7 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) bool show_buttons = nag->details.offset > 0; int button_width = get_detailed_scroll_button_width(cairo, nag); if (show_buttons) { - nag->details.width -= button_width; + nag->details.width += border - button_width; pango_layout_set_width(layout, (nag->details.width - padding * 2) * PANGO_SCALE); } @@ -388,7 +388,7 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) if (!show_buttons) { show_buttons = true; - nag->details.width -= button_width; + nag->details.width += border - button_width; pango_layout_set_width(layout, (nag->details.width - padding * 2) * PANGO_SCALE); } @@ -408,16 +408,17 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) if (show_buttons) { nag->details.button_up.x = nag->details.x + nag->details.width; - nag->details.button_up.y = nag->details.y; + nag->details.button_up.y = nag->details.y - border; nag->details.button_up.width = button_width; - nag->details.button_up.height = nag->details.height / 2; + nag->details.button_up.height = (border_rect_height + border) / 2; render_details_scroll_button(cairo, nag, &nag->details.button_up); nag->details.button_down.x = nag->details.x + nag->details.width; nag->details.button_down.y = nag->details.button_up.y + nag->details.button_up.height; nag->details.button_down.width = button_width; - nag->details.button_down.height = nag->details.height / 2; + nag->details.button_down.height = + border_rect_height - nag->details.button_up.height; render_details_scroll_button(cairo, nag, &nag->details.button_down); } From 7ec7a322d24f488515e307c4babe6b0536c493c8 Mon Sep 17 00:00:00 2001 From: stormshadow <190884359+st0rm-shad0w@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:09:44 +0530 Subject: [PATCH 33/71] labnag: remove doubled border between scroll buttons and adjust text position Each scroll button draws its own full border, so placing the up and down buttons flush produced a divider twice the intended thickness. Shift button_down up by one border width and extend its height accordingly so its top border overlaps button_up's bottom border. Also drop a stray \`+ border\` offset from the scroll button text Y-position that was pushing the label below the button's center. --- clients/labnag.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/labnag.c b/clients/labnag.c index a3a526c5..8d47ee29 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -318,7 +318,7 @@ render_details_scroll_button(cairo_t *cairo, struct nag *nag, cairo_set_source_u32(cairo, nag->conf->button_text); cairo_move_to(cairo, button->x + border + padding, - button->y + border + (button->height - text_height) / 2); + button->y + (button->height - text_height) / 2); render_text(cairo, nag->conf->font_description, 1, true, "%s", button->text); } @@ -415,10 +415,10 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) nag->details.button_down.x = nag->details.x + nag->details.width; nag->details.button_down.y = - nag->details.button_up.y + nag->details.button_up.height; + nag->details.button_up.y + nag->details.button_up.height - border; nag->details.button_down.width = button_width; nag->details.button_down.height = - border_rect_height - nag->details.button_up.height; + border_rect_height - nag->details.button_up.height + border; render_details_scroll_button(cairo, nag, &nag->details.button_down); } From 998fd807373ee7e28cc88717f5f20dd3f4a0c342 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 29 Apr 2026 11:14:21 +0200 Subject: [PATCH 34/71] session: run activation env update synchronously dbus-update-activation-environment and systemctl --user import-environment were fired asynchronously via spawn_async_no_shell, racing with the autostart script. Any systemd user service started from autostart (e.g. via labwc-session.target) could start before the import completed, leaving WAYLAND_DISPLAY and related variables absent from its environment and those of any apps it launches. Run both commands synchronously via a new spawn_sync_no_shell helper so the import is guaranteed to complete before the autostart script executes. --- include/common/spawn.h | 6 ++++++ src/common/spawn.c | 32 ++++++++++++++++++++++++++++++++ src/config/session.c | 4 ++-- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/include/common/spawn.h b/include/common/spawn.h index 43123269..4df5ed0b 100644 --- a/include/common/spawn.h +++ b/include/common/spawn.h @@ -16,6 +16,12 @@ pid_t spawn_primary_client(const char *command); */ void spawn_async_no_shell(char const *command); +/** + * spawn_sync_no_shell - execute synchronously + * @command: command to be executed + */ +void spawn_sync_no_shell(char const *command); + /** * spawn_piped - execute asynchronously * @command: command to be executed diff --git a/src/common/spawn.c b/src/common/spawn.c index 898c4800..a703bee5 100644 --- a/src/common/spawn.c +++ b/src/common/spawn.c @@ -89,6 +89,38 @@ out: g_strfreev(argv); } +void +spawn_sync_no_shell(char const *command) +{ + GError *err = NULL; + gchar **argv = NULL; + + assert(command); + + g_shell_parse_argv((gchar *)command, NULL, &argv, &err); + if (err) { + g_message("%s", err->message); + g_error_free(err); + return; + } + + pid_t child = fork(); + switch (child) { + case -1: + wlr_log(WLR_ERROR, "unable to fork()"); + goto out; + case 0: + reset_signals_and_limits(); + execvp(argv[0], argv); + _exit(1); + default: + waitpid(child, NULL, 0); + break; + } +out: + g_strfreev(argv); +} + pid_t spawn_primary_client(const char *command) { diff --git a/src/config/session.c b/src/config/session.c index 7df6236c..61ce156f 100644 --- a/src/config/session.c +++ b/src/config/session.c @@ -219,12 +219,12 @@ execute_update(const char *env_keys, const char *env_unset_keys, bool initialize char *cmd = strdup_printf("dbus-update-activation-environment %s", initialize ? env_keys : env_unset_keys); - spawn_async_no_shell(cmd); + spawn_sync_no_shell(cmd); free(cmd); cmd = strdup_printf("systemctl --user %s %s", initialize ? "import-environment" : "unset-environment", env_keys); - spawn_async_no_shell(cmd); + spawn_sync_no_shell(cmd); free(cmd); } From def6ff22d32c00c1291d0ee4e24b8fa9c78f67ff Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 29 Apr 2026 14:38:05 +0200 Subject: [PATCH 35/71] session: drop goto label from spawn_sync_no_shell Replace goto out with break since g_strfreev follows the switch directly. Co-Authored-By: Claude Sonnet 4.6 --- src/common/spawn.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common/spawn.c b/src/common/spawn.c index a703bee5..6073d6ae 100644 --- a/src/common/spawn.c +++ b/src/common/spawn.c @@ -108,7 +108,7 @@ spawn_sync_no_shell(char const *command) switch (child) { case -1: wlr_log(WLR_ERROR, "unable to fork()"); - goto out; + break; case 0: reset_signals_and_limits(); execvp(argv[0], argv); @@ -117,7 +117,6 @@ spawn_sync_no_shell(char const *command) waitpid(child, NULL, 0); break; } -out: g_strfreev(argv); } From df9537c809c859adf264dc4e4c171f4f14006454 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Wed, 29 Apr 2026 20:04:23 +0100 Subject: [PATCH 36/71] NEWS.md: interim update --- NEWS.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index ac001850..997f4068 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog] | Date | All Changes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| -| 2026-04-17 | [unreleased] | 0.20.0 | 27753 | +| 2026-04-29 | [unreleased] | 0.20.0 | 27849 | | 2026-04-17 | [0.9.7] | 0.19.2 | 29277 | | 2026-03-15 | [0.9.6] | 0.19.2 | 29271 | | 2026-03-04 | [0.9.5] | 0.19.2 | 29251 | @@ -125,6 +125,14 @@ Note to maintainers: ### Added +- Add labnag options `--details-border-color` and `--details-margin` + @st0rm-shad0w [#3527] +- Add config option `` to defer raise-on-focus by a + small amount when `raiseOnFocus` is enabled @joske [#3513] +- Install `labwc-session.target` systemd user unit when the systemd dependency + is available @joske [#3534] +- Add `onbutton` to config option ``. Also add + associated option ``. @diniamo [#3540] - Add `overrideInhibition` option to `` [#3507] @drougas - Add action `ToggleShowDesktop` to hide/unhide windows [#3500] @johanmalm - Add `` config option so that privileged protocols can be @@ -139,6 +147,11 @@ Note to maintainers: ### Fixed +- Run session activation environment update synchronously to avoid a race + condition with the autostart script [#3543] @joske +- Allow interactive resize on fully maximized windows so that a resize + initiated by modifier plus right-mouse-button-drag is not ignored [#3525] + @bjorn - Gracefully handle missing XWayland packages, so that a labwc compositor which has been built with XWayland support (which is optional) can be run even if XWayland is not installed. [#3401] @quite @@ -162,6 +175,8 @@ Note to maintainers: ### Changed +- Change the default keybinds for XF86Audio{LowerVolume,RaiseVolume,Mute} to use + pactl instead of amixer [#3484] @danielrrrr - Drop cosmic-workspace protocol [#3031] @tokyo4j ## 0.9.7 - 2026-04-17 @@ -3270,6 +3285,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3446]: https://github.com/labwc/labwc/pull/3446 [#3450]: https://github.com/labwc/labwc/pull/3450 [#3469]: https://github.com/labwc/labwc/pull/3469 +[#3484]: https://github.com/labwc/labwc/pull/3484 [#3490]: https://github.com/labwc/labwc/pull/3490 [#3493]: https://github.com/labwc/labwc/pull/3493 [#3494]: https://github.com/labwc/labwc/pull/3494 @@ -3278,3 +3294,9 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3502]: https://github.com/labwc/labwc/pull/3502 [#3507]: https://github.com/labwc/labwc/pull/3507 [#3511]: https://github.com/labwc/labwc/pull/3511 +[#3513]: https://github.com/labwc/labwc/pull/3513 +[#3525]: https://github.com/labwc/labwc/pull/3525 +[#3527]: https://github.com/labwc/labwc/pull/3527 +[#3534]: https://github.com/labwc/labwc/pull/3534 +[#3540]: https://github.com/labwc/labwc/pull/3540 +[#3543]: https://github.com/labwc/labwc/pull/3543 From bc7498dc6ffd634a2fb3888fc79addc2373890d5 Mon Sep 17 00:00:00 2001 From: elviosak <33790211+elviosak@users.noreply.github.com> Date: Fri, 1 May 2026 08:52:17 -0300 Subject: [PATCH 37/71] fix chromium popup on maximized window --- src/xdg-popup.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/xdg-popup.c b/src/xdg-popup.c index 04f82f9e..8e6870af 100644 --- a/src/xdg-popup.c +++ b/src/xdg-popup.c @@ -43,10 +43,13 @@ popup_unconstrain(struct xdg_popup *popup) * than zero, typically with Qt apps. We therefore clamp it to avoid for * example the 'File' menu of a maximized window to end up on an another * output. + * Also some apps open the menu exactly at the right border when maximized, + * causing popup_box->x (or y?) to be in the next output. We subtract one + * inside MAX to avoid the problem mentioned above. */ struct wlr_box *popup_box = &popup->wlr_popup->scheduled.geometry; - struct output *output = output_nearest_to(parent_lx + MAX(popup_box->x, 0), - parent_ly + MAX(popup_box->y, 0)); + struct output *output = output_nearest_to(parent_lx + MAX(popup_box->x - 1, 0), + parent_ly + MAX(popup_box->y - 1, 0)); struct wlr_box usable = output_usable_area_in_layout_coords(output); /* Get offset of toplevel window from its surface */ From 6ce25978e7328ee7263ad7642e942f68c792914f Mon Sep 17 00:00:00 2001 From: elviosak <33790211+elviosak@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:42:39 -0300 Subject: [PATCH 38/71] Add Next/PreviousWindowImmediate to switch windows without OSD Co-authored-by: @johanmalm --- docs/labwc-actions.5.scd | 7 ++++- include/cycle.h | 4 +++ include/view.h | 2 ++ src/action.c | 19 ++++++++++++++ src/cycle/cycle.c | 55 ++++++++++++++++++++++++++++++++++++++++ src/view.c | 10 ++++---- src/window-rules.c | 6 ++--- 7 files changed, 94 insertions(+), 9 deletions(-) diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index fb616340..47ee1801 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -126,13 +126,18 @@ Actions are used in menus and keyboard/mouse bindings. position if it had been maximized or tiled to a direction or region. **++ -** +**++ +**++ +**++ Cycle focus to next/previous window, respectively. Default keybinds for NextWindow and PreviousWindow are Alt-Tab and Shift-Alt-Tab. While cycling through windows, the arrow keys move the selected window forwards/backwards and the escape key halts the cycling. + NextWindowImmediate and PreviousWindowImmediate skip the Window Switcher + and OSD, useful for binding to keys without modifiers. + *workspace* [all|current] This determines whether to cycle through windows on all workspaces or the current workspace. Default is "current". diff --git a/include/cycle.h b/include/cycle.h index 9bb4cb69..440b938f 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -103,6 +103,10 @@ void cycle_finish(bool switch_focus); /* Re-initialize the window switcher */ void cycle_reinitialize(void); +/* Immediately cycle to next/previous window */ +void cycle_immediate(enum lab_cycle_dir direction, + struct cycle_filter filter); + /* Focus the clicked window and close OSD */ void cycle_on_cursor_release(struct wlr_scene_node *node); diff --git a/include/view.h b/include/view.h index da7aef02..a179338e 100644 --- a/include/view.h +++ b/include/view.h @@ -630,4 +630,6 @@ enum lab_placement_policy view_placement_parse(const char *policy); /* xdg.c */ struct wlr_xdg_surface *xdg_surface_from_view(struct view *view); +bool view_matches_criteria(struct view *view, enum lab_view_criteria criteria); + #endif /* LABWC_VIEW_H */ diff --git a/src/action.c b/src/action.c index 34435e02..daf8fb30 100644 --- a/src/action.c +++ b/src/action.c @@ -84,6 +84,8 @@ struct action_arg_list { X(SHRINK_TO_EDGE, "ShrinkToEdge") \ X(NEXT_WINDOW, "NextWindow") \ X(PREVIOUS_WINDOW, "PreviousWindow") \ + X(NEXT_WINDOW_IMMEDIATE, "NextWindowImmediate") \ + X(PREVIOUS_WINDOW_IMMEDIATE, "PreviousWindowImmediate") \ X(RECONFIGURE, "Reconfigure") \ X(SHOW_MENU, "ShowMenu") \ X(TOGGLE_MAXIMIZE, "ToggleMaximize") \ @@ -337,6 +339,8 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char break; case ACTION_TYPE_NEXT_WINDOW: case ACTION_TYPE_PREVIOUS_WINDOW: + case ACTION_TYPE_NEXT_WINDOW_IMMEDIATE: + case ACTION_TYPE_PREVIOUS_WINDOW_IMMEDIATE: if (!strcasecmp(argument, "workspace")) { if (!strcasecmp(content, "all")) { action_arg_add_int(action, argument, CYCLE_WORKSPACE_ALL); @@ -1146,6 +1150,21 @@ run_action(struct view *view, struct action *action, } break; } + case ACTION_TYPE_NEXT_WINDOW_IMMEDIATE: + case ACTION_TYPE_PREVIOUS_WINDOW_IMMEDIATE: { + enum lab_cycle_dir dir = (action->type == ACTION_TYPE_NEXT_WINDOW_IMMEDIATE) ? + LAB_CYCLE_DIR_FORWARD : LAB_CYCLE_DIR_BACKWARD; + struct cycle_filter filter = { + .workspace = action_get_int(action, "workspace", + rc.window_switcher.workspace_filter), + .output = action_get_int(action, "output", + CYCLE_OUTPUT_ALL), + .app_id = action_get_int(action, "identifier", + CYCLE_APP_ID_ALL), + }; + cycle_immediate(dir, filter); + break; + } case ACTION_TYPE_RECONFIGURE: kill(getpid(), SIGHUP); break; diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 2649f1e5..14682c9f 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -322,6 +322,61 @@ handle_osd_tree_destroy(struct wl_listener *listener, void *data) free(osd_output); } +static struct wl_list *prev(struct wl_list *elm) { return elm->prev; } +static struct wl_list *next(struct wl_list *elm) { return elm->next; } + +void +cycle_immediate(enum lab_cycle_dir direction, struct cycle_filter filter) +{ + if (wl_list_empty(&server.views)) { + return; + } + enum lab_view_criteria criteria = + LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER + | LAB_VIEW_CRITERIA_NO_DIALOG; + if (filter.workspace == CYCLE_WORKSPACE_CURRENT) { + criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; + } + uint64_t cycle_outputs = get_outputs_by_filter(filter.output); + const char *cycle_app_id = NULL; + if (filter.app_id == CYCLE_APP_ID_CURRENT && server.active_view) { + cycle_app_id = server.active_view->app_id; + } + + struct wl_list *head = &server.views; + struct wl_list *(*iter)(struct wl_list *list); + iter = direction == LAB_CYCLE_DIR_FORWARD ? next : prev; + + struct wl_list *from = (direction == LAB_CYCLE_DIR_FORWARD) && server.active_view + ? &server.active_view->link : head; + + for (struct wl_list *elm = iter(from); elm != head; elm = iter(elm)) { + struct view *view = wl_container_of(elm, view, link); + if (!view_matches_criteria(view, criteria)) { + continue; + } + if (filter.output != CYCLE_OUTPUT_ALL) { + if (!view->output || !(cycle_outputs & view->output->id_bit)) { + continue; + } + } + if (cycle_app_id && strcmp(view->app_id, cycle_app_id) != 0) { + continue; + } + if (server.active_view && direction == LAB_CYCLE_DIR_FORWARD) { + /* + * When cycling forward, the current active view needs to be + * sent to back to keep the same sequence and avoid getting + * stuck in the 2 topmost views. + */ + view_move_to_back(server.active_view); + } + desktop_focus_view(view, true); + break; + } + cursor_update_focus(); +} + /* Return false on failure */ static bool init_cycle(struct cycle_filter filter) diff --git a/src/view.c b/src/view.c index 9b6604ad..bcbd366e 100644 --- a/src/view.c +++ b/src/view.c @@ -80,7 +80,7 @@ struct view_query * view_query_create(void) { struct view_query *query = znew(*query); - /* Must be synced with view_matches_criteria() in window-rules.c */ + /* Must be synced with view_matches_rule() in window-rules.c */ query->window_type = LAB_WINDOW_TYPE_INVALID; query->maximized = VIEW_AXIS_INVALID; query->decoration = LAB_SSD_MODE_INVALID; @@ -263,8 +263,8 @@ view_get_root(struct view *view) return view; } -static bool -matches_criteria(struct view *view, enum lab_view_criteria criteria) +bool +view_matches_criteria(struct view *view, enum lab_view_criteria criteria) { if (!view_is_focusable(view)) { return false; @@ -316,7 +316,7 @@ view_next(struct wl_list *head, struct view *view, enum lab_view_criteria criter for (elm = elm->next; elm != head; elm = elm->next) { view = wl_container_of(elm, view, link); - if (matches_criteria(view, criteria)) { + if (view_matches_criteria(view, criteria)) { return view; } } @@ -332,7 +332,7 @@ view_prev(struct wl_list *head, struct view *view, enum lab_view_criteria criter for (elm = elm->prev; elm != head; elm = elm->prev) { view = wl_container_of(elm, view, link); - if (matches_criteria(view, criteria)) { + if (view_matches_criteria(view, criteria)) { return view; } } diff --git a/src/window-rules.c b/src/window-rules.c index 3911be7d..f43b92f4 100644 --- a/src/window-rules.c +++ b/src/window-rules.c @@ -24,7 +24,7 @@ other_instances_exist(struct view *self, struct view_query *query) } static bool -view_matches_criteria(struct window_rule *rule, struct view *view) +view_matches_rule(struct window_rule *rule, struct view *view) { struct view_query query = { .identifier = rule->identifier, @@ -52,7 +52,7 @@ window_rules_apply(struct view *view, enum window_rule_event event) if (rule->event != event) { continue; } - if (view_matches_criteria(rule, view)) { + if (view_matches_rule(rule, view)) { actions_run(view, &rule->actions, NULL); } } @@ -81,7 +81,7 @@ window_rules_get_property(struct view *view, const char *property) * attribute would still return here if that property was asked * for. */ - if (view_matches_criteria(rule, view)) { + if (view_matches_rule(rule, view)) { if (rule->server_decoration && !strcasecmp(property, "serverDecoration")) { return rule->server_decoration; From 0ff9af4ae05b80bc09f4bf290ab7666930debf16 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Mon, 4 May 2026 13:56:27 +0900 Subject: [PATCH 39/71] src/img/img-xpm.c: fix unused variable warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a warning in gcc16 below: ../src/img/img-xpm.c: In function ‘xpm_load_to_surface’: ../src/img/img-xpm.c:354:33: warning: variable ‘xcnt’ set but not used [-Wunused-but-set-variable=] 354 | for (int n = 0, xcnt = 0; n < wbytes; n += cpp, xcnt++) { | ^~~~ --- src/img/img-xpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/img/img-xpm.c b/src/img/img-xpm.c index 0d251b49..3bc7a9bb 100644 --- a/src/img/img-xpm.c +++ b/src/img/img-xpm.c @@ -351,7 +351,7 @@ xpm_load_to_surface(struct file_handle *handle) goto out; } - for (int n = 0, xcnt = 0; n < wbytes; n += cpp, xcnt++) { + for (int n = 0; n < wbytes; n += cpp) { g_strlcpy(pixel_str, &buffer[n], cpp + 1); struct xpm_color *color = From 0caefa6a9e42628549fc81b8af154bedcfc51163 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 5 May 2026 10:28:52 +0200 Subject: [PATCH 40/71] Translation updates from weblate Co-authored-by: Tobias Si Co-authored-by: Weblate Translate-URL: https://translate.lxqt-project.org/projects/labwc/labwc/sr_Latn/ Translation: Labwc/labwc --- po/LINGUAS | 2 +- po/sr_Latn.po | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 po/sr_Latn.po diff --git a/po/LINGUAS b/po/LINGUAS index 70114b72..50e5157a 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1 +1 @@ -ar ca cs da de el es et eu fa fi fr gl he hr hu id it ja ka kab kk ko lt ms nl pa pl pt pt_BR ru sk sv tr uk vi zh_CN zh_TW +ar ca cs da de el es et eu fa fi fr gl he hr hu id it ja ka kab kk ko lt ms nl pa pl pt pt_BR ru sk sr_Latn sv tr uk vi zh_CN zh_TW diff --git a/po/sr_Latn.po b/po/sr_Latn.po new file mode 100644 index 00000000..ef41f62e --- /dev/null +++ b/po/sr_Latn.po @@ -0,0 +1,81 @@ +# Labwc pot file +# Copyright (C) 2024 +# This file is distributed under the same license as the labwc package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: labwc\n" +"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" +"POT-Creation-Date: 2024-09-19 21:09+1000\n" +"PO-Revision-Date: 2026-05-05 08:28+0000\n" +"Last-Translator: Tobias Si \n" +"Language-Team: Serbian (latin) \n" +"Language: sr_Latn\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.2.1\n" + +#: src/menu/menu.c:1016 +msgid "Go there..." +msgstr "Idi tamo..." + +#: src/menu/menu.c:1034 +msgid "Terminal" +msgstr "Terminal" + +#: src/menu/menu.c:1040 +msgid "Reconfigure" +msgstr "Podesi" + +#: src/menu/menu.c:1042 +msgid "Exit" +msgstr "Izlaz" + +#: src/menu/menu.c:1056 +msgid "Minimize" +msgstr "Minimiziraj" + +#: src/menu/menu.c:1058 +msgid "Maximize" +msgstr "Maksimiziraj" + +#: src/menu/menu.c:1060 +msgid "Fullscreen" +msgstr "Prikaz preko celog ekrana" + +#: src/menu/menu.c:1062 +msgid "Roll Up/Down" +msgstr "Prevuci Gore/Dole" + +#: src/menu/menu.c:1064 +msgid "Decorations" +msgstr "Dekoracije" + +#: src/menu/menu.c:1066 +msgid "Always on Top" +msgstr "Uvek na vrhu" + +#: src/menu/menu.c:1071 +msgid "Move Left" +msgstr "Pomeri levo" + +#: src/menu/menu.c:1078 +msgid "Move Right" +msgstr "Pomeri desno" + +#: src/menu/menu.c:1083 +msgid "Always on Visible Workspace" +msgstr "Uvek na vidljivom radnom prostoru" + +#: src/menu/menu.c:1086 +msgid "Workspace" +msgstr "Radni prostor" + +#: src/menu/menu.c:1089 +msgid "Close" +msgstr "Zatvori" From 07a0a4e59b02b7a0679c938080d6bb35b9ad3183 Mon Sep 17 00:00:00 2001 From: Alex Chernika Date: Sun, 26 Apr 2026 12:33:54 +0200 Subject: [PATCH 41/71] scaled-buffer: introduce `scaled_font_buffer_update_markup()` This function behaves identically to `scaled_font_buffer_update()` but allows setting the text as pango markup, supporting further customization like underscores. --- include/common/font.h | 4 +++- include/scaled-buffer/scaled-font-buffer.h | 15 +++++++++++-- src/common/font.c | 10 +++++++-- src/scaled-buffer/scaled-font-buffer.c | 26 ++++++++++++++++++---- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/include/common/font.h b/include/common/font.h index a7a5ba55..22be2ae8 100644 --- a/include/common/font.h +++ b/include/common/font.h @@ -4,6 +4,7 @@ #include #include +#include struct lab_data_buffer; @@ -43,10 +44,11 @@ void font_get_buffer_size(int max_width, const char *text, struct font *font, * @font: font description * @color: foreground color in rgba format * @bg_pattern: background pattern + * @use_markup: flag to render pango markup */ void font_buffer_create(struct lab_data_buffer **buffer, int max_width, int height, const char *text, struct font *font, const float *color, - cairo_pattern_t *bg_pattern, double scale); + cairo_pattern_t *bg_pattern, double scale, bool use_markup); /** * font_finish - free some font related resources diff --git a/include/scaled-buffer/scaled-font-buffer.h b/include/scaled-buffer/scaled-font-buffer.h index a5e95087..2f0fe422 100644 --- a/include/scaled-buffer/scaled-font-buffer.h +++ b/include/scaled-buffer/scaled-font-buffer.h @@ -15,6 +15,7 @@ struct scaled_font_buffer { /* Private */ char *text; + bool use_markup; int max_width; float color[4]; float bg_color[4]; @@ -69,8 +70,18 @@ scaled_font_buffer_create_for_titlebar(struct wlr_scene_tree *parent, * bg_color is ignored for font buffers created with * scaled_font_buffer_create_for_titlebar(). */ -void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, - int max_width, struct font *font, const float *color, +void scaled_font_buffer_update(struct scaled_font_buffer *self, + const char *text, int max_width, struct font *font, const float *color, const float *bg_color); +/** + * Update an existing auto scaling font buffer allowing the use of pango markup. + * + * Behaves identically to scaled_font_buffer_update(), but allows customization + * of the `use_markup` field of the @self struct via @use_markup. + */ +void scaled_font_buffer_update_markup(struct scaled_font_buffer *self, + const char *text, int max_width, struct font *font, const float *color, + const float *bg_color, bool use_markup); + #endif /* LABWC_SCALED_FONT_BUFFER_H */ diff --git a/src/common/font.c b/src/common/font.c index b307729c..5162d284 100644 --- a/src/common/font.c +++ b/src/common/font.c @@ -79,7 +79,7 @@ font_get_buffer_size(int max_width, const char *text, struct font *font, void font_buffer_create(struct lab_data_buffer **buffer, int max_width, int height, const char *text, struct font *font, const float *color, - cairo_pattern_t *bg_pattern, double scale) + cairo_pattern_t *bg_pattern, double scale, bool use_markup) { if (string_null_or_empty(text)) { return; @@ -123,7 +123,13 @@ font_buffer_create(struct lab_data_buffer **buffer, int max_width, PangoLayout *layout = pango_cairo_create_layout(cairo); pango_context_set_round_glyph_positions(pango_layout_get_context(layout), false); pango_layout_set_width(layout, width * PANGO_SCALE); - pango_layout_set_text(layout, text, -1); + + if (use_markup) { + pango_layout_set_markup(layout, text, -1); + } else { + pango_layout_set_text(layout, text, -1); + } + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); if (!opaque_bg) { diff --git a/src/scaled-buffer/scaled-font-buffer.c b/src/scaled-buffer/scaled-font-buffer.c index bb93fc67..c748c00c 100644 --- a/src/scaled-buffer/scaled-font-buffer.c +++ b/src/scaled-buffer/scaled-font-buffer.c @@ -26,7 +26,7 @@ _create_buffer(struct scaled_buffer *scaled_buffer, double scale) /* Buffer gets free'd automatically along the backing wlr_buffer */ font_buffer_create(&buffer, self->max_width, self->height, self->text, - &self->font, self->color, bg_pattern, scale); + &self->font, self->color, bg_pattern, scale, self->use_markup); if (!buffer) { wlr_log(WLR_ERROR, "font_buffer_create() failed"); @@ -56,6 +56,7 @@ _equal(struct scaled_buffer *scaled_buffer_a, struct scaled_font_buffer *b = scaled_buffer_b->data; return str_equal(a->text, b->text) + && a->use_markup == b->use_markup && a->max_width == b->max_width && str_equal(a->font.name, b->font.name) && a->font.size == b->font.size @@ -104,10 +105,10 @@ scaled_font_buffer_create_for_titlebar(struct wlr_scene_tree *parent, return self; } -void -scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, +static void +_scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, int max_width, struct font *font, const float *color, - const float *bg_color) + const float *bg_color, bool use_markup) { assert(self); assert(text); @@ -120,6 +121,7 @@ scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, /* Update internal state */ self->text = xstrdup(text); + self->use_markup = use_markup; self->max_width = max_width; if (font->name) { self->font.name = xstrdup(font->name); @@ -139,3 +141,19 @@ scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, scaled_buffer_request_update(self->scaled_buffer, self->width, self->height); } + +void +scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, + int max_width, struct font *font, const float *color, + const float *bg_color) +{ + _scaled_font_buffer_update(self, text, max_width, font, color, bg_color, false); +} + +void +scaled_font_buffer_update_markup(struct scaled_font_buffer *self, const char *text, + int max_width, struct font *font, const float *color, + const float *bg_color, bool use_markup) +{ + _scaled_font_buffer_update(self, text, max_width, font, color, bg_color, use_markup); +} From 3632c6703a3fcd4545632ed62a1d635a92a60077 Mon Sep 17 00:00:00 2001 From: Alex Chernika Date: Fri, 10 Apr 2026 14:52:24 +0200 Subject: [PATCH 42/71] menu: implement menu accelerators Menu accelerators are one-letter mnemonics to quickly select/exec items from the current menu. For each menu item, the accelerator is defined as the first character of the item label, converted to lowercase. A different accelerator can be explicitly defined in menu.xml with the special '_' character before the target letter. - Add a field `accelerator` to the `menuitem` struct - Implement `menu_item_select_by_accelerator()` Example: The accelerator for an item with the label "e_macs" is 'm'. --- docs/labwc-menu.5.scd | 15 +++++ include/menu/menu.h | 17 ++++- src/input/keyboard.c | 17 +++-- src/menu/menu.c | 149 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 190 insertions(+), 8 deletions(-) diff --git a/docs/labwc-menu.5.scd b/docs/labwc-menu.5.scd index cf7ccbc7..df04ccf9 100644 --- a/docs/labwc-menu.5.scd +++ b/docs/labwc-menu.5.scd @@ -148,6 +148,21 @@ obmenu-generator with the menu generator of your choice): ``` +# ACCELERATORS / MNEMONICS + +Menu accelerators are one-letter mnemonics to quickly select/exec items from +the current menu. For each menu item, the accelerator is defined as the first +character of the item label, converted to lowercase. A different accelerator +can be explicitly defined in menu.xml with the special '\_' character before the +target letter. Accelerators can be any unicode character and are not limited to +ASCII. A usual underscore can be shown by duplicating it. + +If the menu only contains a single instance of the pressed accelerator the item +will be executed directly. Otherwise, all matching items are cycled through. + +Example: +The accelerator for an item with the label "e_Macs" is 'm'. + # LOCALISATION Available localisation for the default "client-menu" is only shown if no diff --git a/include/menu/menu.h b/include/menu/menu.h index 46bbca12..3d546efa 100644 --- a/include/menu/menu.h +++ b/include/menu/menu.h @@ -23,9 +23,11 @@ struct menuitem { char *text; char *icon_name; const char *arrow; + uint32_t accelerator; struct menu *parent; struct menu *submenu; bool selectable; + bool use_markup; enum menuitem_type type; int native_width; struct wlr_scene_tree *tree; @@ -66,6 +68,19 @@ struct menu { /* For keyboard support */ void menu_item_select_next(void); void menu_item_select_previous(void); + +/** + * menu_item_select_by_accelerator - selects the next menu item with + * a matching accelerator, starting after the current selection + * + * @accelerator a shortcut to quickly select/open an item, defined in menu.xml + * with an underscore in the item label before the target letter. + * + * Return: a boolean value that represents whether the newly selected item + * needs to be executed. + */ +bool menu_item_select_by_accelerator(uint32_t accelerator); + void menu_submenu_enter(void); void menu_submenu_leave(void); bool menu_call_selected_actions(void); @@ -100,7 +115,7 @@ void menu_open_root(struct menu *menu, int x, int y); void menu_process_cursor_motion(struct wlr_scene_node *node); /** - * menu_close_root- close root menu + * menu_close_root - close root menu * * This function will close server.menu_current and set it to NULL. * Asserts that server.input_mode is set to LAB_INPUT_STATE_MENU. diff --git a/src/input/keyboard.c b/src/input/keyboard.c index 98be5d11..36884144 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -444,15 +444,24 @@ handle_menu_keys(struct keysyms *syms) break; case XKB_KEY_Return: case XKB_KEY_KP_Enter: - menu_call_selected_actions(); + if (!menu_call_selected_actions()) { + menu_submenu_enter(); + }; break; case XKB_KEY_Escape: menu_close_root(); cursor_update_focus(); break; - default: - continue; - } + default: { + uint32_t accelerator = xkb_keysym_to_utf32(syms->syms[i]); + if (accelerator == 0) { + continue; + } + if (menu_item_select_by_accelerator(accelerator)) { + menu_call_selected_actions(); + } + break; + }} break; } } diff --git a/src/menu/menu.c b/src/menu/menu.c index 251f9a30..7b132806 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -2,12 +2,16 @@ #define _POSIX_C_SOURCE 200809L #include "menu/menu.h" #include +#include #include +#include #include #include #include #include +#include #include +#include #include #include #include @@ -129,6 +133,91 @@ validate(void) } } +static uint32_t +get_unicode_char(const char *first_byte, size_t *out_bytes) +{ + if (!first_byte || first_byte[0] == '\0') { + *out_bytes = 0; + return 0; + } + + /* Temporarily set locale to UTF-8 */ + locale_t utf8_locale = newlocale(LC_CTYPE_MASK, "C.UTF-8", (locale_t)0); + locale_t old_locale = (locale_t)0; + if (utf8_locale != (locale_t)0) { + old_locale = uselocale(utf8_locale); + } + + uint32_t result = 0; + char32_t codepoint = 0; + mbstate_t state = {0}; + size_t bytes = mbrtoc32(&codepoint, first_byte, 4, &state); + if (bytes > 0 && bytes <= 4) { + *out_bytes = bytes; + result = (uint32_t)towlower((wint_t)codepoint); + } else { + *out_bytes = 1; + result = (uint32_t)(unsigned char)first_byte[0]; + } + + /* Restore previous locale */ + if (utf8_locale != (locale_t)0) { + uselocale(old_locale); + freelocale(utf8_locale); + } + + return result; +} + +static void +item_parse_accelerator(struct menuitem *item, const char *text) +{ + const char *accel_ptr = NULL; + char *underscore = strchr(text, '_'); + while (underscore) { + if (underscore[1] == '_') { + /* Ignore escaped underscores */ + underscore = strchr(underscore + 2, '_'); + } else if (underscore[1] != '\0') { + /* Found a valid accelerator */ + accel_ptr = underscore + 1; + break; + } else { + /* Ignore empty accelertor */ + break; + } + } + + size_t bytes = 0; + if (!accel_ptr) { + item->text = xstrdup(text); + item->accelerator = get_unicode_char(text, &bytes); + } else { + item->use_markup = true; + item->accelerator = get_unicode_char(accel_ptr, &bytes); + item->text = strdup_printf("%.*s%.*s%s", + /* Prefix length + prefix */ + (int)(accel_ptr - 1 - text), text, + /* Accelerator (utf-8 byte) length + accelerator */ + (int)bytes, accel_ptr, + /* Remainder */ + accel_ptr + bytes); + } + + /* Remove undescores used for escaping */ + char *src = item->text; + char *dst = item->text; + while (*src) { + if (*src == '_' && *(src + 1) == '_') { + *dst++ = '_'; + src += 2; + } else { + *dst++ = *src++; + } + } + *dst = '\0'; +} + static struct menuitem * item_create(struct menu *menu, const char *text, const char *icon_name, bool show_arrow) { @@ -140,8 +229,8 @@ item_create(struct menu *menu, const char *text, const char *icon_name, bool sho menuitem->parent = menu; menuitem->selectable = true; menuitem->type = LAB_MENU_ITEM; - menuitem->text = xstrdup(text); menuitem->arrow = show_arrow ? "›" : NULL; + item_parse_accelerator(menuitem, text); #if HAVE_LIBSFDO if (rc.menu_show_icons && !string_null_or_empty(icon_name)) { @@ -212,8 +301,8 @@ item_create_scene_for_state(struct menuitem *item, float *text_color, /* Create label */ struct scaled_font_buffer *label_buffer = scaled_font_buffer_create(tree); assert(label_buffer); - scaled_font_buffer_update(label_buffer, item->text, label_max_width, - &rc.font_menuitem, text_color, bg_color); + scaled_font_buffer_update_markup(label_buffer, item->text, label_max_width, + &rc.font_menuitem, text_color, bg_color, item->use_markup); /* Vertically center and left-align label */ int x = theme->menu_items_padding_x + icon_width; int y = (theme->menu_item_height - label_buffer->height) / 2; @@ -1460,6 +1549,60 @@ menu_item_select_previous(void) menu_item_select(/* forward */ false); } +bool +menu_item_select_by_accelerator(uint32_t accelerator) +{ + struct menu *menu = get_selection_leaf(); + if (!menu || wl_list_empty(&menu->menuitems)) { + return false; + } + + bool needs_exec = false; + bool matched = false; + + struct menuitem *selection = menu->selection.item; + struct wl_list *start = selection ? &selection->link : &menu->menuitems; + struct wl_list *current = start; + struct menuitem *item = NULL; + struct menuitem *next_selection = NULL; + do { + current = current->next; + if (current == &menu->menuitems) { + /* Allow wrap around */ + continue; + } + item = wl_container_of(current, item, link); + if (item->accelerator == accelerator) { + if (!matched) { + /* Found first match */ + next_selection = item; + needs_exec = true; + matched = true; + } else { + /* + * Found another match, + * cycle selection instead of executing + */ + needs_exec = false; + break; + } + } + } while (current != start); + + if (!next_selection) { + return false; + } + + menu_process_item_selection(next_selection); + if (needs_exec && next_selection->submenu) { + /* Since we can't execute a submenu, enter it. */ + needs_exec = false; + menu_submenu_enter(); + } + + return needs_exec; +} + bool menu_call_selected_actions(void) { From f42e1895d41187cfca00e9cd61aeaeabbb830fbf Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Tue, 5 May 2026 19:11:34 +0100 Subject: [PATCH 43/71] buf.c: fix -fanalyze warning ../src/common/buf.c:61:28: error: write to string literal [-Werror=analyzer-write-to-string-literal] 61 | *p = '\0'; | ~~~^~~~~~ --- src/common/buf.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/common/buf.c b/src/common/buf.c index c141b62e..2a68b734 100644 --- a/src/common/buf.c +++ b/src/common/buf.c @@ -53,15 +53,14 @@ buf_expand_shell_variables(struct buf *s) if (s->data[i] == '$' && isvalid(s->data[i+1])) { /* expand environment variable */ buf_clear(&environment_variable); - buf_add(&environment_variable, s->data + i + 1); - char *p = environment_variable.data; - while (isvalid(*p)) { - ++p; + int len = 0; + while (isvalid(s->data[i + 1 + len])) { + buf_add_char(&environment_variable, s->data[i + 1 + len]); + ++len; } - *p = '\0'; - i += strlen(environment_variable.data); + i += len; strip_curly_braces(environment_variable.data); - p = getenv(environment_variable.data); + char *p = getenv(environment_variable.data); if (p) { buf_add(&tmp, p); } From ff2f243eb1b410e6f4792bdd1d1fceaeba01e612 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Mon, 4 May 2026 22:49:57 +0300 Subject: [PATCH 44/71] NEWS.md: typo and style fixes with help of perl(1) and codespell(1) $ perl -pi.bak -e 's,\S\K/>, />,' NEWS.md $ codespell [-i3] -L DoubleClick,inter-operability NEWS.md --- NEWS.md | 74 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/NEWS.md b/NEWS.md index 997f4068..2e0a6109 100644 --- a/NEWS.md +++ b/NEWS.md @@ -264,9 +264,9 @@ Note to package maintainers: This release requires wayland version >=1.22.90 - Add theme option window.button.hover.bg.color [#3365] @johanmalm - Implement scrollable window-switcher OSD [#3291] @tokyo4j - Support the `NextWindow` options listed below [#3271] @tokyo4j - - `` - - `` - - `` + - `` + - `` + - `` - Add config option `*` for setting the active workspace on startup. [#3265] @5trixs0f @@ -314,7 +314,7 @@ Note to package maintainers: This release requires wayland version >=1.22.90 and get keyboard focus so that they can be operated with the keyboard. An example use-case is the xfce4-panel applications-menu being opened by the command xfce4-popup-applicationsmenu. [#3165] @johanmalm - - On popup destory, return focus to whoever had it before the popop [#3165] + - On popup destroy, return focus to whoever had it before the popop [#3165] @johanmalm @tokyo4j - Unshade window if selected from client-list-combined-menu [#3345] @Amodio - Show non-dialog child windows in window-switcher [#3339] @tokyo4j @@ -348,7 +348,7 @@ A big thank you to all involved in this release. ### Added -- Add `` to optionally order windows by age +- Add `` to optionally order windows by age rather than most recent focus. @mbroemme [#3229] - Replace `` with `` to provide more granular control when configuring the size of snapping areas @@ -379,7 +379,7 @@ A big thank you to all involved in this release. [#3134] - labnag: add --keyboard-focus option @tokyo4j [#3120] - Allow window switcher to temporarily unshade windows using config option - `` @Amodio @Consolatis [#3124] + `` @Amodio @Consolatis [#3124] - For the 'classic' style window-switcher, add the following theme options: - `osd.window-switcher.style-classic.item.active.border.color` - `osd.window-switcher.style-classic.item.active.bg.color` @@ -409,7 +409,7 @@ A big thank you to all involved in this release. client surface. Fixes a regression in 885919f. @tokyo4j [#3211] - Set all foreign-toplevel initial states correctly. This is not believed to fix any particular user-issue, but just feels safer. @jlindgren90 [#3217] -- Update layer-shell client top layer visiblity on unmap instead of destroy +- Update layer-shell client top layer visibility on unmap instead of destroy because it is possible for fullscreen xwayland windows to be unmapped without being destroyed, and in this case the top layer visibility needs to be updated to unhide other layer-shell clients like panels. @jlindgren90 [#3199] @@ -421,7 +421,7 @@ A big thank you to all involved in this release. @elviosak [#3146] [#3168] - Work around client-side rounding issues at right/bottom pixel. This fixes an issue with some clients (notably Qt ones) where cursor coordinates in the - rightmost or bottom fixel are incorrectly rounded up putting them outside the + rightmost or bottom pixel are incorrectly rounded up putting them outside the surface bounds. The issue has been particularly noticeable with layer-shell clients like lxqt-panel. @jlindgren90 [#3157] [#2379] [#3099] Note: This also avoids a similar server-side rounding issue with some @@ -469,7 +469,7 @@ A big thank you to all involved in this release. when a window is using fullscreen mode. @johanmalm [#3158] - Call labnag with on-demand keyboard interactivity by default @tokyo4j [#3120] - Temporarily unshade windows when switching windows. Restore old behaviour with - `` @Amodio @Consolatis [#3124] + `` @Amodio @Consolatis [#3124] - In the classic style window-switcher, the default color of the selected window item has been changed to inherit the border color but with 15% opacity @tokyo4j [#3118] @@ -488,7 +488,7 @@ A big thank you to all involved in this release. ``. @tokyo4j [#2981] - Add `toggle` option to `GoToDesktop` action. This has the effect of going back to the last desktop if already on the target. @RainerKuemmerle [#3024] -- Add `` to allow hiding titlebar +- Add `` to allow hiding titlebar when window is maximized. @CosmicFusion @tokyo4j [#3015] - Use client-send-to-menu as 'Workspace' submenu in built-in client-menu @johanmalm [#2995] @@ -539,7 +539,7 @@ A big thank you to all involved in this release. - Change default keybind `W-` to combine cardinal directions to support resizing of windows to fill a quarter of an output. This only affects users who do not use an `rc.xml` (thereby using default keybinds) or use the - `` option. Previous behavior can be restored by setting + `` option. Previous behavior can be restored by setting `combine="no"` as shown below. [#3081] @tokyo4j ``` @@ -638,7 +638,7 @@ window.*.title.bg.colorTo.splitTo: window rule to enable this. @Consolatis @tokyo4j [#2840] - Add config option ``. This enables autoscroll (middle-click to scroll up/down) in Chromium and electron based clients - without inadvertantly pasting the primary clipboard. @johanmalm [#2832] + without inadvertently pasting the primary clipboard. @johanmalm [#2832] - Bump `xdg_shell` version from 3 to 6 @tokyo4j [#2814] - Bump `wl_compositor` version from 5 to 6 @tokyo4j [#2812] - Support tablet tool mouse buttons @jp7677 [#2778] @@ -715,7 +715,7 @@ window.*.title.bg.colorTo.splitTo: agnostic on choice of launcher. - `A-` for `MoveToEdge` because `Alt-` keybinds should be for clients to use and this one results in frequent user complaints because it prevents - some common usage patterns like alt-left/right in web browers. + some common usage patterns like alt-left/right in web browsers. - Change default titlebar menu button from a dot to an arrow @johanmalm [#2844] - When `dragLock` is set to `yes`, the drag no longer expires after a short delay (known as `Sticky` mode) as recommended by libinput [#2803]. The timeout @@ -750,7 +750,7 @@ release. - Localize desktop-entry application names used by the window switcher via `desktop_entry_name` or the `%n` specifier @tokyo4j [#2653] - Add `HideCursor` action @jp7677 [#2633] -- Support application icons in window-switcher using `` +- Support application icons in window-switcher using `` and use this by default. @tokyo4j [#2621] - Support application icons in client-list-combined-menu @tokyo4j [#2617] - Support the use of the keypad-enter key when using menu. @zeusgoose [#2610] @@ -887,7 +887,7 @@ Notes to package maintainers: closing a popup did not move the pointer focus to the main toplevel until the cursor was moved. [#2443] - Improve algorithm for menu placement with xdg-positioner [#2408] -- Do not forward IME key-release without correspinding key-press to avoid stuck +- Do not forward IME key-release without corresponding key-press to avoid stuck keys [#2437] ### Changed @@ -949,7 +949,7 @@ Notes to package maintainers: ```xml - + ``` @@ -966,8 +966,8 @@ menu.border.color: #aaaaaa ```xml - - + + ``` @@ -1097,7 +1097,7 @@ Notes to package maintainers: - Support the openbox style menus listed below. Written-by: @droc12345 1. `client-list-combined-menu` shows windows across all workspaces. This can be used with a mouse/key bind using: - `` [#2101] + `` [#2101] 2. `client-send-to` shows all workspaces that the current window can be sent to. This can additional be used within a client menu using: `` [#2152] @@ -1256,14 +1256,14 @@ have been attributed with a 'Written-by' against each relevant log entry. ```xml cascade - + ``` - Support relative tablet motion. Written-by: @jp7677 [#1962] ```xml - + ``` ### Fixed @@ -1347,7 +1347,7 @@ joint effort by @spl237 and @Consolatis. - Respect `menu.overlap.x` when using pipemenus. [#1940] - Do not try to restore windows to very small width/height on unmaximize. This fixes a bug with Thonny (Python IDE made with Tk). [#1938] -- Conditially set squared server-side decoration (SSD) corners when a view is +- Conditionally set squared server-side decoration (SSD) corners when a view is tiled. Written-by: @jp7677 [#1926] - Remember initial direction when starting window-cycling with `PreviousView`. Also make the toggling of direction when shift is pressed relative to the @@ -1371,7 +1371,7 @@ joint effort by @spl237 and @Consolatis. Chromium and Steam. [#1861] - Session-lock: fix flashing & update cursor shape. [#1858] - Remove tearing-controller listeners on destroy. [#1853] -- Handle invalid `ForEach` and `If` action cofigs. [#1838] +- Handle invalid `ForEach` and `If` action configs. [#1838] - Delay startup of applications until event loop is ready. This avoids race conditions when using autostart scripts that trigger a labwc SIGHUP. [#1588] - With `SendToDesktop` action follow=no option, ensure the topmost window is @@ -1388,7 +1388,7 @@ joint effort by @spl237 and @Consolatis. - Remove subprojects/seatd.wrap as no longer needed - Action `MoveToCursor` is deprecated in favour of: - ``. + ``. ## 0.7.2 - 2024-05-10 @@ -1405,7 +1405,7 @@ contributions from others as noted in the log. ### Added - Add `` to prevent clicks with small movements - from inadvertantly closing a menu or selecting a menu item. This is the + from inadvertently closing a menu or selecting a menu item. This is the equivalent of `` on Openbox. [#1760] - Support drop-shadows (disabled by default) for windows using server-side decorations. Written-by: @cillian64 @@ -1433,7 +1433,7 @@ window.inactive.shadow.color: #00000040 ```xml - + @@ -1475,7 +1475,7 @@ osd.window-switcher.width: 75% yes|no - + ``` @@ -1638,7 +1638,7 @@ osd.window-switcher.preview.border.color: #ffffff,#00a2ff,#ffffff ```xml - + ``` @@ -1646,7 +1646,7 @@ osd.window-switcher.preview.border.color: #ffffff,#00a2ff,#ffffff is already used by the action itself). [#1589] ```xml - + ``` - Do not deactivate window when giving keyboard focus to a non-view @@ -1711,8 +1711,8 @@ osd.window-switcher.preview.border.color: #ffffff,#00a2ff,#ffffff Written-by: @jp7677 ```xml - - + + ``` - Add tablet support including: @@ -1925,7 +1925,7 @@ relating to surface focus and keyboard issues, amongst others. - Allow referencing the current workspace in actions, for example: ```xml - + ``` ### Fixed @@ -2081,7 +2081,7 @@ relating to surface focus and keyboard issues, amongst others. ```xml - + ``` @@ -2148,9 +2148,9 @@ relating to surface focus and keyboard issues, amongst others. ```xml - + - + ``` @@ -2235,7 +2235,7 @@ Unless otherwise stated all contributions are by the core-devs ```xml - + @@ -2499,7 +2499,7 @@ reported, tested and fixed issues. Particular mentions go to @bi4k8, actions to be de-coupled from buttons. As a result, "Drag" and "DoubleClick" actions previously defined against "TitleBar" should now come under the "Title" context, for example: - `` + `` - Remove default alt-escape keybind for Exit because too many people have exited the compositor by mistake trying to get out of alt-tab cycling or similar. From 949e9ffe42e8e3721481239a1595c0d925246480 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Wed, 6 May 2026 21:17:02 +0100 Subject: [PATCH 45/71] cycle.c: put common code in get_view_criteria() --- src/cycle/cycle.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 14682c9f..d72a48ae 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -9,6 +9,7 @@ #include "common/mem.h" #include "common/scene-helpers.h" #include "config/rcxml.h" +#include "config/types.h" #include "labwc.h" #include "node.h" #include "output.h" @@ -322,6 +323,18 @@ handle_osd_tree_destroy(struct wl_listener *listener, void *data) free(osd_output); } +static enum lab_view_criteria +get_view_criteria(struct cycle_filter *filter) +{ + enum lab_view_criteria criteria = + LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER + | LAB_VIEW_CRITERIA_NO_DIALOG; + if (filter->workspace == CYCLE_WORKSPACE_CURRENT) { + criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; + } + return criteria; +} + static struct wl_list *prev(struct wl_list *elm) { return elm->prev; } static struct wl_list *next(struct wl_list *elm) { return elm->next; } @@ -331,13 +344,10 @@ cycle_immediate(enum lab_cycle_dir direction, struct cycle_filter filter) if (wl_list_empty(&server.views)) { return; } - enum lab_view_criteria criteria = - LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER - | LAB_VIEW_CRITERIA_NO_DIALOG; - if (filter.workspace == CYCLE_WORKSPACE_CURRENT) { - criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; - } + + enum lab_view_criteria criteria = get_view_criteria(&filter); uint64_t cycle_outputs = get_outputs_by_filter(filter.output); + const char *cycle_app_id = NULL; if (filter.app_id == CYCLE_APP_ID_CURRENT && server.active_view) { cycle_app_id = server.active_view->app_id; @@ -381,15 +391,8 @@ cycle_immediate(enum lab_cycle_dir direction, struct cycle_filter filter) static bool init_cycle(struct cycle_filter filter) { - enum lab_view_criteria criteria = - LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER - | LAB_VIEW_CRITERIA_NO_DIALOG; - if (filter.workspace == CYCLE_WORKSPACE_CURRENT) { - criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; - } - - uint64_t cycle_outputs = - get_outputs_by_filter(filter.output); + enum lab_view_criteria criteria = get_view_criteria(&filter); + uint64_t cycle_outputs = get_outputs_by_filter(filter.output); const char *cycle_app_id = NULL; if (filter.app_id == CYCLE_APP_ID_CURRENT && server.active_view) { From 0f5e4f8dd0e179b4e91fd8173bc96d5d9c3fba77 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Wed, 6 May 2026 21:17:35 +0100 Subject: [PATCH 46/71] cycle.c: put common code in get_cycle_app_id --- src/cycle/cycle.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index d72a48ae..0f183ddc 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -335,6 +335,15 @@ get_view_criteria(struct cycle_filter *filter) return criteria; } +static const char * +get_cycle_app_id(struct cycle_filter *filter) +{ + if (filter->app_id == CYCLE_APP_ID_CURRENT && server.active_view) { + return server.active_view->app_id; + } + return NULL; +} + static struct wl_list *prev(struct wl_list *elm) { return elm->prev; } static struct wl_list *next(struct wl_list *elm) { return elm->next; } @@ -347,11 +356,7 @@ cycle_immediate(enum lab_cycle_dir direction, struct cycle_filter filter) enum lab_view_criteria criteria = get_view_criteria(&filter); uint64_t cycle_outputs = get_outputs_by_filter(filter.output); - - const char *cycle_app_id = NULL; - if (filter.app_id == CYCLE_APP_ID_CURRENT && server.active_view) { - cycle_app_id = server.active_view->app_id; - } + const char *cycle_app_id = get_cycle_app_id(&filter); struct wl_list *head = &server.views; struct wl_list *(*iter)(struct wl_list *list); @@ -393,11 +398,7 @@ init_cycle(struct cycle_filter filter) { enum lab_view_criteria criteria = get_view_criteria(&filter); uint64_t cycle_outputs = get_outputs_by_filter(filter.output); - - const char *cycle_app_id = NULL; - if (filter.app_id == CYCLE_APP_ID_CURRENT && server.active_view) { - cycle_app_id = server.active_view->app_id; - } + const char *cycle_app_id = get_cycle_app_id(&filter); struct view *view; for_each_view(view, &server.views, criteria) { From 2480a23b1949c77006a705acd88bf72a3a5ee17a Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Sat, 4 Apr 2026 16:58:05 +0200 Subject: [PATCH 47/71] common/macros: change WLR_VERSION macro to runtime evaluation --- include/common/macros.h | 8 ++++++-- include/labwc.h | 2 ++ src/main.c | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/common/macros.h b/include/common/macros.h index 25226261..f3ceee82 100644 --- a/include/common/macros.h +++ b/include/common/macros.h @@ -62,7 +62,11 @@ #define BOUNDED_INT(a) ((a) < INT_MAX && (a) > INT_MIN) #endif -#define LAB_WLR_VERSION_AT_LEAST(major, minor, micro) \ - (WLR_VERSION_NUM >= (((major) << 16) | ((minor) << 8) | (micro))) +#define _LAB_CALC_WLR_VERSION_NUM(major, minor, micro) (((major) << 16) | ((minor) << 8) | (micro)) + +#define LAB_WLR_VERSION_AT_LEAST(major, minor, micro) ( \ + server.wlr_version >= _LAB_CALC_WLR_VERSION_NUM(major, minor, micro)) + +#define LAB_WLR_VERSION_LOWER(major, minor, micro) (!LAB_WLR_VERSION_AT_LEAST(major, minor, micro)) #endif /* LABWC_MACROS_H */ diff --git a/include/labwc.h b/include/labwc.h index dc4c1311..edde2771 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -148,6 +148,8 @@ struct seat { }; struct server { + uint32_t wlr_version; + struct wl_display *wl_display; struct wl_event_loop *wl_event_loop; /* Can be used for timer events */ diff --git a/src/main.c b/src/main.c index 373f4480..f36be071 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ #include #include "common/fd-util.h" #include "common/font.h" +#include "common/macros.h" #include "common/spawn.h" #include "config/rcxml.h" #include "config/session.h" @@ -164,6 +165,12 @@ main(int argc, char *argv[]) char *primary_client = NULL; enum wlr_log_importance verbosity = WLR_ERROR; + server.wlr_version = _LAB_CALC_WLR_VERSION_NUM( + wlr_version_get_major(), + wlr_version_get_minor(), + wlr_version_get_micro() + ); + int c; while (1) { int index = 0; From 2189b2be1e9449e9f9922fcd1808926df7ea4c51 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Thu, 7 May 2026 02:13:42 +0200 Subject: [PATCH 48/71] main: include wlroots version in --version string --- src/main.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c index f36be071..53f85623 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "common/fd-util.h" #include "common/font.h" #include "common/macros.h" @@ -66,12 +67,15 @@ static void print_version(void) { #define FEATURE_ENABLED(feature) (HAVE_##feature ? "+" : "-") - printf("labwc %s (%sxwayland %snls %srsvg %slibsfdo)\n", + printf("labwc %s (%sxwayland %snls %srsvg %slibsfdo) running on wlroots %d.%d.%d\n", LABWC_VERSION, FEATURE_ENABLED(XWAYLAND), FEATURE_ENABLED(NLS), FEATURE_ENABLED(RSVG), - FEATURE_ENABLED(LIBSFDO) + FEATURE_ENABLED(LIBSFDO), + wlr_version_get_major(), + wlr_version_get_minor(), + wlr_version_get_micro() ); #undef FEATURE_ENABLED } From c1b11c782158bc6afdf4d6c456bfe85f94db4ec6 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sun, 29 Mar 2026 23:52:38 -0700 Subject: [PATCH 49/71] chase wlroots: Add support for HDR10 output v2: Switch XRGB to XBGR v3: Rewrite HDR mode checking and setting v4: Restructure HDR support detection v5: Fix code style v6: Fix old style function declaration v7: This function should be declared static v8: This helper function can accept a const struct on input v9: Rebase now that 0.20 is merged v10: Rewrite with multiple color format attempts v11: Add in the parts that accidentally got left in my original color-management-v1 patch v12: Add missing function prototype v13: Apply suggested changes v14: Changed HDR application setup in new output v15: Rewrite configure_new_output to use lab_wlr_scene_output_commit v16: Fixed application of HDR on external mode or output config change v17: Fixed it for real this time instead of crashing v18: Moved the effective resolution collection, plus one style change. --- include/config/rcxml.h | 7 ++ include/output.h | 3 + src/config/rcxml.c | 13 +++ src/output-state.c | 1 + src/output.c | 208 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 228 insertions(+), 4 deletions(-) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 3ef7bd67..9c2183a8 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -36,6 +36,12 @@ enum tearing_mode { LAB_TEARING_FULLSCREEN_FORCED, }; +enum render_bit_depth { + LAB_RENDER_BIT_DEPTH_DEFAULT = 0, + LAB_RENDER_BIT_DEPTH_8, + LAB_RENDER_BIT_DEPTH_10, +}; + enum tiling_events_mode { LAB_TILING_EVENTS_NEVER = 0, LAB_TILING_EVENTS_REGION = 1 << 0, @@ -74,6 +80,7 @@ struct rcxml { int gap; enum adaptive_sync_mode adaptive_sync; enum tearing_mode allow_tearing; + enum render_bit_depth target_render_depth; bool auto_enable_outputs; bool reuse_output_mode; uint32_t allowed_interfaces; diff --git a/include/output.h b/include/output.h index 89d8be04..c17defa4 100644 --- a/include/output.h +++ b/include/output.h @@ -71,6 +71,9 @@ struct wlr_box output_usable_area_in_layout_coords(struct output *output); void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data); void output_enable_adaptive_sync(struct output *output, bool enabled); +void output_enable_hdr(struct output *output, struct wlr_output_state *os, + bool enabled, bool silent); +void output_state_setup_hdr(struct output *output, bool silent); /** * Notifies whether a fullscreen view is displayed on the given output. diff --git a/src/config/rcxml.c b/src/config/rcxml.c index d0bb76db..0514b6c7 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1080,6 +1080,16 @@ set_tearing_mode(const char *str, enum tearing_mode *variable) } } +static void +set_hdr_mode(const char *str, enum render_bit_depth *variable) +{ + if (parse_bool(str, -1) == 1) { + *variable = LAB_RENDER_BIT_DEPTH_10; + } else { + *variable = LAB_RENDER_BIT_DEPTH_8; + } +} + /* Returns true if the node's children should also be traversed */ static bool entry(xmlNode *node, char *nodename, char *content) @@ -1144,6 +1154,8 @@ entry(xmlNode *node, char *nodename, char *content) set_adaptive_sync_mode(content, &rc.adaptive_sync); } else if (!strcasecmp(nodename, "allowTearing.core")) { set_tearing_mode(content, &rc.allow_tearing); + } else if (!strcasecmp(nodename, "Hdr.core")) { + set_hdr_mode(content, &rc.target_render_depth); } else if (!strcasecmp(nodename, "autoEnableOutputs.core")) { set_bool(content, &rc.auto_enable_outputs); } else if (!strcasecmp(nodename, "reuseOutputMode.core")) { @@ -1518,6 +1530,7 @@ rcxml_init(void) rc.gap = 0; rc.adaptive_sync = LAB_ADAPTIVE_SYNC_DISABLED; rc.allow_tearing = LAB_TEARING_DISABLED; + rc.target_render_depth = LAB_RENDER_BIT_DEPTH_DEFAULT; rc.auto_enable_outputs = true; rc.reuse_output_mode = false; rc.allowed_interfaces = UINT32_MAX; diff --git a/src/output-state.c b/src/output-state.c index 7da1108b..37ebde8a 100644 --- a/src/output-state.c +++ b/src/output-state.c @@ -24,6 +24,7 @@ output_state_init(struct output *output) backup_config, output->wlr_output); wlr_output_head_v1_state_apply(&backup_head->state, &output->pending); + wlr_output_configuration_v1_destroy(backup_config); } diff --git a/src/output.c b/src/output.c index aea35062..29a2d5a0 100644 --- a/src/output.c +++ b/src/output.c @@ -9,6 +9,7 @@ #define _POSIX_C_SOURCE 200809L #include "output.h" #include +#include #include #include #include @@ -51,6 +52,150 @@ #include #endif +static uint32_t output_formats_8bit[] = { + /* 32 bpp RGB with 8 bit component width */ + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + + /* 24 bpp RGB */ + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, +}; + +uint32_t output_formats_10bit[] = { + /* 32 bpp RGB with 10 bit component width */ + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XBGR2101010, + DRM_FORMAT_RGBX1010102, + DRM_FORMAT_BGRX1010102, + + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, +}; + +static bool +output_set_render_format(struct output *output, uint32_t candidates[], size_t count) +{ + for (size_t i = 0; i < count; i++) { + wlr_output_state_set_render_format(&output->pending, candidates[i]); + if (wlr_output_test_state(output->wlr_output, &output->pending)) { + return true; + } + } + return false; +} + +static bool +output_format_in_candidates(uint32_t render_format, uint32_t candidates[], size_t count) +{ + for (size_t i = 0; i < count; i++) { + if (candidates[i] == render_format) { + return true; + } + } + return false; +} + +static enum render_bit_depth +bit_depth_from_format(uint32_t render_format) +{ + if (output_format_in_candidates(render_format, output_formats_10bit, + ARRAY_SIZE(output_formats_10bit))) { + return LAB_RENDER_BIT_DEPTH_10; + } else if (output_format_in_candidates(render_format, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit))) { + return LAB_RENDER_BIT_DEPTH_8; + } + return LAB_RENDER_BIT_DEPTH_DEFAULT; +} + +static enum render_bit_depth +get_config_render_bit_depth(void) +{ + return rc.target_render_depth; +} + +static bool +output_supports_hdr(const struct wlr_output *output, const char **unsupported_reason_ptr) +{ + const char *unsupported_reason = NULL; + if (!(output->supported_primaries & WLR_COLOR_NAMED_PRIMARIES_BT2020)) { + unsupported_reason = "BT2020 primaries not supported by output"; + } else if (!(output->supported_transfer_functions & + WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ)) { + unsupported_reason = "PQ transfer function not supported by output"; + } else if (!server.renderer->features.output_color_transform) { + unsupported_reason = "renderer doesn't support output color transforms"; + } + if (unsupported_reason_ptr) { + *unsupported_reason_ptr = unsupported_reason; + } + return !unsupported_reason; +} + +void +output_state_setup_hdr(struct output *output, bool silent) +{ + uint32_t render_format = output->wlr_output->render_format; + const char *unsupported_reason = NULL; + bool hdr_supported = output_supports_hdr(output->wlr_output, + &unsupported_reason); + bool hdr_succeeded = false; + + enum render_bit_depth render_bit_depth = get_config_render_bit_depth(); + if (render_bit_depth == LAB_RENDER_BIT_DEPTH_DEFAULT) { + render_bit_depth = bit_depth_from_format(render_format); + } + + if (!hdr_supported && render_bit_depth == LAB_RENDER_BIT_DEPTH_10) { + if (!silent) { + wlr_log(WLR_INFO, "Cannot enable HDR on output %s: %s", + output->wlr_output->name, unsupported_reason); + } + render_bit_depth = LAB_RENDER_BIT_DEPTH_8; + } + + if (render_bit_depth == LAB_RENDER_BIT_DEPTH_10 && + bit_depth_from_format(render_format) == render_bit_depth) { + /* 10-bit was set successfully before, try to save some + * tests by reusing the format + */ + hdr_succeeded = true; + } else if (render_bit_depth == LAB_RENDER_BIT_DEPTH_10) { + hdr_succeeded = output_set_render_format(output, output_formats_10bit, + ARRAY_SIZE(output_formats_10bit)); + if (!hdr_succeeded) { + if (!silent) { + wlr_log(WLR_INFO, "No 10 bit color formats" + " supported, HDR disabled."); + } + if (!output_set_render_format(output, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit))) { + if (!silent) { + wlr_log(WLR_ERROR, "No 8 bit color formats" + " supported either!"); + } + } + } + } else { + if (!output_set_render_format(output, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit)) && !silent) { + wlr_log(WLR_ERROR, "No 8 bit color formats supported!"); + } + } + + output_enable_hdr(output, &output->pending, hdr_succeeded, silent); +} + bool output_get_tearing_allowance(struct output *output) { @@ -371,10 +516,7 @@ configure_new_output(struct output *output) output_enable_adaptive_sync(output, true); } - output_state_commit(output); - - wlr_output_effective_resolution(wlr_output, - &output->usable_area.width, &output->usable_area.height); + output_state_setup_hdr(output, false); /* * Wait until wlr_output_layout_add_auto() returns before @@ -384,6 +526,19 @@ configure_new_output(struct output *output) server.pending_output_layout_change++; add_output_to_layout(output); server.pending_output_layout_change--; + + /* + * Commit the output this way instead, HDR needs a buffer, and + * this commit must be called after the output is added to the + * layout above. + */ + lab_wlr_scene_output_commit(output->scene_output, &output->pending); + + /* + * Collect the effective resolution after the final commit. + */ + wlr_output_effective_resolution(wlr_output, + &output->usable_area.width, &output->usable_area.height); } static uint64_t @@ -653,6 +808,7 @@ output_config_apply(struct wlr_output_configuration_v1 *config) wlr_output_state_set_transform(os, head->state.transform); output_enable_adaptive_sync(output, head->state.adaptive_sync_enabled); + output_state_setup_hdr(output, false); } if (!output_state_commit(output)) { /* @@ -666,6 +822,19 @@ output_config_apply(struct wlr_output_configuration_v1 *config) break; } + if (output_enabled) { + /* + * The above commit was likely made without an image + * buffer attached. This will break applying HDR color + * transformation, since image descriptions must be + * committed with a buffer attached. Queue the HDR mode + * again if output is enabled, but make it silent, + * since it would have emitted messages already when + * called above. + */ + output_state_setup_hdr(output, true); + } + /* * Add or remove output from layout only if the commit went * through. Note that at startup, the output may have already @@ -1136,3 +1305,34 @@ output_set_has_fullscreen_view(struct output *output, bool has_fullscreen_view) output_enable_adaptive_sync(output, has_fullscreen_view); output_state_commit(output); } + +void +output_enable_hdr(struct output *output, struct wlr_output_state *os, + bool enabled, bool silent) +{ + if (enabled && !output_supports_hdr(output->wlr_output, NULL)) { + enabled = false; + } + + if (!enabled) { + if (output->wlr_output->supported_primaries != 0 || + output->wlr_output->supported_transfer_functions != 0) { + if (!silent) { + wlr_log(WLR_DEBUG, "Disabling HDR on output %s", + output->wlr_output->name); + } + wlr_output_state_set_image_description(os, NULL); + } + return; + } + + if (!silent) { + wlr_log(WLR_DEBUG, "Enabling HDR on output %s", + output->wlr_output->name); + } + const struct wlr_output_image_description image_desc = { + .primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020, + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ, + }; + wlr_output_state_set_image_description(os, &image_desc); +} From 38f80d716717da6f788a0b663b9078dbd77a9376 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 31 Jul 2025 22:13:02 -0700 Subject: [PATCH 50/71] Document the new HDR option v2: Remove note which is no longer applicable. --- docs/labwc-config.5.scd | 6 ++++++ docs/rc.xml.all | 1 + 2 files changed, 7 insertions(+) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 8ea88d8f..240f7043 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -175,6 +175,7 @@ this is for compatibility with Openbox. no no yes + no no no yes @@ -240,6 +241,11 @@ this is for compatibility with Openbox. 'pkill kanshi ; wlopm --off \*' resume 'kanshi & wlopm --on \*' ``` +** [yes|no] + Automatically enable HDR support on outputs when configuring them, + where supported by the particular output device and display. Default + is no. + ** [yes|no] Try to re-use the existing output mode (resolution / refresh rate). This may prevent unnecessary screenblank delays when starting labwc diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 9755530b..54c9e300 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -13,6 +13,7 @@ no no yes + no no no yes From d86b2cfd0d2edd645bcb2e45c921daa73cf68026 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Fri, 8 May 2026 16:21:47 +0200 Subject: [PATCH 51/71] docs: mention Vulkan requirement for HDR config --- docs/labwc-config.5.scd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 240f7043..dec027dc 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -244,7 +244,8 @@ this is for compatibility with Openbox. ** [yes|no] Automatically enable HDR support on outputs when configuring them, where supported by the particular output device and display. Default - is no. + is no. Additionally requires the Vulkan backend. Can be set via + `WLR_RENDERER=vulkan` in `~/.config/labwc/enviornment`. ** [yes|no] Try to re-use the existing output mode (resolution / refresh rate). From e2f3c0328792195dfdc5e55bf1f6a668df87bcfd Mon Sep 17 00:00:00 2001 From: Alex Chernika Date: Fri, 8 May 2026 20:35:40 +0200 Subject: [PATCH 52/71] src/menu: slight clean up of menu accelerators Slight clean up of the accelerator implementation, including use of glib for utf8-parsing --- src/menu/menu.c | 76 ++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/menu/menu.c b/src/menu/menu.c index 7b132806..2505317d 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -4,14 +4,12 @@ #include #include #include -#include #include +#include #include #include #include -#include #include -#include #include #include #include @@ -133,42 +131,33 @@ validate(void) } } +/* Read a single Unicode codepoint and convert it to lowercase */ static uint32_t -get_unicode_char(const char *first_byte, size_t *out_bytes) +read_unicode_char_lowercase(const char *first_byte, size_t *bytes_read) { - if (!first_byte || first_byte[0] == '\0') { - *out_bytes = 0; + assert(bytes_read); + + if (string_null_or_empty(first_byte)) { + *bytes_read = 0; return 0; } - /* Temporarily set locale to UTF-8 */ - locale_t utf8_locale = newlocale(LC_CTYPE_MASK, "C.UTF-8", (locale_t)0); - locale_t old_locale = (locale_t)0; - if (utf8_locale != (locale_t)0) { - old_locale = uselocale(utf8_locale); + gunichar codepoint = g_utf8_get_char_validated(first_byte, -1); + + bool partial_read = (codepoint == (gunichar)-2); + bool failed_read = (codepoint == (gunichar)-1); + if (partial_read || failed_read) { + /* Read only the first byte */ + *bytes_read = 1; + return (uint32_t)(unsigned char)first_byte[0]; } - uint32_t result = 0; - char32_t codepoint = 0; - mbstate_t state = {0}; - size_t bytes = mbrtoc32(&codepoint, first_byte, 4, &state); - if (bytes > 0 && bytes <= 4) { - *out_bytes = bytes; - result = (uint32_t)towlower((wint_t)codepoint); - } else { - *out_bytes = 1; - result = (uint32_t)(unsigned char)first_byte[0]; - } + *bytes_read = (size_t)(g_utf8_next_char(first_byte) - first_byte); - /* Restore previous locale */ - if (utf8_locale != (locale_t)0) { - uselocale(old_locale); - freelocale(utf8_locale); - } - - return result; + return (uint32_t)g_unichar_tolower(codepoint); } +/* Retrieve the accelerator from an item label */ static void item_parse_accelerator(struct menuitem *item, const char *text) { @@ -188,25 +177,35 @@ item_parse_accelerator(struct menuitem *item, const char *text) } } - size_t bytes = 0; + size_t bytes_read = 0; if (!accel_ptr) { + /* Default to the first char */ item->text = xstrdup(text); - item->accelerator = get_unicode_char(text, &bytes); + item->accelerator = read_unicode_char_lowercase(text, &bytes_read); } else { + /* Set the accelerator and remove the preceding underscore */ item->use_markup = true; - item->accelerator = get_unicode_char(accel_ptr, &bytes); + item->accelerator = read_unicode_char_lowercase(accel_ptr, &bytes_read); item->text = strdup_printf("%.*s%.*s%s", /* Prefix length + prefix */ (int)(accel_ptr - 1 - text), text, /* Accelerator (utf-8 byte) length + accelerator */ - (int)bytes, accel_ptr, + (int)bytes_read, accel_ptr, /* Remainder */ - accel_ptr + bytes); + accel_ptr + bytes_read); + } +} + +/* Remove underscores used for escaping other underscores from a string */ +static void +unescape_underscores(char *text) +{ + if (!text) { + return; } - /* Remove undescores used for escaping */ - char *src = item->text; - char *dst = item->text; + char *src = text; + char *dst = text; while (*src) { if (*src == '_' && *(src + 1) == '_') { *dst++ = '_'; @@ -231,6 +230,7 @@ item_create(struct menu *menu, const char *text, const char *icon_name, bool sho menuitem->type = LAB_MENU_ITEM; menuitem->arrow = show_arrow ? "›" : NULL; item_parse_accelerator(menuitem, text); + unescape_underscores(menuitem->text); #if HAVE_LIBSFDO if (rc.menu_show_icons && !string_null_or_empty(icon_name)) { @@ -1595,7 +1595,7 @@ menu_item_select_by_accelerator(uint32_t accelerator) menu_process_item_selection(next_selection); if (needs_exec && next_selection->submenu) { - /* Since we can't execute a submenu, enter it. */ + /* Since we can't execute a submenu, enter it */ needs_exec = false; menu_submenu_enter(); } From 90b87cb8823a18ad2995fd67e3a00152325af4e5 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Fri, 8 May 2026 20:53:46 +0200 Subject: [PATCH 53/71] docs/labwc-config: fix HDR comment syntax error Fixes 'Expected ``` and a newline to begin literal block' --- docs/labwc-config.5.scd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index dec027dc..70450e3c 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -244,8 +244,8 @@ this is for compatibility with Openbox. ** [yes|no] Automatically enable HDR support on outputs when configuring them, where supported by the particular output device and display. Default - is no. Additionally requires the Vulkan backend. Can be set via - `WLR_RENDERER=vulkan` in `~/.config/labwc/enviornment`. + is no. Additionally requires the Vulkan backend. Can be set + via `WLR_RENDERER=vulkan` in `~/.config/labwc/enviornment`. ** [yes|no] Try to re-use the existing output mode (resolution / refresh rate). From e1fdbb06118da63d3a0c103600d980885022cf94 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Fri, 8 May 2026 20:55:14 +0200 Subject: [PATCH 54/71] CI: also run CI on docs changes --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6130686b..caa54a33 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,7 @@ on: - 'clients/**' - 't/**' - 'scripts/**' + - 'docs/**' - '.github/workflows/**' jobs: From 477b3b1ddbd46a68f0de475f622ac33e47533c42 Mon Sep 17 00:00:00 2001 From: Standreas Date: Sat, 9 May 2026 14:08:33 +0200 Subject: [PATCH 55/71] Fix typo in labwc-config.5.scd --- docs/labwc-config.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 70450e3c..56097bca 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -245,7 +245,7 @@ this is for compatibility with Openbox. Automatically enable HDR support on outputs when configuring them, where supported by the particular output device and display. Default is no. Additionally requires the Vulkan backend. Can be set - via `WLR_RENDERER=vulkan` in `~/.config/labwc/enviornment`. + via `WLR_RENDERER=vulkan` in `~/.config/labwc/environment`. ** [yes|no] Try to re-use the existing output mode (resolution / refresh rate). From bc287686ea919579abb303570a78c00c87dd615e Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Thu, 7 May 2026 20:52:20 +0100 Subject: [PATCH 56/71] NEWS.md: interim update --- NEWS.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 2e0a6109..9a770ecc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog] | Date | All Changes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| -| 2026-04-29 | [unreleased] | 0.20.0 | 27849 | +| 2026-05-07 | [unreleased] | 0.20.0 | 28236 | | 2026-04-17 | [0.9.7] | 0.19.2 | 29277 | | 2026-03-15 | [0.9.6] | 0.19.2 | 29271 | | 2026-03-04 | [0.9.5] | 0.19.2 | 29251 | @@ -125,6 +125,11 @@ Note to maintainers: ### Added +- Add support for HDR10 output @kode54 @Consolatis [#3424] +- Include wlroots version in --version string @Consolatis [#3567] +- Implement menu accelerators (one-letter mnemonics to quickly select/exec + items from the current menu) @ch3rn1ka [#3505] +- Add Next/PreviousWindowImmediate actions @elviosak @johanmalm [#3547] - Add labnag options `--details-border-color` and `--details-margin` @st0rm-shad0w [#3527] - Add config option `` to defer raise-on-focus by a @@ -147,6 +152,8 @@ Note to maintainers: ### Fixed +- Position chromium popup correctly when a window is maximized on a multi- + output setup @elviosak [#3547] - Run session activation environment update synchronously to avoid a race condition with the autostart script [#3543] @joske - Allow interactive resize on fully maximized windows so that a resize @@ -3272,6 +3279,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3410]: https://github.com/labwc/labwc/pull/3410 [#3411]: https://github.com/labwc/labwc/pull/3411 [#3412]: https://github.com/labwc/labwc/pull/3412 +[#3424]: https://github.com/labwc/labwc/pull/3424 [#3425]: https://github.com/labwc/labwc/pull/3425 [#3426]: https://github.com/labwc/labwc/pull/3426 [#3428]: https://github.com/labwc/labwc/pull/3428 @@ -3292,6 +3300,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3499]: https://github.com/labwc/labwc/pull/3499 [#3500]: https://github.com/labwc/labwc/pull/3500 [#3502]: https://github.com/labwc/labwc/pull/3502 +[#3505]: https://github.com/labwc/labwc/pull/3505 [#3507]: https://github.com/labwc/labwc/pull/3507 [#3511]: https://github.com/labwc/labwc/pull/3511 [#3513]: https://github.com/labwc/labwc/pull/3513 @@ -3300,3 +3309,5 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3534]: https://github.com/labwc/labwc/pull/3534 [#3540]: https://github.com/labwc/labwc/pull/3540 [#3543]: https://github.com/labwc/labwc/pull/3543 +[#3547]: https://github.com/labwc/labwc/pull/3547 +[#3567]: https://github.com/labwc/labwc/pull/3567 From 4b61a8a87925c4628d715bcbd471ecb4c33b3e3c Mon Sep 17 00:00:00 2001 From: stormshadow <190884359+st0rm-shad0w@users.noreply.github.com> Date: Mon, 11 May 2026 00:15:39 +0530 Subject: [PATCH 57/71] fix(labnag): add missing --exclusive-zone to getopt options --- clients/labnag.c | 1 + 1 file changed, 1 insertion(+) diff --git a/clients/labnag.c b/clients/labnag.c index 8d47ee29..60a5abb3 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -1592,6 +1592,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, {"message", required_argument, NULL, 'm'}, {"output", required_argument, NULL, 'o'}, {"timeout", required_argument, NULL, 't'}, + {"exclusive-zone", no_argument, NULL, 'x'}, {"version", no_argument, NULL, 'v'}, {"background-color", required_argument, NULL, TO_COLOR_BACKGROUND}, From 8473ea4b722b7f255590078ac9868538d853f5dd Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Tue, 12 May 2026 21:23:22 +0300 Subject: [PATCH 58/71] main: update --version string --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index 53f85623..501c2b7a 100644 --- a/src/main.c +++ b/src/main.c @@ -67,7 +67,7 @@ static void print_version(void) { #define FEATURE_ENABLED(feature) (HAVE_##feature ? "+" : "-") - printf("labwc %s (%sxwayland %snls %srsvg %slibsfdo) running on wlroots %d.%d.%d\n", + printf("labwc %s (%sxwayland %snls %srsvg %slibsfdo) wlroots-%d.%d.%d\n", LABWC_VERSION, FEATURE_ENABLED(XWAYLAND), FEATURE_ENABLED(NLS), From 7a53f294a8ec4e9ff49dc99ed2bb4bd04f39f3cc Mon Sep 17 00:00:00 2001 From: massi Date: Sun, 10 May 2026 17:40:53 -0700 Subject: [PATCH 59/71] output.c: use env var LABWC_TITLE to set nested labwc window title Introduces an environment variable LABWC_TITLE, which, when set, will be used to set the title of a nested instance of labwc. --- src/output.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/output.c b/src/output.c index 29a2d5a0..1139aca2 100644 --- a/src/output.c +++ b/src/output.c @@ -606,7 +606,11 @@ handle_new_output(struct wl_listener *listener, void *data) if (wlr_output_is_wl(wlr_output)) { char title[64]; - snprintf(title, sizeof(title), "%s - %s", "labwc", wlr_output->name); + if (getenv("LABWC_TITLE")) { + snprintf(title, sizeof(title), "%s", getenv("LABWC_TITLE")); + } else { + snprintf(title, sizeof(title), "%s - %s", "labwc", wlr_output->name); + } wlr_wl_output_set_title(wlr_output, title); wlr_wl_output_set_app_id(wlr_output, "labwc"); } From df6dde131f0ed5d77ec4ce5eeb64e02a32225e50 Mon Sep 17 00:00:00 2001 From: massi Date: Mon, 11 May 2026 16:39:22 -0700 Subject: [PATCH 60/71] window-title: implement window-title command line argument When -t or --window-title is supplied, its required argument is treated as a format string where `%o` is replaced by the `wlr-output`'s `name` when we set the window title. This uses glib to split and join the format string because our own `string-helpers` library only has `str_join`. Otherwise using `string-helpers` would have been preferred. --- include/labwc.h | 2 ++ src/main.c | 11 ++++++++++- src/output.c | 14 +++++++------- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index edde2771..bbee63cf 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -315,6 +315,8 @@ struct server { struct sfdo *sfdo; pid_t primary_client_pid; + + char *window_title_fmt; }; /* defined in main.c */ diff --git a/src/main.c b/src/main.c index 501c2b7a..93351b22 100644 --- a/src/main.c +++ b/src/main.c @@ -9,6 +9,7 @@ #include "common/font.h" #include "common/macros.h" #include "common/spawn.h" +#include "common/string-helpers.h" #include "config/rcxml.h" #include "config/session.h" #include "labwc.h" @@ -37,6 +38,7 @@ static const struct option long_options[] = { {"reconfigure", no_argument, NULL, 'r'}, {"startup", required_argument, NULL, 's'}, {"session", required_argument, NULL, 'S'}, + {"window-title", required_argument, NULL, 't'}, {"version", no_argument, NULL, 'v'}, {"verbose", no_argument, NULL, 'V'}, {0, 0, 0, 0} @@ -178,7 +180,7 @@ main(int argc, char *argv[]) int c; while (1) { int index = 0; - c = getopt_long(argc, argv, "c:C:dehmrs:S:vV", long_options, &index); + c = getopt_long(argc, argv, "c:C:dehmrs:S:t:vV", long_options, &index); if (c == -1) { break; } @@ -207,6 +209,9 @@ main(int argc, char *argv[]) case 'S': primary_client = optarg; break; + case 't': + server.window_title_fmt = optarg; + break; case 'v': print_version(); exit(0); @@ -265,6 +270,10 @@ main(int argc, char *argv[]) increase_nofile_limit(); + if (string_null_or_empty(server.window_title_fmt)) { + server.window_title_fmt = "labwc - %o"; + } + server_init(); server_start(); diff --git a/src/output.c b/src/output.c index 1139aca2..33154e58 100644 --- a/src/output.c +++ b/src/output.c @@ -10,6 +10,7 @@ #include "output.h" #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include "common/macros.h" #include "common/mem.h" #include "common/scene-helpers.h" +#include "common/string-helpers.h" #include "config/rcxml.h" #include "labwc.h" #include "layers.h" @@ -605,13 +607,11 @@ handle_new_output(struct wl_listener *listener, void *data) } if (wlr_output_is_wl(wlr_output)) { - char title[64]; - if (getenv("LABWC_TITLE")) { - snprintf(title, sizeof(title), "%s", getenv("LABWC_TITLE")); - } else { - snprintf(title, sizeof(title), "%s - %s", "labwc", wlr_output->name); - } - wlr_wl_output_set_title(wlr_output, title); + gchar** parts = g_strsplit(server.window_title_fmt, "%o", -1); + gchar* formatted_title = g_strjoinv(wlr_output->name, parts); + g_strfreev(parts); + wlr_wl_output_set_title(wlr_output, formatted_title); + g_free(formatted_title); wlr_wl_output_set_app_id(wlr_output, "labwc"); } From 0a9bd41df0cf15e4242e9b637c29c172d572c508 Mon Sep 17 00:00:00 2001 From: massi Date: Mon, 11 May 2026 14:59:29 -0700 Subject: [PATCH 61/71] window-title: add documentation --- docs/labwc.1.scd | 8 ++++++++ src/main.c | 23 ++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/labwc.1.scd b/docs/labwc.1.scd index 31f28c19..30abf3f6 100644 --- a/docs/labwc.1.scd +++ b/docs/labwc.1.scd @@ -70,6 +70,14 @@ the `--exit` and `--reconfigure` options use. session clients support both X11 and Wayland, this command line option avoids re-writes and fragmentation. +*-t, --window-title* + Set the window title for labwc to use when nested in a compositor. + is a format string to be used as the window title, replacing + `%o` with the name of the output region. This is useful when simulating + multiple screens, such as with running labwc with the enviornment + variable `WLR_WL_OUTPUTS=2`. In this case, `%o` will be unique per + simulated screen. + *-v, --version* Show the version number and quit diff --git a/src/main.c b/src/main.c index 93351b22..98c1f1bf 100644 --- a/src/main.c +++ b/src/main.c @@ -46,17 +46,18 @@ static const struct option long_options[] = { static const char labwc_usage[] = "Usage: labwc [options...]\n" -" -c, --config Specify config file (with path)\n" -" -C, --config-dir Specify config directory\n" -" -d, --debug Enable full logging, including debug information\n" -" -e, --exit Exit the compositor\n" -" -h, --help Show help message and quit\n" -" -m, --merge-config Merge user config files/theme in all XDG Base Dirs\n" -" -r, --reconfigure Reload the compositor configuration\n" -" -s, --startup Run command on startup\n" -" -S, --session Run command on startup and terminate on exit\n" -" -v, --version Show version number and quit\n" -" -V, --verbose Enable more verbose logging\n"; +" -c, --config Specify config file (with path)\n" +" -C, --config-dir Specify config directory\n" +" -d, --debug Enable full logging, including debug information\n" +" -e, --exit Exit the compositor\n" +" -h, --help Show help message and quit\n" +" -m, --merge-config Merge user config files/theme in all XDG Base Dirs\n" +" -r, --reconfigure Reload the compositor configuration\n" +" -s, --startup Run command on startup\n" +" -S, --session Run command on startup and terminate on exit\n" +" -t, --window-title Specify title to use when the compositor is nested\n" +" -v, --version Show version number and quit\n" +" -V, --verbose Enable more verbose logging\n"; static void usage(void) From cd7be59d5fea887b41abb6a0edcedcc233795c36 Mon Sep 17 00:00:00 2001 From: massi Date: Tue, 12 May 2026 17:06:18 -0700 Subject: [PATCH 62/71] window-title: rewording, renaming, lint fixes --- docs/labwc.1.scd | 14 +++++++------- include/labwc.h | 2 +- src/main.c | 32 ++++++++++++++++---------------- src/output.c | 4 ++-- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/labwc.1.scd b/docs/labwc.1.scd index 30abf3f6..3be47743 100644 --- a/docs/labwc.1.scd +++ b/docs/labwc.1.scd @@ -70,13 +70,13 @@ the `--exit` and `--reconfigure` options use. session clients support both X11 and Wayland, this command line option avoids re-writes and fragmentation. -*-t, --window-title* - Set the window title for labwc to use when nested in a compositor. - is a format string to be used as the window title, replacing - `%o` with the name of the output region. This is useful when simulating - multiple screens, such as with running labwc with the enviornment - variable `WLR_WL_OUTPUTS=2`. In this case, `%o` will be unique per - simulated screen. +*-t, --title* + Set the window title for labwc to use when it is running in a window + (i.e. nested in a compositor). is a format string to be used as + the window title, replacing `%o` with the name of the output + region. This is useful when simulating multiple screens, such as with + running labwc with the enviornment variable `WLR_WL_OUTPUTS=2`. In this + case, `%o` will be unique per simulated screen. *-v, --version* Show the version number and quit diff --git a/include/labwc.h b/include/labwc.h index bbee63cf..a6d69317 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -316,7 +316,7 @@ struct server { pid_t primary_client_pid; - char *window_title_fmt; + char *title_fmt; }; /* defined in main.c */ diff --git a/src/main.c b/src/main.c index 98c1f1bf..081bd62a 100644 --- a/src/main.c +++ b/src/main.c @@ -38,7 +38,7 @@ static const struct option long_options[] = { {"reconfigure", no_argument, NULL, 'r'}, {"startup", required_argument, NULL, 's'}, {"session", required_argument, NULL, 'S'}, - {"window-title", required_argument, NULL, 't'}, + {"title", required_argument, NULL, 't'}, {"version", no_argument, NULL, 'v'}, {"verbose", no_argument, NULL, 'V'}, {0, 0, 0, 0} @@ -46,18 +46,18 @@ static const struct option long_options[] = { static const char labwc_usage[] = "Usage: labwc [options...]\n" -" -c, --config Specify config file (with path)\n" -" -C, --config-dir Specify config directory\n" -" -d, --debug Enable full logging, including debug information\n" -" -e, --exit Exit the compositor\n" -" -h, --help Show help message and quit\n" -" -m, --merge-config Merge user config files/theme in all XDG Base Dirs\n" -" -r, --reconfigure Reload the compositor configuration\n" -" -s, --startup Run command on startup\n" -" -S, --session Run command on startup and terminate on exit\n" -" -t, --window-title Specify title to use when the compositor is nested\n" -" -v, --version Show version number and quit\n" -" -V, --verbose Enable more verbose logging\n"; +" -c, --config Specify config file (with path)\n" +" -C, --config-dir Specify config directory\n" +" -d, --debug Enable full logging, including debug information\n" +" -e, --exit Exit the compositor\n" +" -h, --help Show help message and quit\n" +" -m, --merge-config Merge user config files/theme in all XDG Base Dirs\n" +" -r, --reconfigure Reload the compositor configuration\n" +" -s, --startup Run command on startup\n" +" -S, --session Run command on startup and terminate on exit\n" +" -t, --title Specify title to use when running in a window\n" +" -v, --version Show version number and quit\n" +" -V, --verbose Enable more verbose logging\n"; static void usage(void) @@ -211,7 +211,7 @@ main(int argc, char *argv[]) primary_client = optarg; break; case 't': - server.window_title_fmt = optarg; + server.title_fmt = optarg; break; case 'v': print_version(); @@ -271,8 +271,8 @@ main(int argc, char *argv[]) increase_nofile_limit(); - if (string_null_or_empty(server.window_title_fmt)) { - server.window_title_fmt = "labwc - %o"; + if (string_null_or_empty(server.title_fmt)) { + server.title_fmt = "labwc - %o"; } server_init(); diff --git a/src/output.c b/src/output.c index 33154e58..6811b783 100644 --- a/src/output.c +++ b/src/output.c @@ -607,8 +607,8 @@ handle_new_output(struct wl_listener *listener, void *data) } if (wlr_output_is_wl(wlr_output)) { - gchar** parts = g_strsplit(server.window_title_fmt, "%o", -1); - gchar* formatted_title = g_strjoinv(wlr_output->name, parts); + gchar **parts = g_strsplit(server.title_fmt, "%o", -1); + gchar *formatted_title = g_strjoinv(wlr_output->name, parts); g_strfreev(parts); wlr_wl_output_set_title(wlr_output, formatted_title); g_free(formatted_title); From 4af693a7fdf61d0f0bf8c5225335c4d6eadbd543 Mon Sep 17 00:00:00 2001 From: massi Date: Fri, 15 May 2026 18:01:47 -0700 Subject: [PATCH 63/71] window-title: cleaner string replace --- src/output.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/output.c b/src/output.c index 6811b783..2eab8ec3 100644 --- a/src/output.c +++ b/src/output.c @@ -607,11 +607,10 @@ handle_new_output(struct wl_listener *listener, void *data) } if (wlr_output_is_wl(wlr_output)) { - gchar **parts = g_strsplit(server.title_fmt, "%o", -1); - gchar *formatted_title = g_strjoinv(wlr_output->name, parts); - g_strfreev(parts); - wlr_wl_output_set_title(wlr_output, formatted_title); - g_free(formatted_title); + GString *title = g_string_new(server.title_fmt); + g_string_replace(title, "%o", wlr_output->name, 0); + wlr_wl_output_set_title(wlr_output, title->str); + g_string_free(title, TRUE); wlr_wl_output_set_app_id(wlr_output, "labwc"); } From 80ff89c640007a3fa6428d5dd79db3044adfa336 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Sun, 17 May 2026 22:04:52 +0300 Subject: [PATCH 64/71] Typo/style fixes in docs and code comments Codespell(1)-pointed typos in files listed by `git ls-files` (sans checkpatch.pl and possibly some others). Removed some extra spaces. Added a few missing trailing periods. `Default is ...`. Added spaces in ' />' where missing (sans e.g. wayland protocol and t/* files). Fit some lines in docs/*.scd to 80 colums. Used git grep commands (to find similar cases): $ git grep -n '\S/>' $ git grep -nF '. ' $ git grep -n '[^\t*'\'',{#]\t' $ git grep -ni 'default ' No functional change. No change in *.[ch] line numbers. --- docs/autostart | 2 +- docs/labwc-actions.5.scd | 62 +++++++++++++-------------- docs/labwc-config.5.scd | 38 ++++++++-------- docs/labwc-menu.5.scd | 8 ++-- docs/labwc-theme.5.scd | 6 +-- docs/labwc.1.scd | 4 +- docs/menu.xml | 6 +-- docs/rc.xml.all | 14 +++--- include/config/default-bindings.h | 8 ++-- include/scaled-buffer/scaled-buffer.h | 2 +- src/action.c | 4 +- src/config/rcxml.c | 6 +-- src/img/img-xbm.c | 2 +- src/menu/menu.c | 4 +- src/theme.c | 2 +- src/window-rules.c | 4 +- 16 files changed, 88 insertions(+), 84 deletions(-) diff --git a/docs/autostart b/docs/autostart index 17fcc270..18adb23b 100644 --- a/docs/autostart +++ b/docs/autostart @@ -12,7 +12,7 @@ swaybg -c '#113344' >/dev/null 2>&1 & # Configure output directives such as mode, position, scale and transform. -# Use wlr-randr to get your output names +# Use wlr-randr to get your output names. # Example ~/.config/kanshi/config below: # profile { # output HDMI-A-1 position 1366,0 diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 47ee1801..2677e348 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -16,7 +16,7 @@ Actions are used in menus and keyboard/mouse bindings. SIGTERM signal. ** - Execute command. Note that in the interest of backward compatibility, + Execute command. Note that in the interest of backward compatibility, labwc supports as an alternative to even though openbox documentation states that it is deprecated. Note: Tilde (~) is expanded in the command before passing to execvp(). @@ -139,22 +139,21 @@ Actions are used in menus and keyboard/mouse bindings. and OSD, useful for binding to keys without modifiers. *workspace* [all|current] - This determines whether to cycle through windows on all workspaces or the - current workspace. Default is "current". + This determines whether to cycle through windows on all workspaces or + the current workspace. Default is "current". *output* [all|focused|cursor] - This determines whether to cycle through windows on all outputs, the focused - output, or the output under the cursor. Default is "all". + This determines whether to cycle through windows on all outputs, the + focused output, or the output under the cursor. Default is "all". *identifier* [all|current] - This determines whether to cycle through all windows or only windows of the - same application as the currently focused window. Default is "all". + This determines whether to cycle through all windows or only windows of + the same application as the currently focused window. Default is "all". ** Re-load configuration and theme files. -** - +** Show a menu. ``` @@ -300,7 +299,7 @@ Actions are used in menus and keyboard/mouse bindings. (if one exists). *wrap* [yes|no] When using the direction attribute, wrap around from - right-to-left or top-to-bottom, and vice versa. Default no. + right-to-left or top-to-bottom, and vice versa. Default is no. ** Resizes active window size to width and height of the output when the @@ -314,10 +313,10 @@ Actions are used in menus and keyboard/mouse bindings. workspace or its index (starting at 1) as configured in rc.xml. *wrap* [yes|no] Wrap around from last desktop to first, and vice - versa. Default yes. + versa. Default is yes. *toggle* [yes|no] Toggle to “last” if already on the workspace that - would be the actual destination. Default no. + would be the actual destination. Default is no. ** Send active window to workspace. @@ -325,10 +324,11 @@ Actions are used in menus and keyboard/mouse bindings. *to* The workspace to send the window to. Supported values are the same as for GoToDesktop. - *follow* [yes|no] Also switch to the specified workspace. Default yes. + *follow* [yes|no] Also switch to the specified workspace. + Default is yes. *wrap* [yes|no] Wrap around from last desktop to first, and vice - versa. Default yes. + versa. Default is yes. ** Add virtual output (headless backend). @@ -346,11 +346,11 @@ Actions are used in menus and keyboard/mouse bindings. ``` - - + + - + ``` @@ -371,7 +371,7 @@ Actions are used in menus and keyboard/mouse bindings. *output_name* The name of virtual output. If not supplied, will remove the last virtual output added. -** +** Reposition the window according to the desired placement policy. *policy* [automatic|cursor|center|cascade] Use the specified policy, @@ -430,11 +430,11 @@ Actions are used in menus and keyboard/mouse bindings. used. ** - Minimize all windows in the current workspace so that the desktop becomes - visible. On calling the action again the hidden windows are unminimized, - provided that - since the initial `ShowDesktop` - (a) no windows have been - unminimized; (b) workspaces have not been switched; and (c) no new - applications have been started. + Minimize all windows in the current workspace so that the desktop + becomes visible. On calling the action again the hidden windows are + unminimized, provided that - since the initial `ShowDesktop` - (a) no + windows have been unminimized; (b) workspaces have not been switched; + and (c) no new applications have been started. **++ ** @@ -449,8 +449,8 @@ Actions are used in menus and keyboard/mouse bindings. binding. ** - Toggle visibility of key-state on-screen display (OSD). Note: This is for - debugging purposes only. + Toggle visibility of key-state on-screen display (OSD). Note: This is + for debugging purposes only. # CONDITIONAL ACTIONS @@ -464,10 +464,10 @@ Actions that execute other actions. Used in keyboard/mouse bindings. ``` - - - - + + + + ``` @@ -549,9 +549,9 @@ Actions that execute other actions. Used in keyboard/mouse bindings. ``` - + - + diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 56097bca..5691a4e1 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -25,7 +25,7 @@ The XDG Base Directory Specification does not specify whether or not programs should (a) allow the first-identified configuration file to supersede any others, or (b) define rules for merging the information from more than one file. -By default, labwc uses option (a), reading only the first file identified. With +By default, labwc uses option (a), reading only the first file identified. With the --merge-config option, the search order is reversed, but every configuration file encountered is processed in turn. Thus, user-specific files will augment system-wide configurations, with conflicts favoring the user-specific @@ -526,7 +526,8 @@ extending outward from the snapped edge. ** and **, and 50 for **. ** [yes|no] - Show an overlay when snapping a window to an output edge. Default is yes. + Show an overlay when snapping a window to an output edge. + Default is yes. **++ ** @@ -595,7 +596,7 @@ extending outward from the snapped edge. A setting of 0 disables the OSD. Default is 1000 ms. ** - Set the prefix to use when using "number" above. Default is "Workspace" + Set the prefix to use when using "number" above. Default is "Workspace". ## THEME @@ -749,7 +750,7 @@ generate gesture events, like swipe and pinch. There are some related settings (e.g. *threeFingerDrag* and *twoFingerScroll*) in the ** section. In the Wayland Compositor domain, events associated with touchscreens are -sometimes simply referred to as *touch* events. Touchscreens can be configured +sometimes simply referred to as *touch* events. Touchscreens can be configured in both the ** and ** sections. Note that touchscreen gestures are not interpreted by libinput, nor labwc. Any touch point is passed to the client (application) for any interpretation of gestures. @@ -824,9 +825,10 @@ overrideInhibition="">* Make this keybind work even if the screen is locked. Default is no. *overrideInhibition* [yes|no] - Make this keybind work even if the view inhibits keybinds. Default is no. + Make this keybind work even if the view inhibits keybinds. This can be used to prevent W-Tab and similar keybinds from being delivered to Virtual Machines, VNC clients or nested compositors. + Default is no. *onRelease* [yes|no] When yes, fires the keybind action when the key or key @@ -840,7 +842,7 @@ overrideInhibition="">* ``` - + ``` @@ -900,7 +902,7 @@ input-devices by the Wayland protocol. - Shade: A button that, by default, toggles window shading. - AllDesktops: A button that, by default, toggles omnipresence of a window. - - Close: A button that, by default, closses a window. + - Close: A button that, by default, closes a window. - Border: The window's border including Top...BRCorner below. - Top: The top edge of the window's border. - Bottom: The bottom edge of the window's border. @@ -974,10 +976,10 @@ input-devices by the Wayland protocol. ``` - + - - + + ``` @@ -985,7 +987,7 @@ input-devices by the Wayland protocol. ** Load default mousebinds. This is an addition to the openbox specification and provides a way to keep config files simpler whilst - allowing user specific binds. Note that if no rc.xml is found, or if no + allowing user specific binds. Note that if no rc.xml is found, or if no entries exist, the same default mousebinds will be loaded even if the element is not provided. @@ -997,7 +999,7 @@ Note: To rotate touch events with output rotation, use the libinput *calibrationMatrix* setting. ``` - + ``` ** @@ -1260,7 +1262,8 @@ Note: To rotate touch events with output rotation, use the libinput The default method depends on the touchpad hardware. ** [none|twofinger|edge|onbutton] - Configure the method by which physical movements are mapped to scroll events. + Configure the method by which physical movements are mapped to scroll + events. The scroll methods available are: - *twofinger* - Scroll by two fingers being placed on the surface of the @@ -1275,7 +1278,8 @@ Note: To rotate touch events with output rotation, use the libinput ** [button] Set the button used for the *onbutton* scroll method. - *button* is the decimal form of a value from `linux/input-event-codes.h`. + *button* is the decimal form of a value + from `linux/input-event-codes.h`. ** [yes|no|disabledOnExternalMouse] Optionally enable or disable sending any device events. @@ -1324,7 +1328,7 @@ defined as shown below. - + @@ -1521,7 +1525,7 @@ This is the full list of interfaces that can be controlled with this mechanism: *XCURSOR_PATH* Specify a colon-separated list of paths to look for mouse cursors in. - Default + Default is ~/.local/share/icons: ~/.icons: /usr/share/icons: @@ -1532,7 +1536,7 @@ This is the full list of interfaces that can be controlled with this mechanism: *XCURSOR_SIZE* Specify an alternative mouse cursor size in pixels. Requires - XCURSOR_THEME to be set also. Default 24. + XCURSOR_THEME to be set also. Default is 24. *XCURSOR_THEME* Specify a mouse cursor theme within XCURSOR_PATH. diff --git a/docs/labwc-menu.5.scd b/docs/labwc-menu.5.scd index df04ccf9..e2c40d84 100644 --- a/docs/labwc-menu.5.scd +++ b/docs/labwc-menu.5.scd @@ -12,7 +12,7 @@ Static menus are built based on the menu.xml file located at # SYNTAX The menu file must be entirely enclosed within and - tags. Inside these tags, menus are specified as follows: + tags. Inside these tags, menus are specified as follows: ``` @@ -111,7 +111,7 @@ Pipe menus are menus generated dynamically based on output of scripts or binaries. They are so-called because the output of the executable is piped to the labwc menu. -For any ** entry in menu.xml, the +For any ** entry in menu.xml, the COMMAND will be executed the first time the item is selected (for example by cursor or keyboard input). The XML output of the command will be parsed and shown as a submenu. The content of pipemenus is cached until the whole menu @@ -124,7 +124,7 @@ menus, for example: ``` - + ``` @@ -144,7 +144,7 @@ obmenu-generator with the menu generator of your choice): ``` - + ``` diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index 5f99cae7..dce48bdf 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -135,7 +135,7 @@ window.*.title.bg.colorTo.splitTo: #557485 *window.active.title.bg* Texture for the focused window's titlebar. See texture section above. - Default is *Solid* + Default is *Solid*. *window.active.title.bg.color* Background color for the focused window's titlebar. See texture section @@ -144,7 +144,7 @@ window.*.title.bg.colorTo.splitTo: #557485 *window.inactive.title.bg* Texture for non-focused windows' titlebars. See texture section above. - Default is *Solid* + Default is *Solid*. *window.inactive.title.bg.color* Background color for non-focused windows' titlebars. See texture section @@ -470,7 +470,7 @@ all are supported. Width of magnifier window border in pixels. Default is 1. *magnifier.border.color* - Color of the magnfier window border. Default is #ff0000 (red). + Color of the magnifier window border. Default is #ff0000 (red). # BUTTONS diff --git a/docs/labwc.1.scd b/docs/labwc.1.scd index 3be47743..56dfe775 100644 --- a/docs/labwc.1.scd +++ b/docs/labwc.1.scd @@ -66,7 +66,7 @@ the `--exit` and `--reconfigure` options use. Manager, or the Window Manager can be launched independently first. On Wayland, the Compositor is both Display Server and Window Manager, so the described session management mechanisms do not work because the - Compositor needs to be running before the session can function. As some + Compositor needs to be running before the session can function. As some session clients support both X11 and Wayland, this command line option avoids re-writes and fragmentation. @@ -75,7 +75,7 @@ the `--exit` and `--reconfigure` options use. (i.e. nested in a compositor). is a format string to be used as the window title, replacing `%o` with the name of the output region. This is useful when simulating multiple screens, such as with - running labwc with the enviornment variable `WLR_WL_OUTPUTS=2`. In this + running labwc with the environment variable `WLR_WL_OUTPUTS=2`. In this case, `%o` will be unique per simulated screen. *-v, --version* diff --git a/docs/menu.xml b/docs/menu.xml index 4b4d5dda..3344161a 100644 --- a/docs/menu.xml +++ b/docs/menu.xml @@ -23,7 +23,7 @@ Any menu with the id "workspaces" will be hidden if there is only a single workspace available. --> - + diff --git a/include/config/default-bindings.h b/include/config/default-bindings.h index a49f60f4..788e8d2e 100644 --- a/include/config/default-bindings.h +++ b/include/config/default-bindings.h @@ -141,14 +141,14 @@ static struct key_combos { * * * - * - * - * + * + * + * * * * * - * + * * * * diff --git a/include/scaled-buffer/scaled-buffer.h b/include/scaled-buffer/scaled-buffer.h index c2af6054..017e29fb 100644 --- a/include/scaled-buffer/scaled-buffer.h +++ b/include/scaled-buffer/scaled-buffer.h @@ -130,7 +130,7 @@ void scaled_buffer_request_update(struct scaled_buffer *self, /** * scaled_buffer_invalidate_sharing - clear the list of entire cached - * scaled_buffers used to share visually dupliated buffers. This should + * scaled_buffers used to share visually duplicated buffers. This should * be called on Reconfigure to force updates of newly created * scaled_buffers rather than reusing ones created before Reconfigure. */ diff --git a/src/action.c b/src/action.c index daf8fb30..265ff6e7 100644 --- a/src/action.c +++ b/src/action.c @@ -145,7 +145,7 @@ struct action_arg_list { * Will expand to: * * enum action_type { - * ACTION_TYPE_INVALID, + * ACTION_TYPE_INVALID = 0, * ACTION_TYPE_NONE, * ACTION_TYPE_CLOSE, * ACTION_TYPE_KILL, @@ -1312,7 +1312,7 @@ run_action(struct view *view, struct action *action, /* * To support only setting one of width/height - * in + * in * we fall back to current dimension when unset. */ struct wlr_box box = { diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 0514b6c7..ab0639d1 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -613,9 +613,9 @@ fill_mousebind(xmlNode *node, const char *context) /* * Example of what we are parsing: * - * - * - * + * + * + * * */ diff --git a/src/img/img-xbm.c b/src/img/img-xbm.c index 91269f64..3771444a 100644 --- a/src/img/img-xbm.c +++ b/src/img/img-xbm.c @@ -226,7 +226,7 @@ out: /* * Openbox built-in icons are not bigger than 8x8, so have only written this - * function to cope wit that max size + * function to cope with that max size */ #define LABWC_BUILTIN_ICON_MAX_SIZE (8) diff --git a/src/menu/menu.c b/src/menu/menu.c index 2505317d..c0e93c59 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -172,7 +172,7 @@ item_parse_accelerator(struct menuitem *item, const char *text) accel_ptr = underscore + 1; break; } else { - /* Ignore empty accelertor */ + /* Ignore empty accelerator */ break; } } @@ -627,7 +627,7 @@ fill_menu(struct menu *parent, xmlNode *n) * * * - * + * * */ } else { diff --git a/src/theme.c b/src/theme.c index 93ac1c5e..71e577ce 100644 --- a/src/theme.c +++ b/src/theme.c @@ -788,7 +788,7 @@ entry(struct theme *theme, const char *key, const char *value) value, "window.button.spacing"); } - /* botton hover overlay */ + /* button hover overlay */ if (match_glob(key, "window.button.hover.bg.color")) { parse_color(value, theme->window_button_hover_bg_color); } diff --git a/src/window-rules.c b/src/window-rules.c index f43b92f4..4118ae32 100644 --- a/src/window-rules.c +++ b/src/window-rules.c @@ -69,8 +69,8 @@ window_rules_get_property(struct view *view, const char *property) * for foot's "serverDecoration" property to be "default". * * - * - * + * + * * */ struct window_rule *rule; From d5eb227c773c90528b0e99d10f1e4a0267c5e6ea Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 18 May 2026 20:28:57 +0200 Subject: [PATCH 65/71] Translation updates from weblate Co-authored-by: Demian Translate-URL: https://translate.lxqt-project.org/projects/labwc/labwc/de/ Translation: Labwc/labwc --- po/de.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/po/de.po b/po/de.po index 5841f3f6..d83ef64a 100644 --- a/po/de.po +++ b/po/de.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-09-19 21:09+1000\n" -"PO-Revision-Date: 2025-08-15 20:01+0000\n" -"Last-Translator: Ettore Atalan \n" +"PO-Revision-Date: 2026-05-18 18:28+0000\n" +"Last-Translator: Demian \n" "Language-Team: German \n" "Language: de\n" @@ -21,7 +21,7 @@ msgstr "" #: src/menu/menu.c:1016 msgid "Go there..." -msgstr "Dorthin gehen..." +msgstr "Dorthin wechseln ..." #: src/menu/menu.c:1034 msgid "Terminal" @@ -61,11 +61,11 @@ msgstr "Immer im Vordergrund" #: src/menu/menu.c:1071 msgid "Move Left" -msgstr "nach links" +msgstr "Nach links" #: src/menu/menu.c:1078 msgid "Move Right" -msgstr "nach rechts" +msgstr "Nach rechts" #: src/menu/menu.c:1083 msgid "Always on Visible Workspace" From bce14a5ad7981e9ab99dc5b75a922438930ff39b Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Sun, 6 Jul 2025 01:04:55 +0200 Subject: [PATCH 66/71] toplevel-capture: partial initial implementation Missing: - xwayland child windows - xwayland unmanaged windows, e.g. popups / menus / ... - xdg child window positioning - xdg subsurfaces (test-case: mate-terminal settings listboxes) - xdg popup positioning --- include/labwc.h | 7 +++++ include/view.h | 7 +++++ meson.build | 2 +- src/foreign-toplevel/ext-foreign.c | 3 ++ src/server.c | 49 ++++++++++++++++++++++++++++++ src/view.c | 7 +++++ src/xdg-popup.c | 2 ++ src/xdg.c | 24 +++++++++++++++ src/xwayland.c | 1 + 9 files changed, 101 insertions(+), 1 deletion(-) diff --git a/include/labwc.h b/include/labwc.h index a6d69317..87a42198 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -194,6 +194,13 @@ struct server { struct wlr_xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager; struct wl_listener xdg_toplevel_icon_set_icon; + struct { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager; + struct { + struct wl_listener new_request; + } on; + } toplevel_capture; + /* front to back order */ struct wl_list views; uint64_t next_view_creation_id; diff --git a/include/view.h b/include/view.h index a179338e..46943ab2 100644 --- a/include/view.h +++ b/include/view.h @@ -177,6 +177,12 @@ struct view { char *title; char *app_id; /* WM_CLASS for xwayland windows */ + struct { + struct wlr_scene *scene; + struct wlr_ext_image_capture_source_v1 *source; + struct wl_listener on_capture_source_destroy; + } capture; + bool mapped; bool been_mapped; uint64_t creation_id; @@ -319,6 +325,7 @@ struct xdg_toplevel_view { /* Events unique to xdg-toplevel views */ struct wl_listener set_app_id; struct wl_listener request_show_window_menu; + struct wl_listener set_parent; struct wl_listener new_popup; }; diff --git a/meson.build b/meson.build index 7e345ba6..22dc5bdc 100644 --- a/meson.build +++ b/meson.build @@ -53,7 +53,7 @@ add_project_arguments('-DLABWC_VERSION=@0@'.format(version), language: 'c') wlroots = dependency( 'wlroots-0.20', default_options: ['default_library=static', 'examples=false'], - version: ['>=0.20.0', '<0.21.0'], + version: ['>=0.20.1', '<0.21.0'], ) wlroots_has_xwayland = wlroots.get_variable('have_xwayland') == 'true' diff --git a/src/foreign-toplevel/ext-foreign.c b/src/foreign-toplevel/ext-foreign.c index 78774433..1dfa1d96 100644 --- a/src/foreign-toplevel/ext-foreign.c +++ b/src/foreign-toplevel/ext-foreign.c @@ -75,6 +75,9 @@ ext_foreign_toplevel_init(struct ext_foreign_toplevel *ext_toplevel, return; } + /* In support for ext-toplevel-capture */ + ext_toplevel->handle->data = view; + /* Client side requests */ ext_toplevel->on.handle_destroy.notify = handle_handle_destroy; wl_signal_add(&ext_toplevel->handle->events.destroy, &ext_toplevel->on.handle_destroy); diff --git a/src/server.c b/src/server.c index c792d8b4..d0c8c04c 100644 --- a/src/server.c +++ b/src/server.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include "config.h" +#include #include #include #include @@ -410,6 +411,39 @@ handle_renderer_lost(struct wl_listener *listener, void *data) wlr_renderer_destroy(old_renderer); } +static void +handle_toplevel_capture_source_destroy(struct wl_listener *listener, void *data) +{ + struct view *view = wl_container_of(listener, view, capture.on_capture_source_destroy); + assert(view->capture.source); + view->capture.source = NULL; + wl_list_remove(&listener->link); + wl_list_init(&listener->link); +} + +static void +handle_toplevel_capture_request(struct wl_listener *listener, void *data) +{ + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = data; + struct view *view = request->toplevel_handle->data; + assert(view); + wlr_log(WLR_INFO, "Capturing toplevel %s", view->app_id); + + if (!view->capture.source) { + view->capture.source = wlr_ext_image_capture_source_v1_create_with_scene_node( + &view->capture.scene->tree.node, server.wl_event_loop, + server.allocator, server.renderer); + assert(view->capture.source); + + view->capture.on_capture_source_destroy.notify = + handle_toplevel_capture_source_destroy; + wl_signal_add(&view->capture.source->events.destroy, + &view->capture.on_capture_source_destroy); + } + wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept( + request, view->capture.source); +} + void server_init(void) { @@ -689,6 +723,19 @@ server_init(void) wlr_screencopy_manager_v1_create(server.wl_display); wlr_ext_image_copy_capture_manager_v1_create(server.wl_display, 1); wlr_ext_output_image_capture_source_manager_v1_create(server.wl_display, 1); + + server.toplevel_capture.manager = + wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create( + server.wl_display, 1); + if (server.toplevel_capture.manager) { + server.toplevel_capture.on.new_request.notify = handle_toplevel_capture_request; + wl_signal_add(&server.toplevel_capture.manager->events.new_request, + &server.toplevel_capture.on.new_request); + } else { + /* Allow safe removal on shutdown */ + wl_list_init(&server.toplevel_capture.on.new_request.link); + } + wlr_data_control_manager_v1_create(server.wl_display); wlr_ext_data_control_manager_v1_create(server.wl_display, LAB_EXT_DATA_CONTROL_VERSION); @@ -823,6 +870,8 @@ server_finish(void) server.drm_lease_request.notify = NULL; } + wl_list_remove(&server.toplevel_capture.on.new_request.link); + wlr_backend_destroy(server.backend); wlr_allocator_destroy(server.allocator); diff --git a/src/view.c b/src/view.c index bcbd366e..21005f20 100644 --- a/src/view.c +++ b/src/view.c @@ -2482,6 +2482,10 @@ view_init(struct view *view) view->title = xstrdup(""); view->app_id = xstrdup(""); + + view->capture.scene = wlr_scene_create(); + view->capture.scene->restack_xwayland_surfaces = false; + wl_list_init(&view->capture.on_capture_source_destroy.link); } void @@ -2503,6 +2507,9 @@ view_destroy(struct view *view) wl_list_remove(&view->request_fullscreen.link); wl_list_remove(&view->set_title.link); wl_list_remove(&view->destroy.link); + wl_list_remove(&view->capture.on_capture_source_destroy.link); + + wlr_scene_node_destroy(&view->capture.scene->tree.node); if (view->foreign_toplevel) { foreign_toplevel_destroy(view->foreign_toplevel); diff --git a/src/xdg-popup.c b/src/xdg-popup.c index 8e6870af..7ca9ab01 100644 --- a/src/xdg-popup.c +++ b/src/xdg-popup.c @@ -169,4 +169,6 @@ xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup) node_descriptor_create(wlr_popup->base->surface->data, LAB_NODE_XDG_POPUP, view, /*data*/ NULL); + + wlr_scene_xdg_surface_create(&view->capture.scene->tree, wlr_popup->base); } diff --git a/src/xdg.c b/src/xdg.c index 211a340d..03035d63 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -30,6 +30,8 @@ #define LAB_XDG_SHELL_VERSION 6 #define CONFIGURE_TIMEOUT_MS 100 +static struct view *xdg_toplevel_view_get_root(struct view *view); + static struct xdg_toplevel_view * xdg_toplevel_view_from_view(struct view *view) { @@ -462,6 +464,7 @@ handle_destroy(struct wl_listener *listener, void *data) /* Remove xdg-shell view specific listeners */ wl_list_remove(&xdg_toplevel_view->set_app_id.link); wl_list_remove(&xdg_toplevel_view->request_show_window_menu.link); + wl_list_remove(&xdg_toplevel_view->set_parent.link); wl_list_remove(&xdg_toplevel_view->new_popup.link); wl_list_remove(&view->commit.link); @@ -565,6 +568,23 @@ handle_request_show_window_menu(struct wl_listener *listener, void *data) menu_open_root(menu, cursor->x, cursor->y); } +static void +handle_set_parent(struct wl_listener *listener, void *data) +{ + struct xdg_toplevel_view *xdg_toplevel_view = wl_container_of( + listener, xdg_toplevel_view, set_parent); + struct view *view = &xdg_toplevel_view->base; + struct view *view_root = xdg_toplevel_view_get_root(view); + if (view_root == view) { + return; + } + struct wlr_scene_node *node, *tmp; + wl_list_for_each_safe(node, tmp, &view->capture.scene->tree.children, link) { + wlr_log(WLR_DEBUG, "moving capture scene node to view_root"); + wlr_scene_node_reparent(node, &view_root->capture.scene->tree); + } +} + static void handle_set_title(struct wl_listener *listener, void *data) { @@ -1046,6 +1066,9 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data) mappable_connect(&view->mappable, xdg_surface->surface, handle_map, handle_unmap); + struct view *root_view = xdg_toplevel_view_get_root(view); + wlr_scene_xdg_surface_create(&root_view->capture.scene->tree, xdg_surface); + struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel; CONNECT_SIGNAL(toplevel, view, destroy); CONNECT_SIGNAL(toplevel, view, request_move); @@ -1059,6 +1082,7 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data) /* Events specific to XDG toplevel views */ CONNECT_SIGNAL(toplevel, xdg_toplevel_view, set_app_id); CONNECT_SIGNAL(toplevel, xdg_toplevel_view, request_show_window_menu); + CONNECT_SIGNAL(toplevel, xdg_toplevel_view, set_parent); CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup); wl_list_insert(&server.views, &view->link); diff --git a/src/xwayland.c b/src/xwayland.c index 967364c5..5838d412 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -783,6 +783,7 @@ handle_map(struct wl_listener *listener, void *data) view->content_tree = wlr_scene_subsurface_tree_create( view->scene_tree, view->surface); die_if_null(view->content_tree); + wlr_scene_subsurface_tree_create(&view->capture.scene->tree, view->surface); } wlr_scene_node_set_enabled(&view->content_tree->node, !view->shaded); From 151acae28a9a07de2e2fc489a7743b5f89f80150 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Mon, 25 May 2026 14:26:53 +0100 Subject: [PATCH 67/71] Add default keybind super-d for ToggleShowDesktop --- README.md | 1 + docs/labwc-config.5.scd | 1 + docs/rc.xml.all | 3 +++ include/config/default-bindings.h | 3 +++ 4 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 9cc81e88..80796af7 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,7 @@ If you have not created an rc.xml config file, default bindings will be: | `super`-`return` | lab-sensible-terminal | `alt`-`F4` | close window | `super`-`a` | toggle maximize +| `super`-`d` | toggle show-desktop | `super`-`mouse-left` | move window | `super`-`mouse-right` | resize window | `super`-`arrow` | resize window to fill half the output diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 5691a4e1..6a16b89c 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -863,6 +863,7 @@ overrideInhibition="">* W-Return - lab-sensible-terminal A-F4 - close window W-a - toggle maximize + W-d - toggle show-desktop W- - resize window to fill half or quarter of the output A-Space - show window menu ``` diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 2d787cfa..350d2ea0 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -279,6 +279,9 @@ + + + diff --git a/include/config/default-bindings.h b/include/config/default-bindings.h index 788e8d2e..d1a96562 100644 --- a/include/config/default-bindings.h +++ b/include/config/default-bindings.h @@ -28,6 +28,9 @@ static struct key_combos { }, { .binding = "W-a", .action = "ToggleMaximize", + }, { + .binding = "W-d", + .action = "ToggleShowDesktop", }, { .binding = "W-Left", .action = "SnapToEdge", From 7e74b82874b9052360e7938080ca859b42bde24d Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Fri, 22 May 2026 19:30:06 +0100 Subject: [PATCH 68/71] NEWS.md: move wlroots-0.19 notes ...to the 0.9.x section --- NEWS.md | 68 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9a770ecc..cec808db 100644 --- a/NEWS.md +++ b/NEWS.md @@ -79,40 +79,6 @@ The format is based on [Keep a Changelog] [0.2.0]: NEWS.md#020---2021-04-15 [0.1.0]: NEWS.md#010---2021-03-05 -## Notes on wlroots-0.19 - -There are some regression warnings worth noting for the switch to wlroots 0.19: - -- The DRM backend now destroys/recreates outputs on VT switch and in some cases - on suspend/resume too. The reason for this change was that (i) the KMS state - is undefined when a VT is switched away; and (ii) the previous outputs had - issues with restoration, particularly when the output configuration had - changed whilst switched away. This change causes two issues for users: - - Some layer-shell clients do not re-appear on output re-connection, or may - appear on a different output. Whilst this has always been the case, it will - now also happen in said situations. We recommend layer-shell clients to - handle the new-output and surface-destroy signals to achieve desired - behaviours. - - Some Gtk clients issue critical warnings as they assume that at least one - output is always available. This will be fixed in `Gtk-3.24.50`. It is - believed to be a harmless warning, but it can be avoided by running labwc - with the environment variable `LABWC_FALLBACK_OUTPUT=NOOP-fallback` to - temporarily create a fallback-output when the last physical display - disconnects. [#2914] [#2939] [wlroots-4878] [gtk-8792] -- Menu item can no longer be activated in any Gtk applications with a single - press-drag-release mouse action. For context: This is due to ambiguity in the - specifications and contrary implementations. For example, Gtk applications are - broken under KWin in this regard, while vice versa Qt clients are broken under - other compositors like Weston, Mutter and labwc. It has been decided not to - block the release due to this regression as it is an eco-system wide issue - that has existed for a long time. [#2787] -- It is strongly recommended to use at least wlroots 0.19.1 [#2943] - [wlroots-5098] [#2887] - -[wlroots-4878]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4878 -[wlroots-5098]:https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5098 -[gtk-8792]: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8792 - ## unreleased [unreleased-commits] @@ -186,6 +152,40 @@ Note to maintainers: pactl instead of amixer [#3484] @danielrrrr - Drop cosmic-workspace protocol [#3031] @tokyo4j +## Notes on wlroots-0.19 + +There are some regression warnings worth noting for the switch to wlroots 0.19: + +- The DRM backend now destroys/recreates outputs on VT switch and in some cases + on suspend/resume too. The reason for this change was that (i) the KMS state + is undefined when a VT is switched away; and (ii) the previous outputs had + issues with restoration, particularly when the output configuration had + changed whilst switched away. This change causes two issues for users: + - Some layer-shell clients do not re-appear on output re-connection, or may + appear on a different output. Whilst this has always been the case, it will + now also happen in said situations. We recommend layer-shell clients to + handle the new-output and surface-destroy signals to achieve desired + behaviours. + - Some Gtk clients issue critical warnings as they assume that at least one + output is always available. This will be fixed in `Gtk-3.24.50`. It is + believed to be a harmless warning, but it can be avoided by running labwc + with the environment variable `LABWC_FALLBACK_OUTPUT=NOOP-fallback` to + temporarily create a fallback-output when the last physical display + disconnects. [#2914] [#2939] [wlroots-4878] [gtk-8792] +- Menu item can no longer be activated in any Gtk applications with a single + press-drag-release mouse action. For context: This is due to ambiguity in the + specifications and contrary implementations. For example, Gtk applications are + broken under KWin in this regard, while vice versa Qt clients are broken under + other compositors like Weston, Mutter and labwc. It has been decided not to + block the release due to this regression as it is an eco-system wide issue + that has existed for a long time. [#2787] +- It is strongly recommended to use at least wlroots 0.19.1 [#2943] + [wlroots-5098] [#2887] + +[wlroots-4878]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4878 +[wlroots-5098]:https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5098 +[gtk-8792]: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8792 + ## 0.9.7 - 2026-04-17 [0.9.7-commits] From 52487547caaa9f17ecd719f5dc7c0857c04f820d Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Fri, 22 May 2026 19:31:16 +0100 Subject: [PATCH 69/71] NEWS.md: update notes for 0.20.0 --- NEWS.md | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index cec808db..f5731099 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog] | Date | All Changes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| -| 2026-05-07 | [unreleased] | 0.20.0 | 28236 | +| 2026-05-25 | [0.20.0] | 0.20.1 | 28313 | | 2026-04-17 | [0.9.7] | 0.19.2 | 29277 | | 2026-03-15 | [0.9.6] | 0.19.2 | 29271 | | 2026-03-04 | [0.9.5] | 0.19.2 | 29251 | @@ -45,6 +45,7 @@ The format is based on [Keep a Changelog] | 2021-03-05 | [0.1.0] | 0.12.0 | 4627 | [unreleased]: NEWS.md#unreleased +[0.20.0]: NEWS.md#0200---2026-05-25 [0.9.7]: NEWS.md#097---2026-04-17 [0.9.6]: NEWS.md#096---2026-03-15 [0.9.5]: NEWS.md#095---2026-03-04 @@ -83,16 +84,44 @@ The format is based on [Keep a Changelog] [unreleased-commits] -The codebase has been ported to wlroots 0.20 [#2956] @Consolatis +## 0.20.0 - 2026-05-25 + +[0.20.0-commits] + +This is the first release using wlroots-0.20 and therefore has an increased risk +of teething issues. Many thanks to @Consolatis for leading the effort to port +across [#2956]. + +In terms of new features, the most noteworthy ones include: (i) the frequently +requested show-desktop action; (ii) initial toplevel capture support to +screenshot specific windows; (iii) menu accelerators/shortcuts; and (iv) HDR10 +support when running with the Vulkan renderer option. + +The eagle-eyed amongst you will have noticed the sudden jump from labwc `0.9.7` +to `0.20.0`. The reason for this is to align the minor number to that of the +wlroots version against which the compositor is linked. + +The 0.9.x series has turned into a maintenance branch named v0.9 with bug fixes +only, for anyone preferring to build with wlroots-0.19. Note to maintainers: + - libinput >=1.26 is required in support of tablet tool pressure range configuration. +- labwc >=0.20.1 is required to avoid some bugs that we do not want labwc to + ship with. For details, see: + https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5325 ### Added +- Partially support toplevel-capture. Note that the following are not yet + implemented: (i) XWayland child windows; (ii) XWayland unmanaged windows + (e.g. popups); (iii) xdg child window positioning; (iv) xdg subsurfaces; + and (v) xdg popup positioning. [#2968] @Consolatis +- Add command line option -t|--title to set the labwc window title when running + nested [#3577] @mdsib - Add support for HDR10 output @kode54 @Consolatis [#3424] -- Include wlroots version in --version string @Consolatis [#3567] +- Include wlroots version in --version string @Consolatis [#3567] [#3581] - Implement menu accelerators (one-letter mnemonics to quickly select/exec items from the current menu) @ch3rn1ka [#3505] - Add Next/PreviousWindowImmediate actions @elviosak @johanmalm [#3547] @@ -105,7 +134,8 @@ Note to maintainers: - Add `onbutton` to config option ``. Also add associated option ``. @diniamo [#3540] - Add `overrideInhibition` option to `` [#3507] @drougas -- Add action `ToggleShowDesktop` to hide/unhide windows [#3500] @johanmalm +- Add action `ToggleShowDesktop` to hide/unhide windows, and default keybind + `Super-d` to trigger this action [#3500] [#3595] @johanmalm - Add `` config option so that privileged protocols can be restricted [#3493] @xi - Add action `DebugToggleKeyStateIndicator` to show a key-state on-screen @@ -118,6 +148,7 @@ Note to maintainers: ### Fixed +- Enable labnag long option --exclusive-zone [#3576] @st0rm-shad0w - Position chromium popup correctly when a window is maximized on a multi- output setup @elviosak [#3547] - Run session activation environment update synchronously to avoid a race @@ -2739,7 +2770,8 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 ShowMenu [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ -[unreleased-commits]: https://github.com/labwc/labwc/compare/0.9.5...HEAD +[unreleased-commits]: https://github.com/labwc/labwc/compare/0.20.0...HEAD +[0.20.0-commits]: https://github.com/labwc/labwc/compare/0.9.5..0.20.0 [0.9.7-commits]: https://github.com/labwc/labwc/compare/0.9.6...0.9.7 [0.9.6-commits]: https://github.com/labwc/labwc/compare/0.9.5...0.9.6 [0.9.5-commits]: https://github.com/labwc/labwc/compare/0.9.4...0.9.5 @@ -3311,3 +3343,4 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3543]: https://github.com/labwc/labwc/pull/3543 [#3547]: https://github.com/labwc/labwc/pull/3547 [#3567]: https://github.com/labwc/labwc/pull/3567 +[#3595]: https://github.com/labwc/labwc/pull/3595 From 37434e3ea54ea5e3ee7d9cc121cb792f8044250d Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Mon, 25 May 2026 20:14:27 +0100 Subject: [PATCH 70/71] NEWS.md: minor fix s/labwc/wlroots/ --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index f5731099..ad71a1df 100644 --- a/NEWS.md +++ b/NEWS.md @@ -108,7 +108,7 @@ Note to maintainers: - libinput >=1.26 is required in support of tablet tool pressure range configuration. -- labwc >=0.20.1 is required to avoid some bugs that we do not want labwc to +- wlroots >=0.20.1 is required to avoid some bugs that we do not want labwc to ship with. For details, see: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5325 From d5b5b765c7907a21a61081da6e3e1f38dbe17ff8 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Mon, 25 May 2026 20:20:18 +0100 Subject: [PATCH 71/71] build: bump version to 0.20.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 22dc5bdc..8684732f 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'labwc', 'c', - version: '0.9.5', + version: '0.20.0', license: 'GPL-2.0-only', meson_version: '>=0.59.0', default_options: [