From 998ff9e7b5f0433d140220aee64e9629b1a88d76 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Thu, 2 Oct 2025 04:06:48 +0900 Subject: [PATCH 01/37] osd-thumbnail: update default colors of selected window item Previously, the default values of `osd.window-switcher.style-thumbnail.item.active.{bg,border}.color` were blue. But they caused the selected window title in the window switcher to be unreadable due to duplicated colors of the text and background with Openbox themes like Numix. Instead, this commit updates them to follow other themes configurations. The default border color of the selected window item is now `osd.label.text.color` with 50% opacity and the background is `osd.label.text.color` with 15% opacity. For subpixel antialiasing to work, the background color is calculated by manually blending `osd.label.text.color` and `osd.bg.color`, rather than just updating the alpha with 50% or 15%. --- docs/labwc-theme.5.scd | 6 ++++-- docs/themerc | 4 ++-- src/theme.c | 37 +++++++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index efc3a1a5..fad8a77e 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -358,10 +358,12 @@ all are supported. Border width of selected window switcher items in pixels. Default is 2. *osd.window-switcher.style-thumbnail.item.active.border.color* - Color of border around selected window switcher items. Default is #589bda. + Color of border around selected window switcher items. + Default is *osd.label.text.color* with 50% opacity. *osd.window-switcher.style-thumbnail.item.active.bg.color* - Color of selected window switcher items. Default is #c7e2fc. + Color of selected window switcher items. + Default is *osd.label.text.color* with 15% opacity. *osd.window-switcher.style-thumbnail.item.icon.size* Size of window icons in window switcher items in pixels. Default is 60. diff --git a/docs/themerc b/docs/themerc index 7b251a3c..a52dfacd 100644 --- a/docs/themerc +++ b/docs/themerc @@ -106,8 +106,8 @@ osd.window-switcher.style-thumbnail.item.width: 300 osd.window-switcher.style-thumbnail.item.height: 250 osd.window-switcher.style-thumbnail.item.padding: 10 osd.window-switcher.style-thumbnail.item.active.border.width: 2 -osd.window-switcher.style-thumbnail.item.active.border.color: #589bda -osd.window-switcher.style-thumbnail.item.active.bg.color: #c7e2fc +osd.window-switcher.style-thumbnail.item.active.border.color: #706f6d +osd.window-switcher.style-thumbnail.item.active.bg.color: #bfbcba osd.window-switcher.style-thumbnail.item.icon.size: 60 osd.window-switcher.preview.border.width: 1 diff --git a/src/theme.c b/src/theme.c index d5279367..a3aec34a 100644 --- a/src/theme.c +++ b/src/theme.c @@ -613,8 +613,8 @@ theme_builtin(struct theme *theme, struct server *server) theme->osd_window_switcher_thumbnail.item_height = 250; theme->osd_window_switcher_thumbnail.item_padding = 10; theme->osd_window_switcher_thumbnail.item_active_border_width = 2; - parse_color("#589bda", theme->osd_window_switcher_thumbnail.item_active_border_color); - parse_color("#c7e2fc", theme->osd_window_switcher_thumbnail.item_active_bg_color); + theme->osd_window_switcher_thumbnail.item_active_border_color[0] = FLT_MIN; + theme->osd_window_switcher_thumbnail.item_active_bg_color[0] = FLT_MIN; theme->osd_window_switcher_thumbnail.item_icon_size = 60; /* inherit settings in post_processing() if not set elsewhere */ @@ -1633,6 +1633,31 @@ get_titlebar_height(struct theme *theme) return h; } +/* Blend foreground color (with new alpha) with background color */ +static void +blend_color_with_bg(float *dst, float *fg, float fg_a, float *bg) +{ + /* Guard against zero division */ + if (fg[3] <= 0.0f) { + memset(dst, 0, sizeof(float) * 4); + return; + } + + /* Redo premultiplication to update foreground alpha */ + float new_fg[4] = { + fg[0] / fg[3] * fg_a, + fg[1] / fg[3] * fg_a, + fg[2] / fg[3] * fg_a, + fg_a, + }; + + /* Blend colors */ + dst[0] = new_fg[0] + bg[0] * (1.0f - new_fg[3]); + dst[1] = new_fg[1] + bg[1] * (1.0f - new_fg[3]); + dst[2] = new_fg[2] + bg[2] * (1.0f - new_fg[3]); + dst[3] = new_fg[3] + bg[3] * (1.0f - new_fg[3]); +} + static void post_processing(struct theme *theme) { @@ -1721,6 +1746,14 @@ post_processing(struct theme *theme) memcpy(theme->osd_border_color, theme->osd_label_text_color, sizeof(theme->osd_border_color)); } + if (switcher_thumb_theme->item_active_border_color[0] == FLT_MIN) { + blend_color_with_bg(switcher_thumb_theme->item_active_border_color, + theme->osd_label_text_color, 0.50, theme->osd_bg_color); + } + if (switcher_thumb_theme->item_active_bg_color[0] == FLT_MIN) { + blend_color_with_bg(switcher_thumb_theme->item_active_bg_color, + theme->osd_label_text_color, 0.15, theme->osd_bg_color); + } if (theme->osd_workspace_switcher_boxes_width == 0) { theme->osd_workspace_switcher_boxes_height = 0; } From cb0a4b875efe49bea8613f22601bb056e3a025e2 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 8 Oct 2025 18:50:18 +0200 Subject: [PATCH 02/37] desktop-entry.c: on detecting a broken icon theme, fall back to hicolor Fixes: #3126 Reported-By: Kreevoz --- src/desktop-entry.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/desktop-entry.c b/src/desktop-entry.c index 7aefa77e..4d61a9dc 100644 --- a/src/desktop-entry.c +++ b/src/desktop-entry.c @@ -108,6 +108,18 @@ desktop_entry_init(struct server *server) sfdo->icon_theme = sfdo_icon_theme_load( sfdo->icon_ctx, rc.icon_theme_name, load_options); + if (!sfdo->icon_theme) { + /* + * sfdo_icon_theme_load() falls back to hicolor theme with + * _ALLOW_MISSING flag when the theme is missing, but just + * fails when the theme is invalid. + * So manually call sfdo_icon_theme_load() again here. + */ + wlr_log(WLR_ERROR, "Failed to load icon theme %s, falling back to 'hicolor'", + rc.icon_theme_name); + sfdo->icon_theme = sfdo_icon_theme_load( + sfdo->icon_ctx, "hicolor", load_options); + } if (!sfdo->icon_theme) { goto err_icon_theme; } From 814af0ae4db881bba934e0076144570057242770 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:47:57 +0200 Subject: [PATCH 03/37] desktop-entry.c: don't demote error messages with LABWC_DEBUG_LIBSFDO Also add additional logging to tell users how to get more information about failures to load the icon theme. --- src/desktop-entry.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/desktop-entry.c b/src/desktop-entry.c index 4d61a9dc..79b015f2 100644 --- a/src/desktop-entry.c +++ b/src/desktop-entry.c @@ -37,9 +37,10 @@ log_handler(enum sfdo_log_level level, const char *fmt, va_list args, void *tag) /* * To avoid logging issues with .desktop files as errors, all libsfdo - * error-logging is demoted to info level. + * error-logging is demoted to info level unless running with + * LABWC_DEBUG_LIBSFDO. */ - if (level == SFDO_LOG_LEVEL_ERROR) { + if (!debug_libsfdo && level == SFDO_LOG_LEVEL_ERROR) { level = SFDO_LOG_LEVEL_INFO; } @@ -117,6 +118,12 @@ desktop_entry_init(struct server *server) */ wlr_log(WLR_ERROR, "Failed to load icon theme %s, falling back to 'hicolor'", rc.icon_theme_name); + + if (!debug_libsfdo) { + wlr_log(WLR_ERROR, "Further information is available by setting " + "the LABWC_DEBUG_LIBSFDO=1 env var before starting labwc"); + } + sfdo->icon_theme = sfdo_icon_theme_load( sfdo->icon_ctx, "hicolor", load_options); } @@ -141,6 +148,10 @@ err_desktop_ctx: err_basedir_ctx: free(sfdo); wlr_log(WLR_ERROR, "Failed to initialize icon loader"); + if (!debug_libsfdo) { + wlr_log(WLR_ERROR, "Further information is available by setting " + "the LABWC_DEBUG_LIBSFDO=1 env var before starting labwc"); + } } void From 017152da52d500cec0f60f51870c29422844e8ca Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Wed, 8 Oct 2025 20:57:45 +0100 Subject: [PATCH 04/37] build: use spaces instead of tab --- clients/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/meson.build b/clients/meson.build index c131915d..54b92db8 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -51,7 +51,7 @@ executable( epoll_dep, ], include_directories: [labwc_inc], - install: true + install: true, ) clients = files('lab-sensible-terminal') From 7166efe7bf3fe239b73b394759f59835653d6d32 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 8 Oct 2025 22:10:24 +0200 Subject: [PATCH 05/37] CI: also run on clients/ changes --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e2876ac9..a9ccbb06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,7 @@ on: - 'src/**' - 'include/**' - 'protocols/**' + - 'clients/**' - 'scripts/**' - '.github/workflows/**' From c27d4955a42184c2bd593cd4c2e1725a2d7f3cf6 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 8 Oct 2025 18:32:30 +0900 Subject: [PATCH 06/37] desktop-entry: fix wrong description of sfdo-icon flags --- src/desktop-entry.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/desktop-entry.c b/src/desktop-entry.c index 79b015f2..7617fa26 100644 --- a/src/desktop-entry.c +++ b/src/desktop-entry.c @@ -96,15 +96,15 @@ desktop_entry_init(struct server *server) * We set some relaxed load options to accommodate delinquent themes in * the wild, namely: * - * - SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING to "impose less - * restrictions on the format of icon theme files" + * - SFDO_ICON_THEME_LOAD_OPTION_RELAXED to "impose less restrictions + * on the format of icon theme files" * - * - SFDO_ICON_THEME_LOAD_OPTION_RELAXED to "continue loading even if it - * fails to find a theme or one of its dependencies." + * - SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING to "continue loading + * even if it fails to find a theme or one of its dependencies." */ int load_options = SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT - | SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING - | SFDO_ICON_THEME_LOAD_OPTION_RELAXED; + | SFDO_ICON_THEME_LOAD_OPTION_RELAXED + | SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING; sfdo->icon_theme = sfdo_icon_theme_load( sfdo->icon_ctx, From 474c513ed6565df300b91e44d3ec77f9da5105c5 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Thu, 9 Oct 2025 09:39:59 +0200 Subject: [PATCH 07/37] fix double free for libxml2 < 2.13 xmlAddChild() only unlinks the second argument since libxml2 2.13. regression from 503af105 --- src/common/xml.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/xml.c b/src/common/xml.c index 7e089267..57c415ec 100644 --- a/src/common/xml.c +++ b/src/common/xml.c @@ -79,7 +79,8 @@ merge_two_trees(xmlNode *dst, xmlNode *src) && !strcasecmp((char *)dst->name, (char *)src->name)) { xmlNode *next_dst = dst->last; xmlNode *next_src = src->children; - xmlAddChild(dst, src->children); + xmlUnlinkNode(next_src); + xmlAddChild(dst, next_src); xmlUnlinkNode(src); xmlFreeNode(src); src = next_src; From e1820adcd3367cc5e5459c6f3fcecb8c62f4f747 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Thu, 9 Oct 2025 09:45:36 +0200 Subject: [PATCH 08/37] fix typo in comment --- src/common/xml.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/xml.c b/src/common/xml.c index 57c415ec..0185d9a3 100644 --- a/src/common/xml.c +++ b/src/common/xml.c @@ -48,7 +48,7 @@ create_attribute_tree(const xmlAttr *attr) } /* - * Consider . + * Consider . * These three attributes are represented by following trees. * action(dst)---name * action(src)---position---x From 6cdfe32af0a2e0fac2b2dcc46498d780f143c80f Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Thu, 9 Oct 2025 21:24:44 +0900 Subject: [PATCH 09/37] rcxml: move from to --- docs/labwc-config.5.scd | 11 +++++------ docs/rc.xml.all | 2 +- src/config/rcxml.c | 12 ++++++------ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index d389c4cf..21ac2629 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -171,7 +171,6 @@ this is for compatibility with Openbox. ``` server - titlebar 0 no no @@ -188,11 +187,6 @@ this is for compatibility with Openbox. that it is not always possible to turn off client side decorations. Default is server. -** [titlebar|none] - Specify how server side decorations are shown for maximized windows. - *titlebar* shows titlebar above a maximized window. *none* shows no server - side decorations around a maximized window. Default is titlebar. - ** The distance in pixels between windows and output edges when using movement actions, for example MoveToEdge. Default is 0. @@ -603,6 +597,11 @@ extending outward from the snapped edge. Even when disabling server side decorations via ToggleDecorations, keep a small border (and resize area) around the window. Default is yes. +** [titlebar|none] + Specify how server side decorations are shown for maximized windows. + *titlebar* shows titlebar above a maximized window. *none* shows no server + side decorations around a maximized window. Default is titlebar. + ** [yes|no] Should drop-shadows be rendered behind windows. Default is no. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index ed80fdf0..44ed984a 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -11,7 +11,6 @@ server - titlebar 0 no no @@ -45,6 +44,7 @@ 8 yes + titlebar no no diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 1353b3c2..4ed6bd6d 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1083,12 +1083,6 @@ entry(xmlNode *node, char *nodename, char *content) } else { rc.xdg_shell_server_side_deco = true; } - } else if (!strcasecmp(nodename, "maximizedDecoration.core")) { - if (!strcasecmp(content, "titlebar")) { - rc.hide_maximized_window_titlebar = false; - } else if (!strcasecmp(content, "none")) { - rc.hide_maximized_window_titlebar = true; - } } else if (!strcmp(nodename, "gap.core")) { rc.gap = atoi(content); } else if (!strcasecmp(nodename, "adaptiveSync.core")) { @@ -1130,6 +1124,12 @@ entry(xmlNode *node, char *nodename, char *content) rc.corner_radius = atoi(content); } else if (!strcasecmp(nodename, "keepBorder.theme")) { set_bool(content, &rc.ssd_keep_border); + } else if (!strcasecmp(nodename, "maximizedDecoration.theme")) { + if (!strcasecmp(content, "titlebar")) { + rc.hide_maximized_window_titlebar = false; + } else if (!strcasecmp(content, "none")) { + rc.hide_maximized_window_titlebar = true; + } } else if (!strcasecmp(nodename, "dropShadows.theme")) { set_bool(content, &rc.shadows_enabled); } else if (!strcasecmp(nodename, "dropShadowsOnTiled.theme")) { From e44a48953067fdf1cc95eb79750f0f4353ea203f Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Fri, 19 Sep 2025 20:47:56 +0100 Subject: [PATCH 10/37] NEWS.md: update notes for 0.9.2 --- NEWS.md | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4216dad6..1ae97c51 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 | |------------|---------------|-----------------|---------------| -| 2025-09-15 | [unreleased] | 0.19.0 | 28686 | +| 2025-10-10 | [0.9.2] | 0.19.1 | 28818 | | 2025-08-02 | [0.9.1] | 0.19.0 | 28605 | | 2025-07-11 | [0.9.0] | 0.19.0 | 28586 | | 2025-05-02 | [0.8.4] | 0.18.2 | 27679 | @@ -39,6 +39,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.2]: NEWS.md#092---2025-10-10 [0.9.1]: NEWS.md#091---2025-08-02 [0.9.0]: NEWS.md#090---2025-07-11 [0.8.4]: NEWS.md#084---2025-05-02 @@ -100,23 +101,35 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: around a bug on the wlroots side which is expected to be fixed in wlroots `0.19.1` [#2887] +With wlroots compiled with libwayland (>= 1.24.0), there is an invisible margin +preventing pointer focus on some layer-shell surfaces including those created by +Gtk. In simple words, this is because libwayland now rounds floats a bit +differently [#3099]. There is a pending fix [wlroots-5159]. + [wlroots-4878]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4878 [wlroots-5098]:https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5098 +[wlroots-5159]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5159 [gtk-8792]: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8792 ## unreleased [unreleased-commits] +## 0.9.2 - 2025-10-10 + +[0.9.2-commits] + ### Added +- Allow `SnapToEdge` and `ToggleSnapToEdge` to combine two cardinal directions + with the config option `combine="yes|no"`. [#3081] @tokyo4j - Support `Border` context for mousebinds as an alias for `Top`...`BRCorner` to make configuration easier. @tokyo4j [#3047] - Add window-switcher mode with thumbnails. This can be enabled with: ``. @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] @@ -126,6 +139,7 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: [#2994] - Add `labnag` (a dialog client with message and buttons) and associated `` option in 'If' actions. @johanmalm @Consolatis @tokyo4j [#2699] +- Support config option `` @johanmalm [#3097] - Allow snapping to corner edges during interactive move with associated config options ``. @tokyo4j [#2885] - Support new values "up-left", "up-right", "down-left" and "down-right" with @@ -147,6 +161,7 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: ### Fixed +- On detecting broken icon theme, fall back on 'hicolor' @Consolatis [#3126] - Restore initially-maximized window position after unplug/plug @tokyo4j [#3042] - Fix large client-side icon not being loaded when the rendered icon size is larger than icon sizes from the client. @tokyo4j [#3033] @@ -162,6 +177,27 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: ### Changed +- 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 + `combine="no"` as shown below. [#3081] @tokyo4j + +``` + + + + + + + + + + + + +``` + - `Focus` and `Raise` on window border press because it is probably what most people expect and it makes the behavior consistent with that of Openbox. @johanmalm [#3039] [#3049] @@ -2337,7 +2373,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.0...HEAD +[unreleased-commits]: https://github.com/labwc/labwc/compare/0.9.2...HEAD +[0.9.2-commits]: https://github.com/labwc/labwc/compare/0.9.1...0.9.2 [0.9.1-commits]: https://github.com/labwc/labwc/compare/0.9.0...0.9.1 [0.9.0-commits]: https://github.com/labwc/labwc/compare/0.8.4...0.9.0 [0.8.4-commits]: https://github.com/labwc/labwc/compare/0.8.3...0.8.4 @@ -2800,3 +2837,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3043]: https://github.com/labwc/labwc/pull/3043 [#3047]: https://github.com/labwc/labwc/pull/3047 [#3049]: https://github.com/labwc/labwc/pull/3049 +[#3081]: https://github.com/labwc/labwc/pull/3081 +[#3097]: https://github.com/labwc/labwc/pull/3097 +[#3099]: https://github.com/labwc/labwc/pull/3099 +[#3126]: https://github.com/labwc/labwc/pull/3126 From d94e5da815564516d40c1f40ad651553547f071e Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 10 Oct 2025 17:30:55 +0900 Subject: [PATCH 11/37] view: fix unexpected view->tiled with SnapToEdge against centered view In 2ac4811, I was missing that windows can be tiled to "center". As a result, after executing `` against a center-tiled window, `view->tiled` is set to `CENTER|LEFT`. --- src/view.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/view.c b/src/view.c index 792401cf..ddfe6b1e 100644 --- a/src/view.c +++ b/src/view.c @@ -2161,7 +2161,8 @@ view_snap_to_edge(struct view *view, enum lab_edge edge, view_set_shade(view, false); - if (lab_edge_is_cardinal(edge) && view->maximized == VIEW_AXIS_NONE) { + if (lab_edge_is_cardinal(edge) && view->maximized == VIEW_AXIS_NONE + && view->tiled != LAB_EDGE_CENTER) { enum lab_edge invert_edge = lab_edge_invert(edge); /* Represents axis of snapping direction */ enum lab_edge parallel_mask = edge | invert_edge; From 70e5beb5ec4de9d4f01b1528c0016ccc9355730c Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Fri, 10 Oct 2025 19:50:59 +0100 Subject: [PATCH 12/37] build: bump version to 0.9.2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 0ac5cea1..d3ba14da 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'labwc', 'c', - version: '0.9.0', + version: '0.9.2', license: 'GPL-2.0-only', meson_version: '>=0.59.0', default_options: [ From 2f96664670c7196f4526006102a1cb80b95e441e Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 1 Oct 2025 21:09:17 +0900 Subject: [PATCH 13/37] build: fix build with libinput as a subproject Unfortunately, has_header_symbol() doesn't work with internal dependencies. Ref: https://github.com/mesonbuild/meson/issues/13553 --- meson.build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index d3ba14da..335bf629 100644 --- a/meson.build +++ b/meson.build @@ -127,7 +127,9 @@ conf_data.set10('HAVE_RSVG', have_rsvg) conf_data.set10('HAVE_LIBSFDO', have_libsfdo) foreach sym : ['LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY', 'LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG'] - conf_data.set10('HAVE_' + sym, cc.has_header_symbol('libinput.h', sym, dependencies: input)) + has_sym = input.type_name() != 'internal' \ + and cc.has_header_symbol('libinput.h', sym, dependencies: input) + conf_data.set10('HAVE_' + sym, has_sym) endforeach if get_option('static_analyzer').enabled() From 5f981226c27d85a408341fb4edca6b5b9790ecf4 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 1 Oct 2025 11:54:26 +0900 Subject: [PATCH 14/37] action: simplify action_prompt_command() --- include/action-prompt-command.h | 12 ---- src/action-prompt-command.c | 108 -------------------------------- src/action.c | 48 +++++++++++++- src/meson.build | 1 - 4 files changed, 46 insertions(+), 123 deletions(-) delete mode 100644 include/action-prompt-command.h delete mode 100644 src/action-prompt-command.c diff --git a/include/action-prompt-command.h b/include/action-prompt-command.h deleted file mode 100644 index 219896b9..00000000 --- a/include/action-prompt-command.h +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -#ifndef LABWC_ACTION_PROMPT_COMMAND_H -#define LABWC_ACTION_PROMPT_COMMAND_H - -struct buf; -struct action; -struct theme; - -void action_prompt_command(struct buf *buf, const char *format, - struct action *action, struct theme *theme); - -#endif /* LABWC_ACTION_PROMPT_COMMAND_H */ diff --git a/src/action-prompt-command.c b/src/action-prompt-command.c deleted file mode 100644 index faad41c9..00000000 --- a/src/action-prompt-command.c +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -#define _POSIX_C_SOURCE 200809L -#include "action-prompt-command.h" -#include -#include -#include "action.h" -#include "common/buf.h" -#include "theme.h" -#include "translate.h" - -enum { - LAB_PROMPT_NONE = 0, - LAB_PROMPT_MESSAGE, - LAB_PROMPT_NO, - LAB_PROMPT_YES, - LAB_PROMPT_BG_COL, - LAB_PROMPT_TEXT_COL, - - LAB_PROMPT_COUNT -}; - -typedef void field_conversion_type(struct buf *buf, struct action *action, - struct theme *theme); - -struct field_converter { - const char fmt_char; - field_conversion_type *fn; -}; - -/* %m */ -static void -set_message(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add(buf, action_get_str(action, "message.prompt", "Choose wisely")); -} - -/* %n */ -static void -set_no(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add(buf, _("No")); -} - -/* %y */ -static void -set_yes(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add(buf, _("Yes")); -} - -/* %b */ -static void -set_bg_col(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add_hex_color(buf, theme->osd_bg_color); -} - -/* %t */ -static void -set_text_col(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add_hex_color(buf, theme->osd_label_text_color); -} - -static const struct field_converter field_converter[LAB_PROMPT_COUNT] = { - [LAB_PROMPT_MESSAGE] = { 'm', set_message }, - [LAB_PROMPT_NO] = { 'n', set_no }, - [LAB_PROMPT_YES] = { 'y', set_yes }, - [LAB_PROMPT_BG_COL] = { 'b', set_bg_col }, - [LAB_PROMPT_TEXT_COL] = { 't', set_text_col }, -}; - -void -action_prompt_command(struct buf *buf, const char *format, - struct action *action, struct theme *theme) -{ - if (!format) { - wlr_log(WLR_ERROR, "missing format"); - return; - } - - for (const char *p = format; *p; p++) { - /* - * If we're not on a conversion specifier (like %m) then just - * keep adding it to the buffer - */ - if (*p != '%') { - buf_add_char(buf, *p); - continue; - } - - /* Process the %* conversion specifier */ - ++p; - - bool found = false; - for (unsigned char i = 0; i < LAB_PROMPT_COUNT; i++) { - if (*p == field_converter[i].fmt_char) { - field_converter[i].fn(buf, action, theme); - found = true; - break; - } - } - if (!found) { - wlr_log(WLR_ERROR, - "invalid prompt command conversion specifier '%c'", *p); - } - } -} diff --git a/src/action.c b/src/action.c index 9a5623ff..a78b01e8 100644 --- a/src/action.c +++ b/src/action.c @@ -10,7 +10,6 @@ #include #include #include "action-prompt-codes.h" -#include "action-prompt-command.h" #include "common/buf.h" #include "common/macros.h" #include "common/list.h" @@ -30,6 +29,7 @@ #include "regions.h" #include "ssd.h" #include "theme.h" +#include "translate.h" #include "view.h" #include "workspaces.h" @@ -831,11 +831,55 @@ handle_view_destroy(struct wl_listener *listener, void *data) prompt->view = NULL; } +static void +print_prompt_command(struct buf *buf, const char *format, + struct action *action, struct theme *theme) +{ + assert(format); + + for (const char *p = format; *p; p++) { + /* + * If we're not on a conversion specifier (like %m) then just + * keep adding it to the buffer + */ + if (*p != '%') { + buf_add_char(buf, *p); + continue; + } + + /* Process the %* conversion specifier */ + ++p; + + switch (*p) { + case 'm': + buf_add(buf, action_get_str(action, + "message.prompt", "Choose wisely")); + break; + case 'n': + buf_add(buf, _("No")); + break; + case 'y': + buf_add(buf, _("Yes")); + break; + case 'b': + buf_add_hex_color(buf, theme->osd_bg_color); + break; + case 't': + buf_add_hex_color(buf, theme->osd_label_text_color); + break; + default: + wlr_log(WLR_ERROR, + "invalid prompt command conversion specifier '%c'", *p); + break; + } + } +} + static void action_prompt_create(struct view *view, struct server *server, struct action *action) { struct buf command = BUF_INIT; - action_prompt_command(&command, rc.prompt_command, action, rc.theme); + print_prompt_command(&command, rc.prompt_command, action, rc.theme); wlr_log(WLR_INFO, "prompt command: '%s'", command.data); diff --git a/src/meson.build b/src/meson.build index dc760f0c..330b5daf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,5 @@ labwc_sources = files( 'action.c', - 'action-prompt-command.c', 'buffer.c', 'debug.c', 'desktop.c', From 5e8df27f84f59b775fbf981f734427061e194e98 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 1 Oct 2025 14:25:43 +0900 Subject: [PATCH 15/37] osd-classic: add theme options for selected window item This commit adds new theme options: - osd.window-switcher.style-classic.item.active.border.color - osd.window-switcher.style-classic.item.active.bg.color These theme options configures the border/background of selected window item in the `classic` style window switcher. Their default values are identical to `thumbnail` style window switcher, which means the default border color is now `osd.label.text.color` with 50% opacity and the default background color is now `osd.label.text.color` with 15% opacity. --- docs/labwc-theme.5.scd | 8 +++ docs/themerc | 2 + include/theme.h | 2 + src/osd/osd-classic.c | 114 ++++++++++++++++++++++++----------------- src/theme.c | 16 ++++++ 5 files changed, 96 insertions(+), 46 deletions(-) diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index fad8a77e..8097615b 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -327,6 +327,14 @@ all are supported. Border width of the selection box in the window switcher in pixels. Default is 2. +*osd.window-switcher.style-classic.item.active.border.color* + Border color around the selected window switcher item. + Default is *osd.label.text.color* with 50% opacity. + +*osd.window-switcher.style-classic.item.active.bg.color* + Background color of the selected window switcher item. + Default is *osd.label.text.color* with 15% opacity. + *osd.window-switcher.style-classic.item.icon.size* Size of the icon in window switcher, in pixels. If not set, the font size derived from diff --git a/docs/themerc b/docs/themerc index a52dfacd..9139170c 100644 --- a/docs/themerc +++ b/docs/themerc @@ -97,6 +97,8 @@ osd.window-switcher.style-classic.padding: 4 osd.window-switcher.style-classic.item.padding.x: 10 osd.window-switcher.style-classic.item.padding.y: 1 osd.window-switcher.style-classic.item.active.border.width: 2 +osd.window-switcher.style-classic.item.active.border.color: #706f6d +osd.window-switcher.style-classic.item.active.bg.color: #bfbcba # The icon size the same as the font size by default # osd.window-switcher.style-classic.item.icon.size: 50 diff --git a/include/theme.h b/include/theme.h index 797b5072..e7e13d66 100644 --- a/include/theme.h +++ b/include/theme.h @@ -170,6 +170,8 @@ struct theme { int item_padding_x; int item_padding_y; int item_active_border_width; + float item_active_border_color[4]; + float item_active_bg_color[4]; int item_icon_size; bool width_is_percent; diff --git a/src/osd/osd-classic.c b/src/osd/osd-classic.c index be740f2f..4cb92867 100644 --- a/src/osd/osd-classic.c +++ b/src/osd/osd-classic.c @@ -19,9 +19,59 @@ struct osd_classic_scene_item { struct view *view; - struct wlr_scene_node *highlight_outline; + struct wlr_scene_tree *normal_tree, *active_tree; }; +static void +create_fields_scene(struct server *server, struct view *view, + struct wlr_scene_tree *parent, const float *text_color, + const float *bg_color, int field_widths_sum, int x, int y) +{ + struct theme *theme = server->theme; + struct window_switcher_classic_theme *switcher_theme = + &theme->osd_window_switcher_classic; + + struct window_switcher_field *field; + wl_list_for_each(field, &rc.window_switcher.fields, link) { + int field_width = field_widths_sum * field->width / 100.0; + struct wlr_scene_node *node = NULL; + int height = -1; + + if (field->content == LAB_FIELD_ICON) { + int icon_size = MIN(field_width, + switcher_theme->item_icon_size); + struct scaled_icon_buffer *icon_buffer = + scaled_icon_buffer_create(parent, + server, icon_size, icon_size); + scaled_icon_buffer_set_view(icon_buffer, view); + node = &icon_buffer->scene_buffer->node; + height = icon_size; + } else { + struct buf buf = BUF_INIT; + osd_field_get_content(field, &buf, view); + + if (!string_null_or_empty(buf.data)) { + struct scaled_font_buffer *font_buffer = + scaled_font_buffer_create(parent); + scaled_font_buffer_update(font_buffer, + buf.data, field_width, + &rc.font_osd, text_color, bg_color); + node = &font_buffer->scene_buffer->node; + height = font_height(&rc.font_osd); + } + + buf_reset(&buf); + } + + if (node) { + int item_height = switcher_theme->item_height; + wlr_scene_node_set_position(node, + x, y + (item_height - height) / 2); + } + x += field_width + switcher_theme->item_padding_x; + } +} + static void osd_classic_create(struct output *output, struct wl_array *views) { @@ -126,62 +176,33 @@ osd_classic_create(struct output *output, struct wl_array *views) + switcher_theme->item_padding_x; struct wlr_scene_tree *item_root = wlr_scene_tree_create(output->osd_scene.tree); + item->normal_tree = wlr_scene_tree_create(item_root); + item->active_tree = wlr_scene_tree_create(item_root); + wlr_scene_node_set_enabled(&item->active_tree->node, false); - struct window_switcher_field *field; - wl_list_for_each(field, &rc.window_switcher.fields, link) { - int field_width = field_widths_sum * field->width / 100.0; - struct wlr_scene_node *node = NULL; - int height = -1; - - if (field->content == LAB_FIELD_ICON) { - int icon_size = MIN(field_width, - switcher_theme->item_icon_size); - struct scaled_icon_buffer *icon_buffer = - scaled_icon_buffer_create(item_root, - server, icon_size, icon_size); - scaled_icon_buffer_set_view(icon_buffer, *view); - node = &icon_buffer->scene_buffer->node; - height = icon_size; - } else { - buf_clear(&buf); - osd_field_get_content(field, &buf, *view); - - if (!string_null_or_empty(buf.data)) { - struct scaled_font_buffer *font_buffer = - scaled_font_buffer_create(item_root); - scaled_font_buffer_update(font_buffer, - buf.data, field_width, - &rc.font_osd, text_color, bg_color); - node = &font_buffer->scene_buffer->node; - height = font_height(&rc.font_osd); - } - } - - if (node) { - int item_height = switcher_theme->item_height; - wlr_scene_node_set_position(node, - x, y + (item_height - height) / 2); - } - x += field_width + switcher_theme->item_padding_x; - } + float *active_bg_color = switcher_theme->item_active_bg_color; + float *active_border_color = switcher_theme->item_active_border_color; /* Highlight around selected window's item */ int highlight_x = theme->osd_border_width + switcher_theme->padding; struct lab_scene_rect_options highlight_opts = { - .border_colors = (float *[1]) {text_color}, + .border_colors = (float *[1]) {active_border_color}, + .bg_color = active_bg_color, .nr_borders = 1, .border_width = switcher_theme->item_active_border_width, .width = w - 2 * theme->osd_border_width - 2 * switcher_theme->padding, .height = switcher_theme->item_height, }; - struct lab_scene_rect *highlight_rect = lab_scene_rect_create( - output->osd_scene.tree, &highlight_opts); - item->highlight_outline = &highlight_rect->tree->node; - wlr_scene_node_set_position(item->highlight_outline, highlight_x, y); - wlr_scene_node_set_enabled(item->highlight_outline, false); + item->active_tree, &highlight_opts); + wlr_scene_node_set_position(&highlight_rect->tree->node, highlight_x, y); + + create_fields_scene(server, *view, item->normal_tree, + text_color, bg_color, field_widths_sum, x, y); + create_fields_scene(server, *view, item->active_tree, + text_color, active_bg_color, field_widths_sum, x, y); y += switcher_theme->item_height; } @@ -200,8 +221,9 @@ osd_classic_update(struct output *output) { struct osd_classic_scene_item *item; wl_array_for_each(item, &output->osd_scene.items) { - wlr_scene_node_set_enabled(item->highlight_outline, - item->view == output->server->osd_state.cycle_view); + bool active = item->view == output->server->osd_state.cycle_view; + wlr_scene_node_set_enabled(&item->normal_tree->node, !active); + wlr_scene_node_set_enabled(&item->active_tree->node, active); } } diff --git a/src/theme.c b/src/theme.c index a3aec34a..acdbc16b 100644 --- a/src/theme.c +++ b/src/theme.c @@ -604,6 +604,8 @@ theme_builtin(struct theme *theme, struct server *server) theme->osd_window_switcher_classic.item_padding_x = 10; theme->osd_window_switcher_classic.item_padding_y = 1; theme->osd_window_switcher_classic.item_active_border_width = 2; + theme->osd_window_switcher_classic.item_active_border_color[0] = FLT_MIN; + theme->osd_window_switcher_classic.item_active_bg_color[0] = FLT_MIN; theme->osd_window_switcher_classic.item_icon_size = -1; theme->osd_window_switcher_thumbnail.max_width = 80; @@ -989,6 +991,12 @@ entry(struct theme *theme, const char *key, const char *value) get_int_if_positive(value, "osd.window-switcher.style-classic.item.active.border.width"); } + if (match_glob(key, "osd.window-switcher.style-classic.item.active.border.color")) { + parse_color(value, switcher_classic_theme->item_active_border_color); + } + if (match_glob(key, "osd.window-switcher.style-classic.item.active.bg.color")) { + parse_color(value, switcher_classic_theme->item_active_bg_color); + } if (match_glob(key, "osd.window-switcher.style-classic.item.icon.size") || match_glob(key, "osd.window-switcher.item.icon.size")) { switcher_classic_theme->item_icon_size = @@ -1746,6 +1754,14 @@ post_processing(struct theme *theme) memcpy(theme->osd_border_color, theme->osd_label_text_color, sizeof(theme->osd_border_color)); } + if (switcher_classic_theme->item_active_border_color[0] == FLT_MIN) { + blend_color_with_bg(switcher_classic_theme->item_active_border_color, + theme->osd_label_text_color, 0.50, theme->osd_bg_color); + } + if (switcher_classic_theme->item_active_bg_color[0] == FLT_MIN) { + blend_color_with_bg(switcher_classic_theme->item_active_bg_color, + theme->osd_label_text_color, 0.15, theme->osd_bg_color); + } if (switcher_thumb_theme->item_active_border_color[0] == FLT_MIN) { blend_color_with_bg(switcher_thumb_theme->item_active_border_color, theme->osd_label_text_color, 0.50, theme->osd_bg_color); From e6f54a0fc823107d171a26216f43ec7bea0bf953 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Mon, 29 Sep 2025 14:51:17 -0400 Subject: [PATCH 16/37] menu: use xmlFree() for return value of xmlGetProp() --- src/menu/menu.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/menu/menu.c b/src/menu/menu.c index 0e23e850..faf0c8da 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -487,8 +487,8 @@ fill_item(struct menu *menu, xmlNode *node) append_parsed_actions(node, &item->actions); out: - free(label); - free(icon_name); + xmlFree(label); + xmlFree(icon_name); } static void @@ -619,10 +619,10 @@ fill_menu(struct server *server, struct menu *parent, xmlNode *n) item->submenu = menu; } error: - free(label); - free(icon_name); - free(execute); - free(id); + xmlFree(label); + xmlFree(icon_name); + xmlFree(execute); + xmlFree(id); } /* This can be one of and */ @@ -631,7 +631,7 @@ fill_separator(struct menu *menu, xmlNode *n) { char *label = (char *)xmlGetProp(n, (const xmlChar *)"label"); separator_create(menu, label); - free(label); + xmlFree(label); } /* parent==NULL when processing toplevel menus in menu.xml */ From 40eed3915a2c50d80578a72e92883c6b4726d2c7 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Wed, 1 Oct 2025 12:41:00 -0400 Subject: [PATCH 17/37] osd,ssd: don't cast away const --- src/osd/osd-field.c | 2 +- src/ssd/ssd-titlebar.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/osd/osd-field.c b/src/osd/osd-field.c index c8f796d1..5b275bbb 100644 --- a/src/osd/osd-field.c +++ b/src/osd/osd-field.c @@ -36,7 +36,7 @@ get_app_id_or_class(struct view *view, bool trim) /* remove the first two nodes of 'org.' strings */ if (trim && !strncmp(identifier, "org.", 4)) { - char *p = (char *)identifier + 4; + const char *p = identifier + 4; p = strchr(p, '.'); if (p) { return ++p; diff --git a/src/ssd/ssd-titlebar.c b/src/ssd/ssd-titlebar.c index 13f5e7df..152b9b61 100644 --- a/src/ssd/ssd-titlebar.c +++ b/src/ssd/ssd-titlebar.c @@ -440,7 +440,7 @@ ssd_update_title(struct ssd *ssd) } struct view *view = ssd->view; - char *title = (char *)view_get_string_prop(view, "title"); + const char *title = view_get_string_prop(view, "title"); if (string_null_or_empty(title)) { return; } From da96513e70d6f3b9377cf78be5a5dab2cf82fcd1 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Sat, 11 Oct 2025 21:47:40 -0400 Subject: [PATCH 18/37] menu: remove redundant cast --- src/menu/menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/menu.c b/src/menu/menu.c index faf0c8da..89f061c1 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -482,7 +482,7 @@ fill_item(struct menu *menu, xmlNode *node) goto out; } - struct menuitem *item = item_create(menu, (char *)label, icon_name, false); + struct menuitem *item = item_create(menu, label, icon_name, false); lab_xml_expand_dotted_attributes(node); append_parsed_actions(node, &item->actions); From 27cc7389856d34c3d289b01f5b4c677690cd7aeb Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 12 Oct 2025 23:59:04 +0900 Subject: [PATCH 19/37] osd-thumbnail: make sure item->{normal,active}_title are non-null The if-statement doesn't make sense, because `view_get_string_prop()` never returns NULL. And if it did, it would cause segfault in `osd_thumbnail_update()`. --- src/osd/osd-thumbnail.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/osd/osd-thumbnail.c b/src/osd/osd-thumbnail.c index 24505a31..d17f6fc3 100644 --- a/src/osd/osd-thumbnail.c +++ b/src/osd/osd-thumbnail.c @@ -156,14 +156,12 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, /* title */ const char *title = view_get_string_prop(view, "title"); - if (title) { - item->normal_title = create_title(item->tree, switcher_theme, - title, theme->osd_label_text_color, - theme->osd_bg_color, title_y); - item->active_title = create_title(item->tree, switcher_theme, - title, theme->osd_label_text_color, - switcher_theme->item_active_bg_color, title_y); - } + item->normal_title = create_title(item->tree, switcher_theme, + title, theme->osd_label_text_color, + theme->osd_bg_color, title_y); + item->active_title = create_title(item->tree, switcher_theme, + title, theme->osd_label_text_color, + switcher_theme->item_active_bg_color, title_y); /* icon */ int icon_size = switcher_theme->item_icon_size; From babd7af8f83431f161096cf35df1b31ae305a433 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 13 Aug 2025 17:40:25 +0900 Subject: [PATCH 20/37] view: store title/app_id in view This simplifies our codes and eliminates duplicated `view.events.new_{title,app_id}` events. This should not change any behaviors. --- include/view.h | 10 +++--- src/debug.c | 5 ++- src/foreign-toplevel/ext-foreign.c | 14 ++++---- src/foreign-toplevel/wlr-foreign.c | 20 +++--------- src/menu/menu.c | 8 ++--- src/osd/osd-field.c | 34 ++++++------------- src/osd/osd-thumbnail.c | 5 ++- src/scaled-buffer/scaled-icon-buffer.c | 2 +- src/ssd/ssd-titlebar.c | 12 +++---- src/view-impl-common.c | 5 +-- src/view.c | 45 ++++++++++++++++---------- src/xdg.c | 39 +++++----------------- src/xwayland.c | 43 ++++++------------------ 13 files changed, 87 insertions(+), 155 deletions(-) diff --git a/include/view.h b/include/view.h index ba662550..a8456de2 100644 --- a/include/view.h +++ b/include/view.h @@ -106,7 +106,6 @@ struct view_size_hints { struct view_impl { void (*configure)(struct view *view, struct wlr_box geo); void (*close)(struct view *view); - const char *(*get_string_prop)(struct view *view, const char *prop); void (*map)(struct view *view); void (*set_activated)(struct view *view, bool activated); void (*set_fullscreen)(struct view *view, bool fullscreen); @@ -173,6 +172,10 @@ struct view { struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *content_tree; + /* These are never NULL and an empty string is set instead. */ + char *title; + char *app_id; /* WM_CLASS for xwayland windows */ + bool mapped; bool been_mapped; enum lab_ssd_mode ssd_mode; @@ -571,9 +574,8 @@ bool view_on_output(struct view *view, struct output *output); */ bool view_has_strut_partial(struct view *view); -const char *view_get_string_prop(struct view *view, const char *prop); -void view_update_title(struct view *view); -void view_update_app_id(struct view *view); +void view_set_title(struct view *view, const char *title); +void view_set_app_id(struct view *view, const char *app_id); void view_reload_ssd(struct view *view); void view_set_shade(struct view *view, bool shaded); diff --git a/src/debug.c b/src/debug.c index 97c011f2..84b844d8 100644 --- a/src/debug.c +++ b/src/debug.c @@ -71,11 +71,10 @@ get_view_part(struct view *view, struct wlr_scene_node *node) return NULL; } if (node == &view->scene_tree->node) { - const char *app_id = view_get_string_prop(view, "app_id"); - if (string_null_or_empty(app_id)) { + if (string_null_or_empty(view->app_id)) { return "view"; } - snprintf(view_name, sizeof(view_name), "view (%s)", app_id); + snprintf(view_name, sizeof(view_name), "view (%s)", view->app_id); return view_name; } if (node == &view->content_tree->node) { diff --git a/src/foreign-toplevel/ext-foreign.c b/src/foreign-toplevel/ext-foreign.c index 587193a6..748050fa 100644 --- a/src/foreign-toplevel/ext-foreign.c +++ b/src/foreign-toplevel/ext-foreign.c @@ -32,8 +32,8 @@ handle_new_app_id(struct wl_listener *listener, void *data) assert(ext_toplevel->handle); struct wlr_ext_foreign_toplevel_handle_v1_state state = { - .title = view_get_string_prop(ext_toplevel->view, "title"), - .app_id = view_get_string_prop(ext_toplevel->view, "app_id") + .title = ext_toplevel->view->title, + .app_id = ext_toplevel->view->app_id, }; wlr_ext_foreign_toplevel_handle_v1_update_state(ext_toplevel->handle, &state); @@ -47,8 +47,8 @@ handle_new_title(struct wl_listener *listener, void *data) assert(ext_toplevel->handle); struct wlr_ext_foreign_toplevel_handle_v1_state state = { - .title = view_get_string_prop(ext_toplevel->view, "title"), - .app_id = view_get_string_prop(ext_toplevel->view, "app_id") + .title = ext_toplevel->view->title, + .app_id = ext_toplevel->view->app_id, }; wlr_ext_foreign_toplevel_handle_v1_update_state(ext_toplevel->handle, &state); @@ -63,15 +63,15 @@ ext_foreign_toplevel_init(struct ext_foreign_toplevel *ext_toplevel, ext_toplevel->view = view; struct wlr_ext_foreign_toplevel_handle_v1_state state = { - .title = view_get_string_prop(view, "title"), - .app_id = view_get_string_prop(view, "app_id") + .title = view->title, + .app_id = view->app_id, }; ext_toplevel->handle = wlr_ext_foreign_toplevel_handle_v1_create( view->server->foreign_toplevel_list, &state); if (!ext_toplevel->handle) { wlr_log(WLR_ERROR, "cannot create ext toplevel handle for (%s)", - view_get_string_prop(view, "title")); + view->title); return; } diff --git a/src/foreign-toplevel/wlr-foreign.c b/src/foreign-toplevel/wlr-foreign.c index 9f0ed8e6..a84775f5 100644 --- a/src/foreign-toplevel/wlr-foreign.c +++ b/src/foreign-toplevel/wlr-foreign.c @@ -94,13 +94,8 @@ handle_new_app_id(struct wl_listener *listener, void *data) wl_container_of(listener, wlr_toplevel, on_view.new_app_id); assert(wlr_toplevel->handle); - const char *app_id = view_get_string_prop(wlr_toplevel->view, "app_id"); - const char *wlr_app_id = wlr_toplevel->handle->app_id; - if (app_id && wlr_app_id && !strcmp(app_id, wlr_app_id)) { - /* Don't send app_id if they are the same */ - return; - } - wlr_foreign_toplevel_handle_v1_set_app_id(wlr_toplevel->handle, app_id); + wlr_foreign_toplevel_handle_v1_set_app_id(wlr_toplevel->handle, + wlr_toplevel->view->app_id); } static void @@ -110,13 +105,8 @@ handle_new_title(struct wl_listener *listener, void *data) wl_container_of(listener, wlr_toplevel, on_view.new_title); assert(wlr_toplevel->handle); - const char *title = view_get_string_prop(wlr_toplevel->view, "title"); - const char *wlr_title = wlr_toplevel->handle->title; - if (title && wlr_title && !strcmp(title, wlr_title)) { - /* Don't send title if they are the same */ - return; - } - wlr_foreign_toplevel_handle_v1_set_title(wlr_toplevel->handle, title); + wlr_foreign_toplevel_handle_v1_set_title(wlr_toplevel->handle, + wlr_toplevel->view->title); } static void @@ -202,7 +192,7 @@ wlr_foreign_toplevel_init(struct wlr_foreign_toplevel *wlr_toplevel, view->server->foreign_toplevel_manager); if (!wlr_toplevel->handle) { wlr_log(WLR_ERROR, "cannot create wlr foreign toplevel handle for (%s)", - view_get_string_prop(view, "title")); + view->title); return; } diff --git a/src/menu/menu.c b/src/menu/menu.c index 89f061c1..3770e7f6 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -900,8 +900,8 @@ update_client_list_combined_menu(struct server *server) wl_list_for_each(view, &server->views, link) { if (view->workspace == workspace) { - const char *title = view_get_string_prop(view, "title"); - if (!view->foreign_toplevel || string_null_or_empty(title)) { + if (!view->foreign_toplevel + || string_null_or_empty(view->title)) { continue; } @@ -909,9 +909,9 @@ update_client_list_combined_menu(struct server *server) buf_add(&buffer, "*"); } if (view->minimized) { - buf_add_fmt(&buffer, "(%s)", title); + buf_add_fmt(&buffer, "(%s)", view->title); } else { - buf_add(&buffer, title); + buf_add(&buffer, view->title); } item = item_create(menu, buffer.data, NULL, /*show arrow*/ false); diff --git a/src/osd/osd-field.c b/src/osd/osd-field.c index 5b275bbb..9852b11f 100644 --- a/src/osd/osd-field.c +++ b/src/osd/osd-field.c @@ -26,13 +26,9 @@ struct field_converter { /* Internal helpers */ static const char * -get_app_id_or_class(struct view *view, bool trim) +get_identifier(struct view *view, bool trim) { - /* - * XWayland clients return WM_CLASS for 'app_id' so we don't need a - * special case for that here. - */ - const char *identifier = view_get_string_prop(view, "app_id"); + const char *identifier = view->app_id; /* remove the first two nodes of 'org.' strings */ if (trim && !strncmp(identifier, "org.", 4)) { @@ -49,14 +45,13 @@ static const char * get_desktop_name(struct view *view) { #if HAVE_LIBSFDO - const char *app_id = view_get_string_prop(view, "app_id"); - const char *name = desktop_entry_name_lookup(view->server, app_id); + const char *name = desktop_entry_name_lookup(view->server, view->app_id); if (name) { return name; } #endif - return get_app_id_or_class(view, /* trim */ true); + return get_identifier(view, /* trim */ true); } static const char * @@ -73,21 +68,12 @@ get_type(struct view *view, bool short_form) return "???"; } -static const char * -get_title(struct view *view) -{ - return view_get_string_prop(view, "title"); -} - static const char * get_title_if_different(struct view *view) { - const char *identifier = get_app_id_or_class(view, /*trim*/ false); - const char *title = get_title(view); - if (!identifier) { - return title; - } - return (!title || !strcmp(identifier, title)) ? NULL : title; + const char *identifier = get_identifier(view, /*trim*/ false); + const char *title = view->title; + return !strcmp(identifier, title) ? NULL : title; } /* Field handlers */ @@ -169,14 +155,14 @@ static void field_set_identifier(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: I */ - buf_add(buf, get_app_id_or_class(view, /*trim*/ false)); + buf_add(buf, get_identifier(view, /*trim*/ false)); } static void field_set_identifier_trimmed(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: i */ - buf_add(buf, get_app_id_or_class(view, /*trim*/ true)); + buf_add(buf, get_identifier(view, /*trim*/ true)); } static void @@ -190,7 +176,7 @@ static void field_set_title(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: T */ - buf_add(buf, get_title(view)); + buf_add(buf, view->title); } static void diff --git a/src/osd/osd-thumbnail.c b/src/osd/osd-thumbnail.c index d17f6fc3..c9e6b283 100644 --- a/src/osd/osd-thumbnail.c +++ b/src/osd/osd-thumbnail.c @@ -155,12 +155,11 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, } /* title */ - const char *title = view_get_string_prop(view, "title"); item->normal_title = create_title(item->tree, switcher_theme, - title, theme->osd_label_text_color, + view->title, theme->osd_label_text_color, theme->osd_bg_color, title_y); item->active_title = create_title(item->tree, switcher_theme, - title, theme->osd_label_text_color, + view->title, theme->osd_label_text_color, switcher_theme->item_active_bg_color, title_y); /* icon */ diff --git a/src/scaled-buffer/scaled-icon-buffer.c b/src/scaled-buffer/scaled-icon-buffer.c index 0cd09b22..ceab0509 100644 --- a/src/scaled-buffer/scaled-icon-buffer.c +++ b/src/scaled-buffer/scaled-icon-buffer.c @@ -281,7 +281,7 @@ handle_view_new_app_id(struct wl_listener *listener, void *data) struct scaled_icon_buffer *self = wl_container_of(listener, self, on_view.new_app_id); - const char *app_id = view_get_string_prop(self->view, "app_id"); + const char *app_id = self->view->app_id; if (str_equal(app_id, self->view_app_id)) { return; } diff --git a/src/ssd/ssd-titlebar.c b/src/ssd/ssd-titlebar.c index 152b9b61..09f5362a 100644 --- a/src/ssd/ssd-titlebar.c +++ b/src/ssd/ssd-titlebar.c @@ -440,14 +440,13 @@ ssd_update_title(struct ssd *ssd) } struct view *view = ssd->view; - const char *title = view_get_string_prop(view, "title"); - if (string_null_or_empty(title)) { + if (string_null_or_empty(view->title)) { return; } struct theme *theme = view->server->theme; struct ssd_state_title *state = &ssd->state.title; - bool title_unchanged = state->text && !strcmp(title, state->text); + bool title_unchanged = state->text && !strcmp(view->title, state->text); int offset_left, offset_right; get_title_offsets(ssd, &offset_left, &offset_right); @@ -473,7 +472,7 @@ ssd_update_title(struct ssd *ssd) } const float bg_color[4] = {0, 0, 0, 0}; /* ignored */ - scaled_font_buffer_update(subtree->title, title, + scaled_font_buffer_update(subtree->title, view->title, title_bg_width, font, text_color, bg_color); @@ -483,10 +482,7 @@ ssd_update_title(struct ssd *ssd) } if (!title_unchanged) { - if (state->text) { - free(state->text); - } - state->text = xstrdup(title); + xstrdup_replace(state->text, view->title); } ssd_update_title_positions(ssd, offset_left, offset_right); } diff --git a/src/view-impl-common.c b/src/view-impl-common.c index d529941a..df72fe62 100644 --- a/src/view-impl-common.c +++ b/src/view-impl-common.c @@ -10,8 +10,6 @@ void view_impl_map(struct view *view) { desktop_focus_view(view, /*raise*/ true); - view_update_title(view); - view_update_app_id(view); if (!view->been_mapped) { window_rules_apply(view, LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP); } @@ -36,8 +34,7 @@ view_impl_map(struct view *view) desktop_update_top_layer_visibility(view->server); wlr_log(WLR_DEBUG, "[map] identifier=%s, title=%s", - view_get_string_prop(view, "app_id"), - view_get_string_prop(view, "title")); + view->app_id, view->title); } void diff --git a/src/view.c b/src/view.c index ddfe6b1e..87ef8566 100644 --- a/src/view.c +++ b/src/view.c @@ -133,11 +133,11 @@ view_contains_window_type(struct view *view, enum lab_window_type window_type) bool view_matches_query(struct view *view, struct view_query *query) { - if (!query_str_match(query->identifier, view_get_string_prop(view, "app_id"))) { + if (!query_str_match(query->identifier, view->app_id)) { return false; } - if (!query_str_match(query->title, view_get_string_prop(view, "title"))) { + if (!query_str_match(query->title, view->title)) { return false; } @@ -2385,31 +2385,36 @@ view_has_strut_partial(struct view *view) view->impl->has_strut_partial(view); } -/* Note: It is safe to assume that this function never returns NULL */ -const char * -view_get_string_prop(struct view *view, const char *prop) -{ - assert(view); - assert(prop); - if (view->impl->get_string_prop) { - const char *ret = view->impl->get_string_prop(view, prop); - return ret ? ret : ""; - } - return ""; -} - void -view_update_title(struct view *view) +view_set_title(struct view *view, const char *title) { assert(view); + if (!title) { + title = ""; + } + + if (!strcmp(view->title, title)) { + return; + } + xstrdup_replace(view->title, title); + ssd_update_title(view->ssd); wl_signal_emit_mutable(&view->events.new_title, NULL); } void -view_update_app_id(struct view *view) +view_set_app_id(struct view *view, const char *app_id) { assert(view); + if (!app_id) { + app_id = ""; + } + + if (!strcmp(view->app_id, app_id)) { + return; + } + xstrdup_replace(view->app_id, app_id); + wl_signal_emit_mutable(&view->events.new_app_id, NULL); } @@ -2562,6 +2567,9 @@ view_init(struct view *view) wl_signal_init(&view->events.activated); wl_signal_init(&view->events.set_icon); wl_signal_init(&view->events.destroy); + + view->title = xstrdup(""); + view->app_id = xstrdup(""); } void @@ -2585,6 +2593,9 @@ view_destroy(struct view *view) wl_list_remove(&view->set_title.link); wl_list_remove(&view->destroy.link); + zfree(view->title); + zfree(view->app_id); + if (view->foreign_toplevel) { foreign_toplevel_destroy(view->foreign_toplevel); view->foreign_toplevel = NULL; diff --git a/src/xdg.c b/src/xdg.c index 1e09dd99..f877e7ba 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -216,7 +216,7 @@ handle_commit(struct wl_listener *listener, void *data) && extent.height == view->pending.height) { wlr_log(WLR_DEBUG, "window geometry for client (%s) " "appears to be incorrect - ignoring", - view_get_string_prop(view, "app_id")); + view->app_id); size = extent; /* Use surface extent instead */ } } @@ -283,9 +283,8 @@ handle_configure_timeout(void *data) assert(view->pending_configure_serial > 0); assert(view->pending_configure_timeout); - const char *app_id = view_get_string_prop(view, "app_id"); wlr_log(WLR_INFO, "client (%s) did not respond to configure request " - "in %d ms", app_id, CONFIGURE_TIMEOUT_MS); + "in %d ms", view->app_id, CONFIGURE_TIMEOUT_MS); wl_event_source_remove(view->pending_configure_timeout); view->pending_configure_serial = 0; @@ -492,7 +491,9 @@ static void handle_set_title(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_title); - view_update_title(view); + struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view); + + view_set_title(view, toplevel->title); } static void @@ -501,7 +502,9 @@ handle_set_app_id(struct wl_listener *listener, void *data) struct xdg_toplevel_view *xdg_toplevel_view = wl_container_of(listener, xdg_toplevel_view, set_app_id); struct view *view = &xdg_toplevel_view->base; - view_update_app_id(view); + struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view); + + view_set_app_id(view, toplevel->app_id); } static void @@ -740,31 +743,6 @@ set_initial_position(struct view *view) view_place_by_policy(view, /* allow_cursor */ true, rc.placement_policy); } -static const char * -xdg_toplevel_view_get_string_prop(struct view *view, const char *prop) -{ - struct xdg_toplevel_view *xdg_view = xdg_toplevel_view_from_view(view); - struct wlr_xdg_toplevel *xdg_toplevel = xdg_view->xdg_surface - ? xdg_view->xdg_surface->toplevel - : NULL; - if (!xdg_toplevel) { - /* - * This may happen due to a matchOnce rule when - * a view is destroyed while A-Tab is open. See - * https://github.com/labwc/labwc/issues/1082#issuecomment-1716137180 - */ - return ""; - } - - if (!strcmp(prop, "title")) { - return xdg_toplevel->title ? xdg_toplevel->title : ""; - } - if (!strcmp(prop, "app_id")) { - return xdg_toplevel->app_id ? xdg_toplevel->app_id : ""; - } - return ""; -} - static void init_foreign_toplevel(struct view *view) { @@ -884,7 +862,6 @@ xdg_view_get_pid(struct view *view) static const struct view_impl xdg_toplevel_view_impl = { .configure = xdg_toplevel_view_configure, .close = xdg_toplevel_view_close, - .get_string_prop = xdg_toplevel_view_get_string_prop, .map = xdg_toplevel_view_map, .set_activated = xdg_toplevel_view_set_activated, .set_fullscreen = xdg_toplevel_view_set_fullscreen, diff --git a/src/xwayland.c b/src/xwayland.c index 0ab92f0d..6b408a78 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -498,7 +498,8 @@ static void handle_set_title(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_title); - view_update_title(view); + struct xwayland_view *xwayland_view = xwayland_view_from_view(view); + view_set_title(view, xwayland_view->xwayland_surface->title); } static void @@ -507,35 +508,7 @@ handle_set_class(struct wl_listener *listener, void *data) struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_class); struct view *view = &xwayland_view->base; - view_update_app_id(view); -} -static void -xwayland_view_close(struct view *view) -{ - wlr_xwayland_surface_close(xwayland_surface_from_view(view)); -} - -static const char * -xwayland_view_get_string_prop(struct view *view, const char *prop) -{ - struct xwayland_view *xwayland_view = xwayland_view_from_view(view); - struct wlr_xwayland_surface *xwayland_surface = xwayland_view->xwayland_surface; - if (!xwayland_surface) { - /* - * This may happen due to a matchOnce rule when - * a view is destroyed while A-Tab is open. See - * https://github.com/labwc/labwc/issues/1082#issuecomment-1716137180 - */ - return ""; - } - - if (!strcmp(prop, "title")) { - return xwayland_surface->title ? xwayland_surface->title : ""; - } - if (!strcmp(prop, "class")) { - return xwayland_surface->class ? xwayland_surface->class : ""; - } /* * Use the WM_CLASS 'instance' (1st string) for the app_id. Per * ICCCM, this is usually "the trailing part of the name used to @@ -545,10 +518,13 @@ xwayland_view_get_string_prop(struct view *view, const char *prop) * 'instance' except for being capitalized. We want lowercase * here since we use the app_id for icon lookups. */ - if (!strcmp(prop, "app_id")) { - return xwayland_surface->instance ? xwayland_surface->instance : ""; - } - return ""; + view_set_app_id(view, xwayland_view->xwayland_surface->instance); +} + +static void +xwayland_view_close(struct view *view) +{ + wlr_xwayland_surface_close(xwayland_surface_from_view(view)); } static void @@ -1050,7 +1026,6 @@ xwayland_view_get_pid(struct view *view) static const struct view_impl xwayland_view_impl = { .configure = xwayland_view_configure, .close = xwayland_view_close, - .get_string_prop = xwayland_view_get_string_prop, .map = xwayland_view_map, .set_activated = xwayland_view_set_activated, .set_fullscreen = xwayland_view_set_fullscreen, From 364a1d520705ced7e6056cdda0fcfa3dd4643400 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Fri, 3 Oct 2025 02:10:00 +0200 Subject: [PATCH 21/37] osd: allow window switcher to temporary unshade windows This can be configured with a new unshade="yes|no" argument for windowSwitcher in rc.xml Fixes: #3111 --- docs/labwc-config.5.scd | 5 ++++- docs/rc.xml.all | 3 ++- include/config/rcxml.h | 1 + include/labwc.h | 1 + src/config/rcxml.c | 3 +++ src/input/keyboard.c | 3 +++ src/osd/osd.c | 18 +++++++++++++++--- 7 files changed, 29 insertions(+), 5 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 21ac2629..20149bee 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -346,7 +346,7 @@ this is for compatibility with Openbox. ``` -** +** *show* [yes|no] Draw the OnScreenDisplay when switching between windows. Default is yes. @@ -364,6 +364,9 @@ this is for compatibility with Openbox. they are on. Default no (that is only windows on the current workspace are shown). + *unshade* [yes|no] Temporarily unshade windows when switching between + them and permanently unshade on the final selection. Default is yes. + ** Define window switcher fields when using **. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 44ed984a..1dd08c4d 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -79,7 +79,8 @@ - + diff --git a/include/config/rcxml.h b/include/config/rcxml.h index e4fd184b..50874571 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -179,6 +179,7 @@ struct rcxml { bool show; bool preview; bool outlines; + bool unshade; enum lab_view_criteria criteria; struct wl_list fields; /* struct window_switcher_field.link */ enum window_switcher_style style; diff --git a/include/labwc.h b/include/labwc.h index 5a3bbefa..735f57b8 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -303,6 +303,7 @@ struct server { /* Set when in cycle (alt-tab) mode */ struct osd_state { struct view *cycle_view; + bool preview_was_shaded; bool preview_was_enabled; struct wlr_scene_node *preview_node; struct wlr_scene_tree *preview_parent; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 4ed6bd6d..be49eaeb 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1217,6 +1217,8 @@ entry(xmlNode *node, char *nodename, char *content) rc.window_switcher.criteria &= ~LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; } + } else if (!strcasecmp(nodename, "unshade.windowSwitcher")) { + set_bool(content, &rc.window_switcher.unshade); /* Remove this long term - just a friendly warning for now */ } else if (strstr(nodename, "windowswitcher.core")) { @@ -1429,6 +1431,7 @@ rcxml_init(void) rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; rc.window_switcher.preview = true; rc.window_switcher.outlines = true; + rc.window_switcher.unshade = true; rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE | LAB_VIEW_CRITERIA_ROOT_TOPLEVEL | LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER; diff --git a/src/input/keyboard.c b/src/input/keyboard.c index ae92e73f..8f7a3e81 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -96,6 +96,9 @@ end_cycling(struct server *server) /* FIXME: osd_finish() transiently sets focus to the old surface */ osd_finish(server); /* Note that server->osd_state.cycle_view is cleared at this point */ + if (rc.window_switcher.unshade) { + view_set_shade(cycle_view, false); + } desktop_focus_view(cycle_view, /*raise*/ true); } diff --git a/src/osd/osd.c b/src/osd/osd.c index df1b7ad9..e436c19e 100644 --- a/src/osd/osd.c +++ b/src/osd/osd.c @@ -9,6 +9,7 @@ #include "common/scene-helpers.h" #include "config/rcxml.h" #include "labwc.h" +#include "node.h" #include "output.h" #include "scaled-buffer/scaled-font-buffer.h" #include "scaled-buffer/scaled-icon-buffer.h" @@ -160,9 +161,14 @@ restore_preview_node(struct server *server) if (!osd_state->preview_was_enabled) { wlr_scene_node_set_enabled(osd_state->preview_node, false); } + if (osd_state->preview_was_shaded) { + struct view *view = node_view_from_node(osd_state->preview_node); + view_set_shade(view, true); + } osd_state->preview_node = NULL; osd_state->preview_parent = NULL; osd_state->preview_anchor = NULL; + osd_state->preview_was_shaded = false; } } @@ -203,6 +209,7 @@ osd_finish(struct server *server) server->osd_state.preview_node = NULL; server->osd_state.preview_anchor = NULL; server->osd_state.cycle_view = NULL; + server->osd_state.preview_was_shaded = false; destroy_osd_scenes(server); @@ -244,6 +251,10 @@ preview_cycled_view(struct view *view) if (!osd_state->preview_was_enabled) { wlr_scene_node_set_enabled(osd_state->preview_node, true); } + if (rc.window_switcher.unshade && view->shaded) { + view_set_shade(view, false); + osd_state->preview_was_shaded = true; + } /* * FIXME: This abuses an implementation detail of the always-on-top tree. @@ -294,6 +305,10 @@ update_osd(struct server *server) } } + if (rc.window_switcher.preview) { + preview_cycled_view(server->osd_state.cycle_view); + } + /* Outline current window */ if (rc.window_switcher.outlines) { if (view_is_focusable(server->osd_state.cycle_view)) { @@ -301,9 +316,6 @@ update_osd(struct server *server) } } - if (rc.window_switcher.preview) { - preview_cycled_view(server->osd_state.cycle_view); - } out: wl_array_release(&views); } From 2b3aadb6afb2b7cd1031ce0559746cda4074a89a Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Thu, 2 Oct 2025 14:43:25 +0900 Subject: [PATCH 22/37] labnag: s/LAB_EXIT_TIMEOUT/LAB_EXIT_CANCELLED/ --- clients/labnag.c | 2 +- include/action-prompt-codes.h | 2 +- src/action.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/labnag.c b/clients/labnag.c index 23e0be1f..c9566328 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -1233,7 +1233,7 @@ nag_run(struct nag *nag) wl_display_cancel_read(nag->display); } if (nag->pollfds[FD_TIMER].revents & POLLIN) { - exit_status = LAB_EXIT_TIMEOUT; + exit_status = LAB_EXIT_CANCELLED; break; } if (nag->pollfds[FD_SIGNAL].revents & POLLIN) { diff --git a/include/action-prompt-codes.h b/include/action-prompt-codes.h index d1ca0f05..68d1b066 100644 --- a/include/action-prompt-codes.h +++ b/include/action-prompt-codes.h @@ -3,7 +3,7 @@ #define LABWC_ACTION_PROMPT_CODES_H #define LAB_EXIT_FAILURE 255 -#define LAB_EXIT_TIMEOUT 254 +#define LAB_EXIT_CANCELLED 254 #define LAB_EXIT_SUCCESS 0 #endif /* LABWC_ACTION_PROMPT_CODES_H */ diff --git a/src/action.c b/src/action.c index a78b01e8..a351160d 100644 --- a/src/action.c +++ b/src/action.c @@ -934,7 +934,7 @@ action_check_prompt_result(pid_t pid, int exit_code) if (exit_code == LAB_EXIT_SUCCESS) { wlr_log(WLR_INFO, "Selected the 'then' branch"); actions = action_get_actionlist(prompt->action, "then"); - } else if (exit_code == LAB_EXIT_TIMEOUT) { + } else if (exit_code == LAB_EXIT_CANCELLED) { /* no-op */ } else { wlr_log(WLR_INFO, "Selected the 'else' branch"); From 03c70e8a5e3e685a6d94fbca5d9eda1e56ef49e5 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 3 Oct 2025 01:13:39 +0900 Subject: [PATCH 23/37] labnag: remove redundant lines in conf_init() --- clients/labnag.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/clients/labnag.c b/clients/labnag.c index c9566328..0c279b2a 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -1250,13 +1250,6 @@ conf_init(struct conf *conf) | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; conf->layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; - conf->button_background = 0x333333FF; - conf->details_background = 0x333333FF; - conf->background = 0x323232FF; - conf->text = 0xFFFFFFFF; - conf->button_text = 0xFFFFFFFF; - conf->button_border = 0x222222FF; - conf->border_bottom = 0x444444FF; conf->bar_border_thickness = 2; conf->message_padding = 8; conf->details_border_thickness = 3; From ef73431367c30f9ab2241f577e4816f23e7794b5 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 1 Oct 2025 14:49:23 +0900 Subject: [PATCH 24/37] labnag: add --keyboard-focus option The new `--keyboard-focus [none|on-demand|exclusive]` option (default: `none`) allows to some keyboard controls in labnag: Right-arrow or Tab: move the button selection to the right Left-arrow or Shift-Tab: move the button selection to the left Enter: press the selected button Escape: close labnag The selected button is highlighted with the inner 1px border. Maybe we can instead use different colors for the selected button, but I prefer the inner border for now because it doesn't require us to add new color options or make them inherit labwc's theme. --- clients/labnag.c | 237 +++++++++++++++++++++++++++++++++++++++++++- clients/meson.build | 1 + docs/labnag.1.scd | 3 + 3 files changed, 237 insertions(+), 4 deletions(-) diff --git a/clients/labnag.c b/clients/labnag.c index 0c279b2a..31ac4e9d 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -19,6 +19,7 @@ #ifdef __FreeBSD__ #include /* For signalfd() */ #endif +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include "action-prompt-codes.h" #include "pool-buffer.h" #include "cursor-shape-v1-client-protocol.h" @@ -38,6 +40,7 @@ struct conf { char *output; uint32_t anchors; int32_t layer; /* enum zwlr_layer_shell_v1_layer or -1 if unset */ + enum zwlr_layer_surface_v1_keyboard_interactivity keyboard_focus; /* Colors */ uint32_t button_text; @@ -69,11 +72,18 @@ struct pointer { int y; }; +struct keyboard { + struct wl_keyboard *keyboard; + struct xkb_keymap *keymap; + struct xkb_state *state; +}; + struct seat { struct wl_seat *wl_seat; uint32_t wl_name; struct nag *nag; struct pointer pointer; + struct keyboard keyboard; struct wl_list link; /* nag.seats */ }; @@ -130,6 +140,7 @@ struct nag { struct conf *conf; char *message; struct wl_list buttons; + int selected_button; struct pollfd pollfds[NR_FDS]; struct { @@ -409,7 +420,8 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) } static uint32_t -render_button(cairo_t *cairo, struct nag *nag, struct button *button, int *x) +render_button(cairo_t *cairo, struct nag *nag, struct button *button, + bool selected, int *x) { int text_width, text_height; get_text_size(cairo, nag->conf->font_description, &text_width, @@ -439,6 +451,14 @@ render_button(cairo_t *cairo, struct nag *nag, struct button *button, int *x) button->width, button->height); cairo_fill(cairo); + if (selected) { + cairo_set_source_u32(cairo, nag->conf->button_border); + cairo_set_line_width(cairo, 1); + cairo_rectangle(cairo, button->x + 1.5, button->y + 1.5, + button->width - 3, button->height - 3); + cairo_stroke(cairo); + } + cairo_set_source_u32(cairo, nag->conf->button_text); cairo_move_to(cairo, button->x + padding, button->y + padding); render_text(cairo, nag->conf->font_description, 1, true, @@ -464,11 +484,13 @@ render_to_cairo(cairo_t *cairo, struct nag *nag) int x = nag->width - nag->conf->button_margin_right; x -= nag->conf->button_gap_close; + int idx = 0; struct button *button; wl_list_for_each(button, &nag->buttons, link) { - h = render_button(cairo, nag, button, &x); + h = render_button(cairo, nag, button, idx == nag->selected_button, &x); max_height = h > max_height ? h : max_height; x -= nag->conf->button_gap; + idx++; } if (nag->details.visible) { @@ -555,6 +577,15 @@ seat_destroy(struct seat *seat) if (seat->pointer.pointer) { wl_pointer_destroy(seat->pointer.pointer); } + if (seat->keyboard.keyboard) { + wl_keyboard_destroy(seat->keyboard.keyboard); + } + if (seat->keyboard.keymap) { + xkb_keymap_unref(seat->keyboard.keymap); + } + if (seat->keyboard.state) { + xkb_state_unref(seat->keyboard.state); + } wl_seat_destroy(seat->wl_seat); wl_list_remove(&seat->link); free(seat); @@ -939,12 +970,170 @@ static const struct wl_pointer_listener pointer_listener = { .axis_discrete = wl_pointer_axis_discrete, }; +static void +wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) +{ + struct seat *seat = data; + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + wlr_log(WLR_ERROR, "unreconizable keymap format: %d", format); + close(fd); + return; + } + + char *map_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map_shm == MAP_FAILED) { + wlr_log_errno(WLR_ERROR, "mmap()"); + close(fd); + return; + } + + if (seat->keyboard.keymap) { + xkb_keymap_unref(seat->keyboard.keymap); + seat->keyboard.keymap = NULL; + } + if (seat->keyboard.state) { + xkb_state_unref(seat->keyboard.state); + seat->keyboard.state = NULL; + } + struct xkb_context *xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + seat->keyboard.keymap = xkb_keymap_new_from_string(xkb, map_shm, + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (seat->keyboard.keymap) { + seat->keyboard.state = xkb_state_new(seat->keyboard.keymap); + } else { + wlr_log(WLR_ERROR, "failed to compile keymap"); + } + xkb_context_unref(xkb); + + munmap(map_shm, size); + close(fd); +} + +static void +wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + struct wl_surface *surface, struct wl_array *keys) +{ +} + +static void +wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + struct wl_surface *surface) +{ +} + +static void +wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + uint32_t time, uint32_t key, uint32_t state) +{ + struct seat *seat = data; + struct nag *nag = seat->nag; + + if (!seat->keyboard.keymap || !seat->keyboard.state) { + wlr_log(WLR_ERROR, "keymap/state unavailable"); + return; + } + + if (state != WL_KEYBOARD_KEY_STATE_PRESSED) { + return; + } + + key += 8; + const xkb_keysym_t *syms; + if (!xkb_keymap_key_get_syms_by_level(seat->keyboard.keymap, + key, 0, 0, &syms)) { + wlr_log(WLR_ERROR, "failed to translate key: %d", key); + return; + } + xkb_mod_mask_t mods = xkb_state_serialize_mods(seat->keyboard.state, + XKB_STATE_MODS_EFFECTIVE); + xkb_mod_index_t shift_idx = xkb_keymap_mod_get_index( + seat->keyboard.keymap, XKB_MOD_NAME_SHIFT); + bool shift = shift_idx != XKB_MOD_INVALID && (mods & (1 << shift_idx)); + + int nr_buttons = wl_list_length(&nag->buttons); + + switch (syms[0]) { + case XKB_KEY_Left: + case XKB_KEY_Right: + case XKB_KEY_Tab: { + if (nr_buttons <= 0) { + break; + } + int direction; + if (syms[0] == XKB_KEY_Left || (syms[0] == XKB_KEY_Tab && shift)) { + direction = 1; + } else { + direction = -1; + } + nag->selected_button += nr_buttons + direction; + nag->selected_button %= nr_buttons; + render_frame(nag); + close_pollfd(&nag->pollfds[FD_TIMER]); + break; + } + case XKB_KEY_Escape: + exit_status = LAB_EXIT_CANCELLED; + nag->run_display = false; + break; + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: { + int idx = 0; + struct button *button; + wl_list_for_each(button, &nag->buttons, link) { + if (idx == nag->selected_button) { + button_execute(nag, button); + close_pollfd(&nag->pollfds[FD_TIMER]); + exit_status = idx; + break; + } + idx++; + } + break; + } + } +} + +static void +wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + struct seat *seat = data; + + if (!seat->keyboard.state) { + wlr_log(WLR_ERROR, "xkb state unavailable"); + return; + } + + xkb_state_update_mask(seat->keyboard.state, mods_depressed, + mods_latched, mods_locked, 0, 0, group); +} + +static void +wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) +{ +} + +static const struct wl_keyboard_listener keyboard_listener = { + .keymap = wl_keyboard_keymap, + .enter = wl_keyboard_enter, + .leave = wl_keyboard_leave, + .key = wl_keyboard_key, + .modifiers = wl_keyboard_modifiers, + .repeat_info = wl_keyboard_repeat_info, +}; + static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability caps) { struct seat *seat = data; bool cap_pointer = caps & WL_SEAT_CAPABILITY_POINTER; + bool cap_keyboard = caps & WL_SEAT_CAPABILITY_KEYBOARD; + if (cap_pointer && !seat->pointer.pointer) { seat->pointer.pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(seat->pointer.pointer, @@ -953,6 +1142,15 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, wl_pointer_destroy(seat->pointer.pointer); seat->pointer.pointer = NULL; } + + if (cap_keyboard && !seat->keyboard.keyboard) { + seat->keyboard.keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(seat->keyboard.keyboard, + &keyboard_listener, seat); + } else if (!cap_keyboard && seat->keyboard.keyboard) { + wl_keyboard_destroy(seat->keyboard.keyboard); + seat->keyboard.keyboard = NULL; + } } static void @@ -1075,7 +1273,7 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, } } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { nag->layer_shell = wl_registry_bind( - registry, name, &zwlr_layer_shell_v1_interface, 1); + registry, name, &zwlr_layer_shell_v1_interface, 4); } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { nag->cursor_shape_manager = wl_registry_bind( registry, name, &wp_cursor_shape_manager_v1_interface, 1); @@ -1170,6 +1368,8 @@ nag_setup(struct nag *nag) &layer_surface_listener, nag); zwlr_layer_surface_v1_set_anchor(nag->layer_surface, nag->conf->anchors); + zwlr_layer_surface_v1_set_keyboard_interactivity(nag->layer_surface, + nag->conf->keyboard_focus); wl_registry_destroy(registry); @@ -1250,6 +1450,7 @@ conf_init(struct conf *conf) | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; conf->layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; + conf->keyboard_focus = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; conf->bar_border_thickness = 2; conf->message_padding = 8; conf->details_border_thickness = 3; @@ -1357,6 +1558,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, {"debug", no_argument, NULL, 'd'}, {"edge", required_argument, NULL, 'e'}, {"layer", required_argument, NULL, 'y'}, + {"keyboard-focus", required_argument, NULL, 'k'}, {"font", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"detailed-message", no_argument, NULL, 'l'}, @@ -1395,6 +1597,8 @@ nag_parse_options(int argc, char **argv, struct nag *nag, " -e, --edge top|bottom Set the edge to use.\n" " -y, --layer overlay|top|bottom|background\n" " Set the layer to use.\n" + " -k, --keyboard-focus none|exclusive|on-demand|\n" + " Set the policy for keyboard focus.\n" " -f, --font Set the font to use.\n" " -h, --help Show help message and quit.\n" " -l, --detailed-message Read a detailed message from stdin.\n" @@ -1426,7 +1630,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, optind = 1; while (1) { - int c = getopt_long(argc, argv, "B:Z:c:de:y:f:hlL:m:o:s:t:vx", opts, NULL); + int c = getopt_long(argc, argv, "B:Z:c:de:y:k:f:hlL:m:o:s:t:vx", opts, NULL); if (c == -1) { break; } @@ -1480,6 +1684,23 @@ nag_parse_options(int argc, char **argv, struct nag *nag, return LAB_EXIT_FAILURE; } break; + case 'k': + if (strcmp(optarg, "none") == 0) { + conf->keyboard_focus = + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; + } else if (strcmp(optarg, "exclusive") == 0) { + conf->keyboard_focus = + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE; + } else if (strcmp(optarg, "on-demand") == 0) { + conf->keyboard_focus = + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND; + } else { + fprintf(stderr, "Invalid keyboard focus: %s\n" + "Usage: --keyboard-focus none|exclusive|on-demand\n", + optarg); + return LAB_EXIT_FAILURE; + } + break; case 'f': /* Font */ pango_font_description_free(conf->font_description); conf->font_description = pango_font_description_from_string(optarg); @@ -1622,6 +1843,14 @@ main(int argc, char **argv) wl_list_insert(nag.buttons.prev, &nag.details.button_details->link); } + int nr_buttons = wl_list_length(&nag.buttons); + if (conf.keyboard_focus && nr_buttons > 0) { + /* select the leftmost button */ + nag.selected_button = nr_buttons - 1; + } else { + nag.selected_button = -1; + } + wlr_log(WLR_DEBUG, "Output: %s", nag.conf->output); wlr_log(WLR_DEBUG, "Anchors: %lu", (unsigned long)nag.conf->anchors); wlr_log(WLR_DEBUG, "Message: %s", nag.message); diff --git a/clients/meson.build b/clients/meson.build index 54b92db8..467bc035 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -49,6 +49,7 @@ executable( wlroots, server_protos, epoll_dep, + xkbcommon, ], include_directories: [labwc_inc], install: true, diff --git a/docs/labnag.1.scd b/docs/labnag.1.scd index c8aa4d09..a2a36c10 100644 --- a/docs/labnag.1.scd +++ b/docs/labnag.1.scd @@ -31,6 +31,9 @@ _labnag_ [options...] *-y, --layer* overlay|top|bottom|background Set the layer to use. +*-k, --keyboard-focus none|exclusive|on-demand* + Set the policy for keyboard focus. + *-f, --font* Set the font to use. From 953249249cfb20952af48df2b19aba3cde159a5c Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Mon, 13 Oct 2025 18:58:23 +0900 Subject: [PATCH 25/37] rcxml: call labnag with --keyboard-focus on-demand by default --- docs/labwc-config.5.scd | 1 + src/config/rcxml.c | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 20149bee..995be07c 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -285,6 +285,7 @@ this is for compatibility with Openbox. --button-text-color '%t' \\ --border-bottom-size 1 \\ --button-border-size 3 \\ + --keyboard-focus on-demand \\ --timeout 0 ``` diff --git a/src/config/rcxml.c b/src/config/rcxml.c index be49eaeb..6d0caf80 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1645,6 +1645,7 @@ post_processing(void) "--button-text-color '%t' " "--border-bottom-size 1 " "--button-border-size 3 " + "--keyboard-focus on-demand " "--timeout 0"); } if (!rc.fallback_app_icon_name) { From 77a11568a7b9c8d42e29102416c040d4dfc83c32 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Thu, 9 Oct 2025 08:59:40 +0200 Subject: [PATCH 26/37] implement is_modal_dialog() for xdg shell --- src/server.c | 1 + src/xdg.c | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/server.c b/src/server.c index c39649b8..abaaaf0b 100644 --- a/src/server.c +++ b/src/server.c @@ -276,6 +276,7 @@ allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, "zxdg_importer_v1", "zxdg_importer_v2", "xdg_toplevel_icon_manager_v1", + "xdg_dialog_v1", /* plus */ "wp_alpha_modifier_v1", "wp_linux_drm_syncobj_manager_v1", diff --git a/src/xdg.c b/src/xdg.c index f877e7ba..83ab9bb9 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "buffer.h" @@ -617,6 +618,18 @@ xdg_toplevel_view_append_children(struct view *self, struct wl_array *children) } } +static bool +xdg_toplevel_view_is_modal_dialog(struct view *view) +{ + struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view); + struct wlr_xdg_dialog_v1 *dialog = + wlr_xdg_dialog_v1_try_from_wlr_xdg_toplevel(toplevel); + if (!dialog) { + return false; + } + return dialog->modal; +} + static void xdg_toplevel_view_set_activated(struct view *view, bool activated) { @@ -871,6 +884,7 @@ static const struct view_impl xdg_toplevel_view_impl = { .minimize = xdg_toplevel_view_minimize, .get_root = xdg_toplevel_view_get_root, .append_children = xdg_toplevel_view_append_children, + .is_modal_dialog = xdg_toplevel_view_is_modal_dialog, .get_size_hints = xdg_toplevel_view_get_size_hints, .contains_window_type = xdg_toplevel_view_contains_window_type, .get_pid = xdg_view_get_pid, @@ -1116,6 +1130,8 @@ xdg_shell_init(struct server *server) server->xdg_toplevel_icon_set_icon.notify = handle_xdg_toplevel_icon_set_icon; wl_signal_add(&server->xdg_toplevel_icon_manager->events.set_icon, &server->xdg_toplevel_icon_set_icon); + + wlr_xdg_wm_dialog_v1_create(server->wl_display, 1); } void From c78a0fe1b4e34ed1f0e32de0461330e64d7988d4 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Mon, 7 Jul 2025 00:53:36 +0300 Subject: [PATCH 27/37] windowswitcher: show 's' as "state" for shaded views While at it sorted the code to show 'm' before 's' and 's' before 'M' - from the least visible to the most visible state. --- docs/labwc-config.5.scd | 6 +++--- src/osd/osd-field.c | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 995be07c..ca167b62 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -401,9 +401,9 @@ this is for compatibility with Openbox. fields are: - 'B' - shell type, values [xwayland|xdg-shell] - 'b' - shell type (short form), values [X|W] - - 'S' - state of window, values [M|m|F] (3 spaces allocated) - (maximized, minimized, fullscreen) - - 's' - state of window (short form), values [M|m|F] (1 space) + - 'S' - state of window, values [m|s|M|F] (4 spaces allocated) + (minimized, shaded, maximized, fullscreen) + - 's' - state of window (short form), values [m|s|M|F] (1 space) - 'I' - wm-class/app-id - 'i' - wm-class/app-id trimmed, remove "org." if available - 'n' - desktop entry/file application name, falls back to diff --git a/src/osd/osd-field.c b/src/osd/osd-field.c index 9852b11f..01c4e48b 100644 --- a/src/osd/osd-field.c +++ b/src/osd/osd-field.c @@ -111,10 +111,12 @@ static void field_set_win_state(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: s */ - if (view->maximized) { - buf_add(buf, "M"); - } else if (view->minimized) { + if (view->minimized) { buf_add(buf, "m"); + } else if (view->shaded) { + buf_add(buf, "s"); + } else if (view->maximized) { + buf_add(buf, "M"); } else if (view->fullscreen) { buf_add(buf, "F"); } else { @@ -127,6 +129,7 @@ field_set_win_state_all(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: S */ buf_add(buf, view->minimized ? "m" : " "); + buf_add(buf, view->shaded ? "s" : " "); buf_add(buf, view->maximized ? "M" : " "); buf_add(buf, view->fullscreen ? "F" : " "); /* TODO: add always-on-top and omnipresent ? */ From 89fab2d4499d7e69fcfac554f98c30b31e1e4466 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Tue, 14 Oct 2025 13:44:44 +0900 Subject: [PATCH 28/37] theme: replace THEME_(IN)ACTIVE with SSD_(IN)ACTIVE --- include/theme.h | 6 +- src/ssd/ssd-shadow.c | 4 +- src/ssd/ssd.c | 2 +- src/theme.c | 147 ++++++++++++++++++++++--------------------- 4 files changed, 79 insertions(+), 80 deletions(-) diff --git a/include/theme.h b/include/theme.h index e7e13d66..58ea3e13 100644 --- a/include/theme.h +++ b/include/theme.h @@ -83,7 +83,7 @@ struct theme { /* * Themes/textures for each active/inactive window. Indexed by - * THEME_INACTIVE and THEME_ACTIVE. + * ssd_active_state. */ struct { /* title background pattern (solid or gradient) */ @@ -212,10 +212,6 @@ struct theme { int mag_border_width; }; -/* TODO: replace with enum ssd_active_state */ -#define THEME_INACTIVE 0 -#define THEME_ACTIVE 1 - struct server; /** diff --git a/src/ssd/ssd-shadow.c b/src/ssd/ssd-shadow.c index 53c6631d..2ead17ce 100644 --- a/src/ssd/ssd-shadow.c +++ b/src/ssd/ssd-shadow.c @@ -259,8 +259,8 @@ ssd_shadow_update(struct ssd *ssd) bool maximized = view->maximized == VIEW_AXIS_BOTH; bool tiled_shadows = false; if (rc.shadows_on_tiled) { - if (rc.gap >= theme->window[THEME_ACTIVE].shadow_size - && rc.gap >= theme->window[THEME_INACTIVE].shadow_size) { + if (rc.gap >= theme->window[SSD_ACTIVE].shadow_size + && rc.gap >= theme->window[SSD_INACTIVE].shadow_size) { tiled_shadows = true; } else { wlr_log(WLR_INFO, "gap size < shadow_size, ignore rc.shadows_ontiled"); diff --git a/src/ssd/ssd.c b/src/ssd/ssd.c index 9ec7fa3b..d1381c1a 100644 --- a/src/ssd/ssd.c +++ b/src/ssd/ssd.c @@ -348,7 +348,7 @@ ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable) float *color = enable ? rc.theme->window_toggled_keybinds_color - : rc.theme->window[THEME_ACTIVE].border_color; + : rc.theme->window[SSD_ACTIVE].border_color; wlr_scene_rect_set_color(ssd->border.subtrees[SSD_ACTIVE].top, color); } diff --git a/src/theme.c b/src/theme.c index acdbc16b..f6684ee9 100644 --- a/src/theme.c +++ b/src/theme.c @@ -162,7 +162,7 @@ get_button_filename(char *buf, size_t len, const char *name, const char *postfix } static void -load_button(struct theme *theme, struct button *b, int active) +load_button(struct theme *theme, struct button *b, enum ssd_active_state active) { struct lab_img *(*button_imgs)[LAB_BS_ALL + 1] = theme->window[active].button_imgs; @@ -374,8 +374,8 @@ load_buttons(struct theme *theme) for (size_t i = 0; i < ARRAY_SIZE(buttons); ++i) { struct button *b = &buttons[i]; - load_button(theme, b, THEME_INACTIVE); - load_button(theme, b, THEME_ACTIVE); + load_button(theme, b, SSD_INACTIVE); + load_button(theme, b, SSD_ACTIVE); } } @@ -537,24 +537,24 @@ theme_builtin(struct theme *theme, struct server *server) theme->window_titlebar_padding_height = 0; theme->window_titlebar_padding_width = 0; - parse_hexstr("#aaaaaa", theme->window[THEME_ACTIVE].border_color); - parse_hexstr("#aaaaaa", theme->window[THEME_INACTIVE].border_color); + parse_hexstr("#aaaaaa", theme->window[SSD_ACTIVE].border_color); + parse_hexstr("#aaaaaa", theme->window[SSD_INACTIVE].border_color); parse_hexstr("#ff0000", theme->window_toggled_keybinds_color); - theme->window[THEME_ACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; - theme->window[THEME_INACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; - parse_hexstr("#e1dedb", theme->window[THEME_ACTIVE].title_bg.color); - parse_hexstr("#f6f5f4", theme->window[THEME_INACTIVE].title_bg.color); - theme->window[THEME_ACTIVE].title_bg.color_split_to[0] = FLT_MIN; - theme->window[THEME_INACTIVE].title_bg.color_split_to[0] = FLT_MIN; - theme->window[THEME_ACTIVE].title_bg.color_to[0] = FLT_MIN; - theme->window[THEME_INACTIVE].title_bg.color_to[0] = FLT_MIN; - theme->window[THEME_ACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; - theme->window[THEME_INACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; + theme->window[SSD_ACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; + theme->window[SSD_INACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; + parse_hexstr("#e1dedb", theme->window[SSD_ACTIVE].title_bg.color); + parse_hexstr("#f6f5f4", theme->window[SSD_INACTIVE].title_bg.color); + theme->window[SSD_ACTIVE].title_bg.color_split_to[0] = FLT_MIN; + theme->window[SSD_INACTIVE].title_bg.color_split_to[0] = FLT_MIN; + theme->window[SSD_ACTIVE].title_bg.color_to[0] = FLT_MIN; + theme->window[SSD_INACTIVE].title_bg.color_to[0] = FLT_MIN; + theme->window[SSD_ACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; + theme->window[SSD_INACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; - parse_hexstr("#000000", theme->window[THEME_ACTIVE].label_text_color); - parse_hexstr("#000000", theme->window[THEME_INACTIVE].label_text_color); + parse_hexstr("#000000", theme->window[SSD_ACTIVE].label_text_color); + parse_hexstr("#000000", theme->window[SSD_INACTIVE].label_text_color); theme->window_label_text_justify = parse_justification("Center"); theme->window_button_width = 26; @@ -565,15 +565,15 @@ theme_builtin(struct theme *theme, struct server *server) for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST; type <= LAB_NODE_BUTTON_LAST; type++) { parse_hexstr("#000000", - theme->window[THEME_INACTIVE].button_colors[type]); + theme->window[SSD_INACTIVE].button_colors[type]); parse_hexstr("#000000", - theme->window[THEME_ACTIVE].button_colors[type]); + theme->window[SSD_ACTIVE].button_colors[type]); } - theme->window[THEME_ACTIVE].shadow_size = 60; - theme->window[THEME_INACTIVE].shadow_size = 40; - parse_hexstr("#00000060", theme->window[THEME_ACTIVE].shadow_color); - parse_hexstr("#00000040", theme->window[THEME_INACTIVE].shadow_color); + theme->window[SSD_ACTIVE].shadow_size = 60; + theme->window[SSD_INACTIVE].shadow_size = 40; + parse_hexstr("#00000060", theme->window[SSD_ACTIVE].shadow_color); + parse_hexstr("#00000040", theme->window[SSD_INACTIVE].shadow_color); theme->menu_overlap_x = 0; theme->menu_overlap_y = 0; @@ -711,15 +711,15 @@ entry(struct theme *theme, const char *key, const char *value) } if (match_glob(key, "window.active.border.color")) { - parse_color(value, theme->window[THEME_ACTIVE].border_color); + parse_color(value, theme->window[SSD_ACTIVE].border_color); } if (match_glob(key, "window.inactive.border.color")) { - parse_color(value, theme->window[THEME_INACTIVE].border_color); + parse_color(value, theme->window[SSD_INACTIVE].border_color); } /* border.color is obsolete, but handled for backward compatibility */ if (match_glob(key, "border.color")) { - parse_color(value, theme->window[THEME_ACTIVE].border_color); - parse_color(value, theme->window[THEME_INACTIVE].border_color); + parse_color(value, theme->window[SSD_ACTIVE].border_color); + parse_color(value, theme->window[SSD_INACTIVE].border_color); } if (match_glob(key, "window.active.indicator.toggled-keybind.color")) { @@ -727,41 +727,41 @@ entry(struct theme *theme, const char *key, const char *value) } if (match_glob(key, "window.active.title.bg")) { - theme->window[THEME_ACTIVE].title_bg.gradient = parse_gradient(value); + theme->window[SSD_ACTIVE].title_bg.gradient = parse_gradient(value); } if (match_glob(key, "window.inactive.title.bg")) { - theme->window[THEME_INACTIVE].title_bg.gradient = parse_gradient(value); + theme->window[SSD_INACTIVE].title_bg.gradient = parse_gradient(value); } if (match_glob(key, "window.active.title.bg.color")) { - parse_color(value, theme->window[THEME_ACTIVE].title_bg.color); + parse_color(value, theme->window[SSD_ACTIVE].title_bg.color); } if (match_glob(key, "window.inactive.title.bg.color")) { - parse_color(value, theme->window[THEME_INACTIVE].title_bg.color); + parse_color(value, theme->window[SSD_INACTIVE].title_bg.color); } if (match_glob(key, "window.active.title.bg.color.splitTo")) { - parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_split_to); + parse_color(value, theme->window[SSD_ACTIVE].title_bg.color_split_to); } if (match_glob(key, "window.inactive.title.bg.color.splitTo")) { - parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_split_to); + parse_color(value, theme->window[SSD_INACTIVE].title_bg.color_split_to); } if (match_glob(key, "window.active.title.bg.colorTo")) { - parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_to); + parse_color(value, theme->window[SSD_ACTIVE].title_bg.color_to); } if (match_glob(key, "window.inactive.title.bg.colorTo")) { - parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_to); + parse_color(value, theme->window[SSD_INACTIVE].title_bg.color_to); } if (match_glob(key, "window.active.title.bg.colorTo.splitTo")) { - parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_to_split_to); + parse_color(value, theme->window[SSD_ACTIVE].title_bg.color_to_split_to); } if (match_glob(key, "window.inactive.title.bg.colorTo.splitTo")) { - parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_to_split_to); + parse_color(value, theme->window[SSD_INACTIVE].title_bg.color_to_split_to); } if (match_glob(key, "window.active.label.text.color")) { - parse_color(value, theme->window[THEME_ACTIVE].label_text_color); + parse_color(value, theme->window[SSD_ACTIVE].label_text_color); } if (match_glob(key, "window.inactive.label.text.color")) { - parse_color(value, theme->window[THEME_INACTIVE].label_text_color); + parse_color(value, theme->window[SSD_INACTIVE].label_text_color); } if (match_glob(key, "window.label.text.justify")) { theme->window_label_text_justify = parse_justification(value); @@ -797,85 +797,85 @@ entry(struct theme *theme, const char *key, const char *value) for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST; type <= LAB_NODE_BUTTON_LAST; type++) { parse_color(value, - theme->window[THEME_ACTIVE].button_colors[type]); + theme->window[SSD_ACTIVE].button_colors[type]); } } if (match_glob(key, "window.inactive.button.unpressed.image.color")) { for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST; type <= LAB_NODE_BUTTON_LAST; type++) { parse_color(value, - theme->window[THEME_INACTIVE].button_colors[type]); + theme->window[SSD_INACTIVE].button_colors[type]); } } /* individual buttons */ if (match_glob(key, "window.active.button.menu.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_WINDOW_MENU]); - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_WINDOW_ICON]); } if (match_glob(key, "window.active.button.iconify.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_ICONIFY]); } if (match_glob(key, "window.active.button.max.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_MAXIMIZE]); } if (match_glob(key, "window.active.button.shade.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_SHADE]); } if (match_glob(key, "window.active.button.desk.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_OMNIPRESENT]); } if (match_glob(key, "window.active.button.close.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_CLOSE]); } if (match_glob(key, "window.inactive.button.menu.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_WINDOW_MENU]); - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_WINDOW_ICON]); } if (match_glob(key, "window.inactive.button.iconify.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_ICONIFY]); } if (match_glob(key, "window.inactive.button.max.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_MAXIMIZE]); } if (match_glob(key, "window.inactive.button.shade.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_SHADE]); } if (match_glob(key, "window.inactive.button.desk.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_OMNIPRESENT]); } if (match_glob(key, "window.inactive.button.close.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_CLOSE]); } /* window drop-shadows */ if (match_glob(key, "window.active.shadow.size")) { - theme->window[THEME_ACTIVE].shadow_size = get_int_if_positive( + theme->window[SSD_ACTIVE].shadow_size = get_int_if_positive( value, "window.active.shadow.size"); } if (match_glob(key, "window.inactive.shadow.size")) { - theme->window[THEME_INACTIVE].shadow_size = get_int_if_positive( + theme->window[SSD_INACTIVE].shadow_size = get_int_if_positive( value, "window.inactive.shadow.size"); } if (match_glob(key, "window.active.shadow.color")) { - parse_color(value, theme->window[THEME_ACTIVE].shadow_color); + parse_color(value, theme->window[SSD_ACTIVE].shadow_color); } if (match_glob(key, "window.inactive.shadow.color")) { - parse_color(value, theme->window[THEME_INACTIVE].shadow_color); + parse_color(value, theme->window[SSD_INACTIVE].shadow_color); } if (match_glob(key, "menu.overlap.x")) { @@ -1396,7 +1396,8 @@ create_titlebar_fill(cairo_pattern_t *pattern, int height) static void create_backgrounds(struct theme *theme) { - for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) { + enum ssd_active_state active; + FOR_EACH_ACTIVE_STATE(active) { theme->window[active].titlebar_pattern = create_titlebar_pattern( &theme->window[active].title_bg, theme->titlebar_height); @@ -1418,7 +1419,8 @@ create_corners(struct theme *theme) .height = theme->titlebar_height + theme->border_width, }; - for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) { + enum ssd_active_state active; + FOR_EACH_ACTIVE_STATE(active) { struct rounded_corner_ctx ctx = { .box = &box, .radius = rc.corner_radius, @@ -1548,7 +1550,7 @@ shadow_corner_gradient(struct lab_data_buffer *buffer, int visible_size, } static void -create_shadow(struct theme *theme, int active) +create_shadow(struct theme *theme, enum ssd_active_state active) { /* Size of shadow visible extending beyond the window */ int visible_size = theme->window[active].shadow_size; @@ -1590,8 +1592,8 @@ create_shadow(struct theme *theme, int active) static void create_shadows(struct theme *theme) { - create_shadow(theme, THEME_INACTIVE); - create_shadow(theme, THEME_ACTIVE); + create_shadow(theme, SSD_INACTIVE); + create_shadow(theme, SSD_ACTIVE); } static void @@ -1676,8 +1678,8 @@ post_processing(struct theme *theme) theme->titlebar_height = get_titlebar_height(theme); - fill_background_colors(&theme->window[THEME_INACTIVE].title_bg); - fill_background_colors(&theme->window[THEME_ACTIVE].title_bg); + fill_background_colors(&theme->window[SSD_INACTIVE].title_bg); + fill_background_colors(&theme->window[SSD_ACTIVE].title_bg); theme->menu_item_height = font_height(&rc.font_menuitem) + 2 * theme->menu_items_padding_y; @@ -1722,14 +1724,14 @@ post_processing(struct theme *theme) } if (theme->menu_border_color[0] == FLT_MIN) { memcpy(theme->menu_border_color, - theme->window[THEME_ACTIVE].border_color, + theme->window[SSD_ACTIVE].border_color, sizeof(theme->menu_border_color)); } /* Inherit OSD settings if not set */ if (theme->osd_bg_color[0] == FLT_MIN) { memcpy(theme->osd_bg_color, - theme->window[THEME_ACTIVE].title_bg.color, + theme->window[SSD_ACTIVE].title_bg.color, sizeof(theme->osd_bg_color)); } if (theme->osd_border_width == INT_MIN) { @@ -1737,7 +1739,7 @@ post_processing(struct theme *theme) } if (theme->osd_label_text_color[0] == FLT_MIN) { memcpy(theme->osd_label_text_color, - theme->window[THEME_ACTIVE].label_text_color, + theme->window[SSD_ACTIVE].label_text_color, sizeof(theme->osd_label_text_color)); } if (theme->osd_border_color[0] == FLT_MIN) { @@ -1854,14 +1856,15 @@ theme_finish(struct theme *theme) type <= LAB_NODE_BUTTON_LAST; type++) { for (uint8_t state_set = LAB_BS_DEFAULT; state_set <= LAB_BS_ALL; state_set++) { - destroy_img(&theme->window[THEME_INACTIVE] + destroy_img(&theme->window[SSD_INACTIVE] .button_imgs[type][state_set]); - destroy_img(&theme->window[THEME_ACTIVE] + destroy_img(&theme->window[SSD_ACTIVE] .button_imgs[type][state_set]); } } - for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) { + enum ssd_active_state active; + FOR_EACH_ACTIVE_STATE(active) { zfree_pattern(theme->window[active].titlebar_pattern); zdrop(&theme->window[active].titlebar_fill); zdrop(&theme->window[active].corner_top_left_normal); From eebf5b3e4edfb66e4d1e55e0f4c217887d36b233 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 11 Oct 2025 22:01:24 +0200 Subject: [PATCH 29/37] Translation updates from weblate Co-authored-by: alvaroelpob Co-authored-by: p-bo Translate-URL: https://translate.lxqt-project.org/projects/labwc/labwc/ca/ Translate-URL: https://translate.lxqt-project.org/projects/labwc/labwc/cs/ Translation: Labwc/labwc --- po/ca.po | 9 +++++---- po/cs.po | 23 +++++++++++++---------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/po/ca.po b/po/ca.po index f9c6975e..7bc442bc 100644 --- a/po/ca.po +++ b/po/ca.po @@ -8,9 +8,10 @@ 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-03-29 11:25+0000\n" -"Last-Translator: Davidmp \n" -"Language-Team: Catalan \n" +"PO-Revision-Date: 2025-10-11 20:01+0000\n" +"Last-Translator: alvaroelpob \n" +"Language-Team: Catalan \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -24,7 +25,7 @@ msgstr "Ves-hi..." #: src/menu/menu.c:1034 msgid "Terminal" -msgstr "" +msgstr "Terminal" #: src/menu/menu.c:1040 msgid "Reconfigure" diff --git a/po/cs.po b/po/cs.po index 27fe6a68..11ff25ce 100644 --- a/po/cs.po +++ b/po/cs.po @@ -8,29 +8,32 @@ 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: 2024-03-02 02:00+0100\n" -"Last-Translator: zenobit \n" -"Language-Team: Czech \n" +"PO-Revision-Date: 2025-10-11 20:01+0000\n" +"Last-Translator: p-bo \n" +"Language-Team: Czech \n" "Language: cs\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==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:1016 msgid "Go there..." -msgstr "" +msgstr "Přejít tam..." #: src/menu/menu.c:1034 msgid "Terminal" -msgstr "" +msgstr "Terminál" #: src/menu/menu.c:1040 msgid "Reconfigure" -msgstr "Překonfigurovat" +msgstr "Přenastavit" #: src/menu/menu.c:1042 msgid "Exit" -msgstr "Odejít" +msgstr "Ukončit" #: src/menu/menu.c:1056 msgid "Minimize" @@ -46,7 +49,7 @@ msgstr "Na celou obrazovku" #: src/menu/menu.c:1062 msgid "Roll Up/Down" -msgstr "Rolovat nahoru/dolů" +msgstr "Posouvat nahoru/dolů" #: src/menu/menu.c:1064 msgid "Decorations" @@ -66,11 +69,11 @@ msgstr "Posunout doprava" #: src/menu/menu.c:1083 msgid "Always on Visible Workspace" -msgstr "Vždy na viditelné Pracovní Ploše" +msgstr "Vždy na viditelné Pracovní ploše" #: src/menu/menu.c:1086 msgid "Workspace" -msgstr "Pracovní Plocha" +msgstr "Pracovní plocha" #: src/menu/menu.c:1089 msgid "Close" From 7f67b9c8664b57eefb70644b2c17ceb378acd49e Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 15 Oct 2025 16:36:01 +0900 Subject: [PATCH 30/37] Don't remove newlines when parsing config, menu and XBM Removing newlines in rc.xml and menu.xml caused parser error with following content: ...though it is a valid XML. Let's not do that. I moved `grab_file()` to `buf.c` and renamed it to `buf_from_file()`, because it now directly touches `struct buf` and I don't like having a source file only for one function. --- include/common/buf.h | 7 +++++++ include/common/grab-file.h | 20 -------------------- src/common/buf.c | 35 +++++++++++++++++++++++++++++++++++ src/common/grab-file.c | 36 ------------------------------------ src/common/meson.build | 1 - src/config/rcxml.c | 16 ++-------------- src/img/img-xbm.c | 4 ++-- src/menu/menu.c | 32 ++++---------------------------- 8 files changed, 50 insertions(+), 101 deletions(-) delete mode 100644 include/common/grab-file.h delete mode 100644 src/common/grab-file.c diff --git a/include/common/buf.h b/include/common/buf.h index 24158630..1298cac2 100644 --- a/include/common/buf.h +++ b/include/common/buf.h @@ -109,4 +109,11 @@ void buf_reset(struct buf *s); */ void buf_move(struct buf *dst, struct buf *src); +/** + * buf_from_file - read file into memory buffer + * @filename: file to read + * Free returned buffer with buf_reset(). + */ +struct buf buf_from_file(const char *filename); + #endif /* LABWC_BUF_H */ diff --git a/include/common/grab-file.h b/include/common/grab-file.h deleted file mode 100644 index 6a6de56d..00000000 --- a/include/common/grab-file.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Read file into memory - * - * Copyright Johan Malm 2020 - */ - -#ifndef LABWC_GRAB_FILE_H -#define LABWC_GRAB_FILE_H - -#include "common/buf.h" - -/** - * grab_file - read file into memory buffer - * @filename: file to read - * Free returned buffer with buf_reset(). - */ -struct buf grab_file(const char *filename); - -#endif /* LABWC_GRAB_FILE_H */ diff --git a/src/common/buf.c b/src/common/buf.c index bd8e82d0..c141b62e 100644 --- a/src/common/buf.c +++ b/src/common/buf.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "common/macros.h" #include "common/mem.h" #include "common/string-helpers.h" @@ -203,3 +204,37 @@ buf_move(struct buf *dst, struct buf *src) *dst = *src; *src = BUF_INIT; } + +struct buf +buf_from_file(const char *filename) +{ + struct buf buf = BUF_INIT; + FILE *stream = fopen(filename, "r"); + if (!stream) { + return buf; + } + + if (fseek(stream, 0, SEEK_END) == -1) { + wlr_log_errno(WLR_ERROR, "fseek(%s)", filename); + fclose(stream); + return buf; + } + long size = ftell(stream); + if (size == -1) { + wlr_log_errno(WLR_ERROR, "ftell(%s)", filename); + fclose(stream); + return buf; + } + rewind(stream); + + buf_expand(&buf, size + 1); + if (fread(buf.data, 1, size, stream) == (size_t)size) { + buf.len = size; + buf.data[size] = '\0'; + } else { + wlr_log_errno(WLR_ERROR, "fread(%s)", filename); + buf_reset(&buf); + } + fclose(stream); + return buf; +} diff --git a/src/common/grab-file.c b/src/common/grab-file.c deleted file mode 100644 index 9c90045c..00000000 --- a/src/common/grab-file.c +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Read file into memory - * - * Copyright Johan Malm 2020 - */ - -#define _POSIX_C_SOURCE 200809L -#include "common/grab-file.h" -#include "common/buf.h" - -#include -#include -#include - -struct buf -grab_file(const char *filename) -{ - char *line = NULL; - size_t len = 0; - FILE *stream = fopen(filename, "r"); - if (!stream) { - return BUF_INIT; - } - struct buf buffer = BUF_INIT; - while ((getline(&line, &len, stream) != -1)) { - char *p = strrchr(line, '\n'); - if (p) { - *p = '\0'; - } - buf_add(&buffer, line); - } - free(line); - fclose(stream); - return buffer; -} diff --git a/src/common/meson.build b/src/common/meson.build index 39b4d4b4..4cf52023 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -6,7 +6,6 @@ labwc_sources += files( 'fd-util.c', 'file-helpers.c', 'font.c', - 'grab-file.c', 'graphic-helpers.c', 'lab-scene-rect.c', 'match.c', diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 6d0caf80..a966577c 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1872,25 +1872,13 @@ rcxml_read(const char *filename) */ for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); - FILE *stream = fopen(path->string, "r"); - if (!stream) { + struct buf b = buf_from_file(path->string); + if (!b.len) { continue; } wlr_log(WLR_INFO, "read config file %s", path->string); - struct buf b = BUF_INIT; - char *line = NULL; - size_t len = 0; - while (getline(&line, &len, stream) != -1) { - char *p = strrchr(line, '\n'); - if (p) { - *p = '\0'; - } - buf_add(&b, line); - } - zfree(line); - fclose(stream); rcxml_parse_xml(&b); buf_reset(&b); if (!should_merge_config) { diff --git a/src/img/img-xbm.c b/src/img/img-xbm.c index 6ecbbc9a..91269f64 100644 --- a/src/img/img-xbm.c +++ b/src/img/img-xbm.c @@ -12,7 +12,7 @@ #include #include #include -#include "common/grab-file.h" +#include "common/buf.h" #include "common/mem.h" #include "common/string-helpers.h" #include "buffer.h" @@ -273,7 +273,7 @@ img_xbm_load(const char *filename, float *rgba) uint32_t color = argb32(rgba); /* Read file into memory as it's easier to tokenize that way */ - struct buf token_buf = grab_file(filename); + struct buf token_buf = buf_from_file(filename); if (token_buf.len) { struct token *tokens = tokenize_xbm(token_buf.data); pixmap = parse_xbm_tokens(tokens, color); diff --git a/src/menu/menu.c b/src/menu/menu.c index 3770e7f6..bc0fdcad 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -679,30 +679,6 @@ parse_buf(struct server *server, struct menu *parent, struct buf *buf) return true; } -/* - * @stream can come from either of the following: - * - fopen() in the case of reading a file such as menu.xml - * - popen() when processing pipemenus - */ -static void -parse_stream(struct server *server, FILE *stream) -{ - char *line = NULL; - size_t len = 0; - struct buf b = BUF_INIT; - - while (getline(&line, &len, stream) != -1) { - char *p = strrchr(line, '\n'); - if (p) { - *p = '\0'; - } - buf_add(&b, line); - } - free(line); - parse_buf(server, NULL, &b); - buf_reset(&b); -} - static void parse_xml(const char *filename, struct server *server) { @@ -715,13 +691,13 @@ parse_xml(const char *filename, struct server *server) for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); - FILE *stream = fopen(path->string, "r"); - if (!stream) { + struct buf buf = buf_from_file(path->string); + if (!buf.len) { continue; } wlr_log(WLR_INFO, "read menu file %s", path->string); - parse_stream(server, stream); - fclose(stream); + parse_buf(server, /*parent*/ NULL, &buf); + buf_reset(&buf); if (!should_merge_config) { break; } From 8583637331ee81a8a5bf2b6d29c1b58f6d161db6 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Sat, 5 Jul 2025 18:10:07 +0200 Subject: [PATCH 31/37] [revert later] CI: allow compiling wlroots as subproject --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9ccbb06..82139d61 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -94,6 +94,7 @@ jobs: apt-get install -y git gcc clang gdb xwayland apt-get build-dep -y labwc apt-get build-dep -y libwlroots-0.19-dev + apt-get build-dep -y libxkbcommon-dev - name: Install FreeBSD dependencies if: matrix.name == 'FreeBSD' @@ -120,7 +121,7 @@ jobs: 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 \ - dejavu-fonts-ttf libsfdo-devel foot + dejavu-fonts-ttf libsfdo-devel foot hwids # These builds are executed on all runners - name: Build with gcc From a9f114580279005ce659818e0d7d36fd3c8580c6 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Sat, 5 Jul 2025 16:59:41 +0200 Subject: [PATCH 32/37] chase wlroots: increase wlroots meson dep --- meson.build | 4 ++-- subprojects/wlroots.wrap | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 335bf629..ba3a2148 100644 --- a/meson.build +++ b/meson.build @@ -51,9 +51,9 @@ endif add_project_arguments('-DLABWC_VERSION=@0@'.format(version), language: 'c') wlroots = dependency( - 'wlroots-0.19', + 'wlroots-0.20', default_options: ['default_library=static', 'examples=false'], - version: ['>=0.19.0', '<0.20.0'], + version: ['>=0.20.0', '<0.21.0'], ) wlroots_has_xwayland = wlroots.get_variable('have_xwayland') == 'true' diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap index 25a947ed..c1d52098 100644 --- a/subprojects/wlroots.wrap +++ b/subprojects/wlroots.wrap @@ -1,7 +1,7 @@ [wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git -revision = 0.19 +revision = f04ef79f619983bfb4a7c9908bdae62e0d0d5ba7 [provide] -dependency_names = wlroots-0.19 -wlroots-0.19=wlroots +dependency_names = wlroots-0.20 +wlroots-0.20=wlroots From c9b9c3d2270d86464f5409b41a116faea6735d06 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:03:38 +0200 Subject: [PATCH 33/37] chase wlroots: ime: rename to new_text_input (MR 5032) Ref: 536100488fc4c64528786801860f96cfa1a55765 (text-input-v3: Name new text input event correctly) --- src/input/ime.c | 2 +- subprojects/wlroots.wrap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input/ime.c b/src/input/ime.c index 92d88ffe..ab24f927 100644 --- a/src/input/ime.c +++ b/src/input/ime.c @@ -583,7 +583,7 @@ input_method_relay_create(struct seat *seat) relay->popup_tree = wlr_scene_tree_create(&seat->server->scene->tree); relay->new_text_input.notify = handle_new_text_input; - wl_signal_add(&seat->server->text_input_manager->events.text_input, + wl_signal_add(&seat->server->text_input_manager->events.new_text_input, &relay->new_text_input); relay->new_input_method.notify = handle_new_input_method; diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap index c1d52098..9199c211 100644 --- a/subprojects/wlroots.wrap +++ b/subprojects/wlroots.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git -revision = f04ef79f619983bfb4a7c9908bdae62e0d0d5ba7 +revision = 536100488fc4c64528786801860f96cfa1a55765 [provide] dependency_names = wlroots-0.20 From 92c92ae28d9db21c55a3a98a5de986b9420e3c98 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Wed, 13 Aug 2025 19:57:58 -0700 Subject: [PATCH 34/37] [wip] chase wlroots: Add wl_fixes interface (MR + subproject commit missing) Ref: 812675ba34ce612e9294e8a9814b1baf4b4775d4 (fixes: add implementation) --- src/server.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server.c b/src/server.c index abaaaf0b..bf424f9c 100644 --- a/src/server.c +++ b/src/server.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -255,6 +256,7 @@ allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, "wl_data_device_manager", /* would be great if we could drop this one */ "wl_seat", "xdg_wm_base", + "wl_fixes", /* enhanced */ "wl_output", "wl_drm", @@ -435,6 +437,8 @@ server_init(struct server *server) server->wl_event_loop = wl_display_get_event_loop(server->wl_display); + wlr_fixes_create(server->wl_display, 1); + /* Catch signals */ server->sighup_source = wl_event_loop_add_signal( server->wl_event_loop, SIGHUP, handle_sighup, server); From a71723f1ba73fdeea4c0becc4ea35a446e15cb46 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Tue, 9 Sep 2025 14:58:03 +0200 Subject: [PATCH 35/37] chase wlroots: ime: rename to new_input_method (MR 5107) Ref: 06aacb2a6fd237a5e1062d611909432bbcf5b566 (input-method: rename input_method event to new_input_method) --- src/input/ime.c | 2 +- subprojects/wlroots.wrap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input/ime.c b/src/input/ime.c index ab24f927..bfb8816f 100644 --- a/src/input/ime.c +++ b/src/input/ime.c @@ -587,7 +587,7 @@ input_method_relay_create(struct seat *seat) &relay->new_text_input); relay->new_input_method.notify = handle_new_input_method; - wl_signal_add(&seat->server->input_method_manager->events.input_method, + wl_signal_add(&seat->server->input_method_manager->events.new_input_method, &relay->new_input_method); relay->focused_surface_destroy.notify = handle_focused_surface_destroy; diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap index 9199c211..253b83d7 100644 --- a/subprojects/wlroots.wrap +++ b/subprojects/wlroots.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git -revision = 536100488fc4c64528786801860f96cfa1a55765 +revision = 06aacb2a6fd237a5e1062d611909432bbcf5b566 [provide] dependency_names = wlroots-0.20 From e764fc496411fdddcb736d07ff48c9b01fb94370 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sat, 18 Oct 2025 16:32:20 +0900 Subject: [PATCH 36/37] chase wlroots: ime: don't use `data` in kb grab destroy handler (MR 5170) Ref: 06275103f249cd2954630e59383342e102a6c1a3 (input-method-v2: Destroy keyboard grab before input method) Background: My MR in wlroots (!5107) stopped emitting `wlr_input_method_v2` on its `commit`/`destroy` events, but didn't stop emitting `wlr_input_method_keyboard_grab_v2` on its `destroy` event. That was because `handle_keyboard_grab_destroy()` was called *after* `handle_input_method_destroy()` for some reason, which caused segfault when dereferencing `relay->input_method.keyboard_grab` in `handle_keyboard_grab_destroy()`. MR 5170 reversed this weired order of destroy handler calls, and finally stopped emitting `wlr_input_method_keyboard_grab_v2` on its `destroy` event. --- src/input/ime.c | 3 ++- subprojects/wlroots.wrap | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/input/ime.c b/src/input/ime.c index bfb8816f..efbe54d6 100644 --- a/src/input/ime.c +++ b/src/input/ime.c @@ -309,7 +309,8 @@ handle_keyboard_grab_destroy(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, keyboard_grab_destroy); - struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + relay->input_method->keyboard_grab; assert(keyboard_grab); wl_list_remove(&relay->keyboard_grab_destroy.link); diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap index 253b83d7..18e5ef98 100644 --- a/subprojects/wlroots.wrap +++ b/subprojects/wlroots.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git -revision = 06aacb2a6fd237a5e1062d611909432bbcf5b566 +revision = 06275103f249cd2954630e59383342e102a6c1a3 [provide] dependency_names = wlroots-0.20 From 259803178e2d96910b7eacd27877104b336434d4 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:37:22 +0200 Subject: [PATCH 37/37] [wip] chase wlroots: track master branch --- subprojects/wlroots.wrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap index 18e5ef98..4352289b 100644 --- a/subprojects/wlroots.wrap +++ b/subprojects/wlroots.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git -revision = 06275103f249cd2954630e59383342e102a6c1a3 +revision = master [provide] dependency_names = wlroots-0.20