From 65cc2e40ba3d56de5a79fe463f02874978f2feda Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sat, 29 Nov 2025 02:09:24 +0900 Subject: [PATCH 01/50] Rename osd.{h,c} to cycle.{h,c} --- include/{osd.h => cycle.h} | 6 +++--- src/action.c | 2 +- src/config/rcxml.c | 2 +- src/{osd/osd.c => cycle/cycle.c} | 2 +- src/{osd => cycle}/meson.build | 2 +- src/{osd => cycle}/osd-classic.c | 2 +- src/{osd => cycle}/osd-field.c | 2 +- src/{osd => cycle}/osd-thumbnail.c | 2 +- src/input/cursor.c | 2 +- src/input/keyboard.c | 2 +- src/meson.build | 2 +- src/view.c | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) rename include/{osd.h => cycle.h} (97%) rename src/{osd/osd.c => cycle/cycle.c} (99%) rename src/{osd => cycle}/meson.build (86%) rename src/{osd => cycle}/osd-classic.c (99%) rename src/{osd => cycle}/osd-field.c (99%) rename src/{osd => cycle}/osd-thumbnail.c (99%) diff --git a/include/osd.h b/include/cycle.h similarity index 97% rename from include/osd.h rename to include/cycle.h index 2ecaa202..f27405a5 100644 --- a/include/osd.h +++ b/include/cycle.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -#ifndef LABWC_OSD_H -#define LABWC_OSD_H +#ifndef LABWC_CYCLE_H +#define LABWC_CYCLE_H #include #include @@ -98,4 +98,4 @@ struct osd_impl { extern struct osd_impl osd_classic_impl; extern struct osd_impl osd_thumbnail_impl; -#endif // LABWC_OSD_H +#endif // LABWC_CYCLE_H diff --git a/src/action.c b/src/action.c index a351160d..02c90eca 100644 --- a/src/action.c +++ b/src/action.c @@ -18,12 +18,12 @@ #include "common/spawn.h" #include "common/string-helpers.h" #include "config/rcxml.h" +#include "cycle.h" #include "debug.h" #include "input/keyboard.h" #include "labwc.h" #include "magnifier.h" #include "menu/menu.h" -#include "osd.h" #include "output.h" #include "output-virtual.h" #include "regions.h" diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 264c65a8..0f4b8c47 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -29,8 +29,8 @@ #include "config/tablet.h" #include "config/tablet-tool.h" #include "config/touch.h" +#include "cycle.h" #include "labwc.h" -#include "osd.h" #include "regions.h" #include "ssd.h" #include "translate.h" diff --git a/src/osd/osd.c b/src/cycle/cycle.c similarity index 99% rename from src/osd/osd.c rename to src/cycle/cycle.c index 149f8d42..350b5f3f 100644 --- a/src/osd/osd.c +++ b/src/cycle/cycle.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -#include "osd.h" +#include "cycle.h" #include #include #include diff --git a/src/osd/meson.build b/src/cycle/meson.build similarity index 86% rename from src/osd/meson.build rename to src/cycle/meson.build index 17b4bf51..07e9f7aa 100644 --- a/src/osd/meson.build +++ b/src/cycle/meson.build @@ -1,5 +1,5 @@ labwc_sources += files( - 'osd.c', + 'cycle.c', 'osd-classic.c', 'osd-field.c', 'osd-thumbnail.c', diff --git a/src/osd/osd-classic.c b/src/cycle/osd-classic.c similarity index 99% rename from src/osd/osd-classic.c rename to src/cycle/osd-classic.c index 0c80bd79..e02f8ed1 100644 --- a/src/osd/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -11,9 +11,9 @@ #include "common/list.h" #include "common/string-helpers.h" #include "config/rcxml.h" +#include "cycle.h" #include "labwc.h" #include "node.h" -#include "osd.h" #include "output.h" #include "scaled-buffer/scaled-font-buffer.h" #include "scaled-buffer/scaled-icon-buffer.h" diff --git a/src/osd/osd-field.c b/src/cycle/osd-field.c similarity index 99% rename from src/osd/osd-field.c rename to src/cycle/osd-field.c index c84cce46..4497573e 100644 --- a/src/osd/osd-field.c +++ b/src/cycle/osd-field.c @@ -5,11 +5,11 @@ #include "common/buf.h" #include "common/mem.h" #include "config/rcxml.h" +#include "cycle.h" #include "view.h" #include "workspaces.h" #include "labwc.h" #include "desktop-entry.h" -#include "osd.h" #include "output.h" /* includes '%', terminating 's' and NULL byte, 8 is enough for %-9999s */ diff --git a/src/osd/osd-thumbnail.c b/src/cycle/osd-thumbnail.c similarity index 99% rename from src/osd/osd-thumbnail.c rename to src/cycle/osd-thumbnail.c index 68610896..724fc62a 100644 --- a/src/osd/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -10,9 +10,9 @@ #include "common/buf.h" #include "common/lab-scene-rect.h" #include "common/list.h" +#include "cycle.h" #include "labwc.h" #include "node.h" -#include "osd.h" #include "output.h" #include "scaled-buffer/scaled-font-buffer.h" #include "scaled-buffer/scaled-icon-buffer.h" diff --git a/src/input/cursor.c b/src/input/cursor.c index bf46f175..2660ef6d 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -21,6 +21,7 @@ #include "common/mem.h" #include "config/mousebind.h" #include "config/rcxml.h" +#include "cycle.h" #include "dnd.h" #include "idle.h" #include "input/gestures.h" @@ -30,7 +31,6 @@ #include "labwc.h" #include "layers.h" #include "menu/menu.h" -#include "osd.h" #include "output.h" #include "resistance.h" #include "resize-outlines.h" diff --git a/src/input/keyboard.c b/src/input/keyboard.c index f6b0114c..ab47dd4c 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -11,12 +11,12 @@ #include "common/macros.h" #include "config/keybind.h" #include "config/rcxml.h" +#include "cycle.h" #include "idle.h" #include "input/ime.h" #include "input/key-state.h" #include "labwc.h" #include "menu/menu.h" -#include "osd.h" #include "session-lock.h" #include "view.h" #include "workspaces.h" diff --git a/src/meson.build b/src/meson.build index 330b5daf..a9afdc4f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -49,12 +49,12 @@ endif subdir('common') subdir('config') +subdir('cycle') subdir('decorations') subdir('foreign-toplevel') subdir('img') subdir('input') subdir('menu') -subdir('osd') subdir('protocols') subdir('scaled-buffer') subdir('ssd') diff --git a/src/view.c b/src/view.c index 250ac1ab..acb1ec0b 100644 --- a/src/view.c +++ b/src/view.c @@ -15,11 +15,11 @@ #include "common/match.h" #include "common/mem.h" #include "config/rcxml.h" +#include "cycle.h" #include "foreign-toplevel/foreign.h" #include "input/keyboard.h" #include "labwc.h" #include "menu/menu.h" -#include "osd.h" #include "output.h" #include "placement.h" #include "regions.h" From 4fcb873f6f14c54c2e81451a3d2b90e839dc1967 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sat, 29 Nov 2025 02:41:54 +0900 Subject: [PATCH 02/50] Use "cycle" instead of "osd" across the codebase We were using the word "osd" to describe the window switcher, but it can be used with on-screen display (OSD) disabled by ``. Let's use "cycle" instead to avoid confusion. --- include/common/node-type.h | 2 +- include/config/rcxml.h | 4 +- include/config/types.h | 14 +-- include/cycle.h | 40 ++++---- include/labwc.h | 8 +- include/node.h | 4 +- include/output.h | 8 +- src/action.c | 14 +-- src/config/rcxml.c | 36 +++---- src/cycle/cycle.c | 204 +++++++++++++++++++------------------ src/cycle/osd-classic.c | 42 ++++---- src/cycle/osd-field.c | 12 +-- src/cycle/osd-thumbnail.c | 42 ++++---- src/debug.c | 16 ++- src/desktop.c | 6 +- src/input/cursor.c | 10 +- src/input/keyboard.c | 19 ++-- src/node.c | 8 +- src/output.c | 8 +- src/server.c | 30 +++--- src/view.c | 4 +- 21 files changed, 265 insertions(+), 266 deletions(-) diff --git a/include/common/node-type.h b/include/common/node-type.h index 9987af38..52fff3b0 100644 --- a/include/common/node-type.h +++ b/include/common/node-type.h @@ -47,7 +47,7 @@ enum lab_node_type { LAB_NODE_FRAME, LAB_NODE_ROOT, LAB_NODE_MENUITEM, - LAB_NODE_OSD_ITEM, + LAB_NODE_CYCLE_OSD_ITEM, LAB_NODE_LAYER_SURFACE, LAB_NODE_UNMANAGED, LAB_NODE_ALL, diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 91a73d58..55c0f877 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -182,8 +182,8 @@ struct rcxml { bool unshade; enum lab_view_criteria criteria; struct wl_list fields; /* struct window_switcher_field.link */ - enum window_switcher_style style; - enum osd_output_criteria output_criteria; + enum cycle_osd_style style; + enum cycle_osd_output_criteria output_criteria; char *thumbnail_label_format; } window_switcher; diff --git a/include/config/types.h b/include/config/types.h index 76e699a4..7cb9feab 100644 --- a/include/config/types.h +++ b/include/config/types.h @@ -107,15 +107,15 @@ enum lab_window_type { LAB_WINDOW_TYPE_LEN }; -enum window_switcher_style { - WINDOW_SWITCHER_CLASSIC, - WINDOW_SWITCHER_THUMBNAIL, +enum cycle_osd_style { + CYCLE_OSD_STYLE_CLASSIC, + CYCLE_OSD_STYLE_THUMBNAIL, }; -enum osd_output_criteria { - OSD_OUTPUT_ALL, - OSD_OUTPUT_POINTER, - OSD_OUTPUT_KEYBOARD, +enum cycle_osd_output_criteria { + CYCLE_OSD_OUTPUT_ALL, + CYCLE_OSD_OUTPUT_POINTER, + CYCLE_OSD_OUTPUT_KEYBOARD, }; #endif /* LABWC_CONFIG_TYPES_H */ diff --git a/include/cycle.h b/include/cycle.h index f27405a5..d7d20991 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -14,7 +14,7 @@ enum lab_cycle_dir { }; /* TODO: add field with keyboard layout? */ -enum window_switcher_field_content { +enum cycle_osd_field_content { LAB_FIELD_NONE = 0, LAB_FIELD_TYPE, LAB_FIELD_TYPE_SHORT, @@ -35,8 +35,8 @@ enum window_switcher_field_content { LAB_FIELD_COUNT }; -struct window_switcher_field { - enum window_switcher_field_content content; +struct cycle_osd_field { + enum cycle_osd_field_content content; int width; char *format; struct wl_list link; /* struct rcxml.window_switcher.fields */ @@ -48,54 +48,54 @@ struct server; struct wlr_scene_node; /* Begin window switcher */ -void osd_begin(struct server *server, enum lab_cycle_dir direction); +void cycle_begin(struct server *server, enum lab_cycle_dir direction); /* Cycle the selected view in the window switcher */ -void osd_cycle(struct server *server, enum lab_cycle_dir direction); +void cycle_step(struct server *server, enum lab_cycle_dir direction); /* Closes the OSD */ -void osd_finish(struct server *server, bool switch_focus); +void cycle_finish(struct server *server, bool switch_focus); /* Notify OSD about a destroying view */ -void osd_on_view_destroy(struct view *view); +void cycle_on_view_destroy(struct view *view); /* Focus the clicked window and close OSD */ -void osd_on_cursor_release(struct server *server, struct wlr_scene_node *node); +void cycle_on_cursor_release(struct server *server, struct wlr_scene_node *node); /* Used by osd.c internally to render window switcher fields */ -void osd_field_get_content(struct window_switcher_field *field, +void cycle_osd_field_get_content(struct cycle_osd_field *field, struct buf *buf, struct view *view); /* Sets view info to buf according to format */ -void osd_field_set_custom(struct buf *buf, struct view *view, +void cycle_osd_field_set_custom(struct buf *buf, struct view *view, const char *format); /* Used by rcxml.c when parsing the config */ -void osd_field_arg_from_xml_node(struct window_switcher_field *field, +void cycle_osd_field_arg_from_xml_node(struct cycle_osd_field *field, const char *nodename, const char *content); -bool osd_field_is_valid(struct window_switcher_field *field); -void osd_field_free(struct window_switcher_field *field); +bool cycle_osd_field_is_valid(struct cycle_osd_field *field); +void cycle_osd_field_free(struct cycle_osd_field *field); /* Internal API */ -struct osd_item { +struct cycle_osd_item { struct view *view; struct wlr_scene_tree *tree; struct wl_list link; }; -struct osd_impl { +struct cycle_osd_impl { /* * Create a scene-tree of OSD for an output. - * This sets output->osd_scene.{items,tree}. + * This sets output->cycle_osd.{items,tree}. */ void (*create)(struct output *output, struct wl_array *views); /* - * Update output->osd_scene.tree to highlight - * server->osd_state.cycle_view. + * Update output->cycle_osd.tree to highlight + * server->cycle_state.selected_view. */ void (*update)(struct output *output); }; -extern struct osd_impl osd_classic_impl; -extern struct osd_impl osd_thumbnail_impl; +extern struct cycle_osd_impl cycle_osd_classic_impl; +extern struct cycle_osd_impl cycle_osd_thumbnail_impl; #endif // LABWC_CYCLE_H diff --git a/include/labwc.h b/include/labwc.h index 160c42f4..d6fd78ff 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -18,7 +18,7 @@ enum input_mode { LAB_INPUT_STATE_MOVE, LAB_INPUT_STATE_RESIZE, LAB_INPUT_STATE_MENU, - LAB_INPUT_STATE_WINDOW_SWITCHER, + LAB_INPUT_STATE_CYCLE, /* a.k.a. window switching */ }; struct seat { @@ -302,15 +302,15 @@ struct server { struct wlr_security_context_manager_v1 *security_context_manager_v1; /* Set when in cycle (alt-tab) mode */ - struct osd_state { - struct view *cycle_view; + struct cycle_state { + struct view *selected_view; bool preview_was_shaded; bool preview_was_enabled; struct wlr_scene_node *preview_node; struct wlr_scene_tree *preview_parent; struct wlr_scene_node *preview_anchor; struct lab_scene_rect *preview_outline; - } osd_state; + } cycle; struct theme *theme; diff --git a/include/node.h b/include/node.h index ad21f313..7b4936d6 100644 --- a/include/node.h +++ b/include/node.h @@ -53,10 +53,10 @@ struct menuitem *node_menuitem_from_node( struct wlr_scene_node *wlr_scene_node); /** - * node_osd_item_from_node - return osd item struct from node + * node_cycle_osd_item_from_node - return cycle OSD item struct from node * @wlr_scene_node: wlr_scene_node from which to return data */ -struct osd_item *node_osd_item_from_node( +struct cycle_osd_item *node_cycle_osd_item_from_node( struct wlr_scene_node *wlr_scene_node); /** diff --git a/include/output.h b/include/output.h index 3226c964..25001247 100644 --- a/include/output.h +++ b/include/output.h @@ -15,14 +15,14 @@ struct output { struct wlr_scene_output *scene_output; struct wlr_scene_tree *layer_tree[LAB_NR_LAYERS]; struct wlr_scene_tree *layer_popup_tree; - struct wlr_scene_tree *osd_tree; + struct wlr_scene_tree *cycle_osd_tree; struct wlr_scene_tree *session_lock_tree; struct wlr_scene_buffer *workspace_osd; - struct osd_scene { - struct wl_list items; /* struct osd_item */ + struct cycle_osd_scene { + struct wl_list items; /* struct cycle_osd_item */ struct wlr_scene_tree *tree; - } osd_scene; + } cycle_osd; /* In output-relative scene coordinates */ struct wlr_box usable_area; diff --git a/src/action.c b/src/action.c index 02c90eca..ceeffd67 100644 --- a/src/action.c +++ b/src/action.c @@ -1112,17 +1112,17 @@ run_action(struct view *view, struct server *server, struct action *action, } break; case ACTION_TYPE_NEXT_WINDOW: - if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { - osd_cycle(server, LAB_CYCLE_DIR_FORWARD); + if (server->input_mode == LAB_INPUT_STATE_CYCLE) { + cycle_step(server, LAB_CYCLE_DIR_FORWARD); } else { - osd_begin(server, LAB_CYCLE_DIR_FORWARD); + cycle_begin(server, LAB_CYCLE_DIR_FORWARD); } break; case ACTION_TYPE_PREVIOUS_WINDOW: - if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { - osd_cycle(server, LAB_CYCLE_DIR_BACKWARD); + if (server->input_mode == LAB_INPUT_STATE_CYCLE) { + cycle_step(server, LAB_CYCLE_DIR_BACKWARD); } else { - osd_begin(server, LAB_CYCLE_DIR_BACKWARD); + cycle_begin(server, LAB_CYCLE_DIR_BACKWARD); } break; case ACTION_TYPE_RECONFIGURE: @@ -1569,7 +1569,7 @@ actions_run(struct view *activator, struct server *server, struct action *action; wl_list_for_each(action, actions, link) { - if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER + if (server->input_mode == LAB_INPUT_STATE_CYCLE && action->type != ACTION_TYPE_NEXT_WINDOW && action->type != ACTION_TYPE_PREVIOUS_WINDOW) { wlr_log(WLR_INFO, "Only NextWindow or PreviousWindow " diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 0f4b8c47..dc11b1c8 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -323,23 +323,23 @@ fill_window_rules(xmlNode *node) static void clear_window_switcher_fields(void) { - struct window_switcher_field *field, *field_tmp; + struct cycle_osd_field *field, *field_tmp; wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) { wl_list_remove(&field->link); - osd_field_free(field); + cycle_osd_field_free(field); } } static void fill_window_switcher_field(xmlNode *node) { - struct window_switcher_field *field = znew(*field); + struct cycle_osd_field *field = znew(*field); wl_list_append(&rc.window_switcher.fields, &field->link); xmlNode *child; char *key, *content; LAB_XML_FOR_EACH(node, child, key, content) { - osd_field_arg_from_xml_node(field, key, content); + cycle_osd_field_arg_from_xml_node(field, key, content); } } @@ -1212,20 +1212,20 @@ entry(xmlNode *node, char *nodename, char *content) set_bool(content, &rc.window_switcher.show); } else if (!strcasecmp(nodename, "style.osd.windowSwitcher")) { if (!strcasecmp(content, "classic")) { - rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; + rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; } else if (!strcasecmp(content, "thumbnail")) { - rc.window_switcher.style = WINDOW_SWITCHER_THUMBNAIL; + rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL; } else { wlr_log(WLR_ERROR, "Invalid windowSwitcher style %s: " "should be one of classic|thumbnail", content); } } else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) { if (!strcasecmp(content, "all")) { - rc.window_switcher.output_criteria = OSD_OUTPUT_ALL; + rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL; } else if (!strcasecmp(content, "pointer")) { - rc.window_switcher.output_criteria = OSD_OUTPUT_POINTER; + rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_POINTER; } else if (!strcasecmp(content, "keyboard")) { - rc.window_switcher.output_criteria = OSD_OUTPUT_KEYBOARD; + rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_KEYBOARD; } else { wlr_log(WLR_ERROR, "Invalid windowSwitcher output %s: " "should be one of all|pointer|keyboard", content); @@ -1238,9 +1238,9 @@ entry(xmlNode *node, char *nodename, char *content) " Use "); } else if (!strcasecmp(nodename, "style.windowSwitcher")) { if (!strcasecmp(content, "classic")) { - rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; + rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; } else if (!strcasecmp(content, "thumbnail")) { - rc.window_switcher.style = WINDOW_SWITCHER_THUMBNAIL; + rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL; } wlr_log(WLR_ERROR, " is deprecated." " Use "); @@ -1465,8 +1465,8 @@ rcxml_init(void) rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS; rc.window_switcher.show = true; - rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; - rc.window_switcher.output_criteria = OSD_OUTPUT_ALL; + rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; + rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL; rc.window_switcher.thumbnail_label_format = xstrdup("%T"); rc.window_switcher.preview = true; rc.window_switcher.outlines = true; @@ -1635,7 +1635,7 @@ static void load_default_window_switcher_fields(void) { static const struct { - enum window_switcher_field_content content; + enum cycle_osd_field_content content; int width; } fields[] = { #if HAVE_LIBSFDO @@ -1648,7 +1648,7 @@ load_default_window_switcher_fields(void) #endif }; - struct window_switcher_field *field; + struct cycle_osd_field *field; for (size_t i = 0; i < ARRAY_SIZE(fields); i++) { field = znew(*field); field->content = fields[i].content; @@ -1867,13 +1867,13 @@ validate(void) /* OSD fields */ int field_width_sum = 0; - struct window_switcher_field *field, *field_tmp; + struct cycle_osd_field *field, *field_tmp; wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) { field_width_sum += field->width; - if (!osd_field_is_valid(field) || field_width_sum > 100) { + if (!cycle_osd_field_is_valid(field) || field_width_sum > 100) { wlr_log(WLR_ERROR, "Deleting invalid window switcher field %p", field); wl_list_remove(&field->link); - osd_field_free(field); + cycle_osd_field_free(field); } } } diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 350b5f3f..b9a72a0e 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -17,32 +17,32 @@ #include "theme.h" #include "view.h" -static void update_osd(struct server *server); +static void update_cycle(struct server *server); static void destroy_osd_scenes(struct server *server) { struct output *output; wl_list_for_each(output, &server->outputs, link) { - struct osd_item *item, *tmp; - wl_list_for_each_safe(item, tmp, &output->osd_scene.items, link) { + struct cycle_osd_item *item, *tmp; + wl_list_for_each_safe(item, tmp, &output->cycle_osd.items, link) { wl_list_remove(&item->link); free(item); } - if (output->osd_scene.tree) { - wlr_scene_node_destroy(&output->osd_scene.tree->node); - output->osd_scene.tree = NULL; + if (output->cycle_osd.tree) { + wlr_scene_node_destroy(&output->cycle_osd.tree->node); + output->cycle_osd.tree = NULL; } } } static void -osd_update_preview_outlines(struct view *view) +update_preview_outlines(struct view *view) { /* Create / Update preview outline tree */ struct server *server = view->server; struct theme *theme = server->theme; - struct lab_scene_rect *rect = view->server->osd_state.preview_outline; + struct lab_scene_rect *rect = view->server->cycle.preview_outline; if (!rect) { struct lab_scene_rect_options opts = { .border_colors = (float *[3]) { @@ -55,7 +55,7 @@ osd_update_preview_outlines(struct view *view) }; rect = lab_scene_rect_create(&server->scene->tree, &opts); wlr_scene_node_place_above(&rect->tree->node, &server->menu_tree->node); - server->osd_state.preview_outline = rect; + server->cycle.preview_outline = rect; } struct wlr_box geo = ssd_max_extents(view); @@ -68,7 +68,7 @@ osd_update_preview_outlines(struct view *view) * If !start_view, the second focusable view is returned. */ static struct view * -get_next_cycle_view(struct server *server, struct view *start_view, +get_next_selected_view(struct server *server, struct view *start_view, enum lab_cycle_dir dir) { struct view *(*iter)(struct wl_list *head, struct view *view, @@ -96,127 +96,128 @@ get_next_cycle_view(struct server *server, struct view *start_view, } void -osd_on_view_destroy(struct view *view) +cycle_on_view_destroy(struct view *view) { assert(view); - struct osd_state *osd_state = &view->server->osd_state; + struct server *server = view->server; + struct cycle_state *cycle = &server->cycle; - if (view->server->input_mode != LAB_INPUT_STATE_WINDOW_SWITCHER) { + if (server->input_mode != LAB_INPUT_STATE_CYCLE) { /* OSD not active, no need for clean up */ return; } - if (osd_state->cycle_view == view) { + if (cycle->selected_view == view) { /* * If we are the current OSD selected view, cycle * to the next because we are dying. */ /* Also resets preview node */ - osd_state->cycle_view = get_next_cycle_view(view->server, - osd_state->cycle_view, LAB_CYCLE_DIR_BACKWARD); + cycle->selected_view = get_next_selected_view(server, + cycle->selected_view, LAB_CYCLE_DIR_BACKWARD); /* * If we cycled back to ourselves, then we have no more windows. * Just close the OSD for good. */ - if (osd_state->cycle_view == view || !osd_state->cycle_view) { - /* osd_finish() additionally resets cycle_view to NULL */ - osd_finish(view->server, /*switch_focus*/ false); + if (cycle->selected_view == view + || !cycle->selected_view) { + /* cycle_finish() additionally resets selected_view to NULL */ + cycle_finish(server, /*switch_focus*/ false); } } - if (osd_state->cycle_view) { + if (cycle->selected_view) { /* Recreate the OSD to reflect the view has now gone. */ - destroy_osd_scenes(view->server); - update_osd(view->server); + destroy_osd_scenes(server); + update_cycle(server); } if (view->scene_tree) { struct wlr_scene_node *node = &view->scene_tree->node; - if (osd_state->preview_anchor == node) { + if (cycle->preview_anchor == node) { /* * If we are the anchor for the current OSD selected view, * replace the anchor with the node before us. */ - osd_state->preview_anchor = lab_wlr_scene_get_prev_node(node); + cycle->preview_anchor = lab_wlr_scene_get_prev_node(node); } } } void -osd_on_cursor_release(struct server *server, struct wlr_scene_node *node) +cycle_on_cursor_release(struct server *server, struct wlr_scene_node *node) { - assert(server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER); + assert(server->input_mode == LAB_INPUT_STATE_CYCLE); - struct osd_item *item = node_osd_item_from_node(node); - server->osd_state.cycle_view = item->view; - osd_finish(server, /*switch_focus*/ true); + struct cycle_osd_item *item = node_cycle_osd_item_from_node(node); + server->cycle.selected_view = item->view; + cycle_finish(server, /*switch_focus*/ true); } static void restore_preview_node(struct server *server) { - struct osd_state *osd_state = &server->osd_state; - if (osd_state->preview_node) { - wlr_scene_node_reparent(osd_state->preview_node, - osd_state->preview_parent); + if (server->cycle.preview_node) { + wlr_scene_node_reparent(server->cycle.preview_node, + server->cycle.preview_parent); - if (osd_state->preview_anchor) { - wlr_scene_node_place_above(osd_state->preview_node, - osd_state->preview_anchor); + if (server->cycle.preview_anchor) { + wlr_scene_node_place_above(server->cycle.preview_node, + server->cycle.preview_anchor); } else { /* Selected view was the first node */ - wlr_scene_node_lower_to_bottom(osd_state->preview_node); + wlr_scene_node_lower_to_bottom(server->cycle.preview_node); } /* Node was disabled / minimized before, disable again */ - if (!osd_state->preview_was_enabled) { - wlr_scene_node_set_enabled(osd_state->preview_node, false); + if (!server->cycle.preview_was_enabled) { + wlr_scene_node_set_enabled(server->cycle.preview_node, false); } - if (osd_state->preview_was_shaded) { - struct view *view = node_view_from_node(osd_state->preview_node); + if (server->cycle.preview_was_shaded) { + struct view *view = node_view_from_node(server->cycle.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; + server->cycle.preview_node = NULL; + server->cycle.preview_parent = NULL; + server->cycle.preview_anchor = NULL; + server->cycle.preview_was_shaded = false; } } void -osd_begin(struct server *server, enum lab_cycle_dir direction) +cycle_begin(struct server *server, enum lab_cycle_dir direction) { if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { return; } - server->osd_state.cycle_view = get_next_cycle_view(server, - server->osd_state.cycle_view, direction); + server->cycle.selected_view = get_next_selected_view(server, + server->cycle.selected_view, direction); seat_focus_override_begin(&server->seat, - LAB_INPUT_STATE_WINDOW_SWITCHER, LAB_CURSOR_DEFAULT); - update_osd(server); + LAB_INPUT_STATE_CYCLE, LAB_CURSOR_DEFAULT); + update_cycle(server); /* Update cursor, in case it is within the area covered by OSD */ cursor_update_focus(server); } void -osd_cycle(struct server *server, enum lab_cycle_dir direction) +cycle_step(struct server *server, enum lab_cycle_dir direction) { - assert(server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER); + assert(server->input_mode == LAB_INPUT_STATE_CYCLE); - server->osd_state.cycle_view = get_next_cycle_view(server, - server->osd_state.cycle_view, direction); - update_osd(server); + server->cycle.selected_view = get_next_selected_view(server, + server->cycle.selected_view, direction); + update_cycle(server); } void -osd_finish(struct server *server, bool switch_focus) +cycle_finish(struct server *server, bool switch_focus) { - if (server->input_mode != LAB_INPUT_STATE_WINDOW_SWITCHER) { + if (server->input_mode != LAB_INPUT_STATE_CYCLE) { return; } @@ -224,62 +225,63 @@ osd_finish(struct server *server, bool switch_focus) /* FIXME: this sets focus to the old surface even with switch_focus=true */ seat_focus_override_end(&server->seat); - struct view *cycle_view = server->osd_state.cycle_view; - 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; + struct view *selected_view = server->cycle.selected_view; + server->cycle.preview_node = NULL; + server->cycle.preview_anchor = NULL; + server->cycle.selected_view = NULL; + server->cycle.preview_was_shaded = false; destroy_osd_scenes(server); - if (server->osd_state.preview_outline) { + if (server->cycle.preview_outline) { /* Destroy the whole multi_rect so we can easily react to new themes */ - wlr_scene_node_destroy(&server->osd_state.preview_outline->tree->node); - server->osd_state.preview_outline = NULL; + wlr_scene_node_destroy(&server->cycle.preview_outline->tree->node); + server->cycle.preview_outline = NULL; } /* Hiding OSD may need a cursor change */ cursor_update_focus(server); - if (switch_focus && cycle_view) { + if (switch_focus && selected_view) { if (rc.window_switcher.unshade) { - view_set_shade(cycle_view, false); + view_set_shade(selected_view, false); } - desktop_focus_view(cycle_view, /*raise*/ true); + desktop_focus_view(selected_view, /*raise*/ true); } } static void -preview_cycled_view(struct view *view) +preview_selected_view(struct view *view) { assert(view); assert(view->scene_tree); - struct osd_state *osd_state = &view->server->osd_state; + struct server *server = view->server; + struct cycle_state *cycle = &server->cycle; /* Move previous selected node back to its original place */ - restore_preview_node(view->server); + restore_preview_node(server); /* Store some pointers so we can reset the preview later on */ - osd_state->preview_node = &view->scene_tree->node; - osd_state->preview_parent = view->scene_tree->node.parent; + cycle->preview_node = &view->scene_tree->node; + cycle->preview_parent = view->scene_tree->node.parent; /* Remember the sibling right before the selected node */ - osd_state->preview_anchor = lab_wlr_scene_get_prev_node( - osd_state->preview_node); - while (osd_state->preview_anchor && !osd_state->preview_anchor->data) { + cycle->preview_anchor = lab_wlr_scene_get_prev_node( + cycle->preview_node); + while (cycle->preview_anchor && !cycle->preview_anchor->data) { /* Ignore non-view nodes */ - osd_state->preview_anchor = lab_wlr_scene_get_prev_node( - osd_state->preview_anchor); + cycle->preview_anchor = lab_wlr_scene_get_prev_node( + cycle->preview_anchor); } /* Store node enabled / minimized state and force-enable if disabled */ - osd_state->preview_was_enabled = osd_state->preview_node->enabled; - if (!osd_state->preview_was_enabled) { - wlr_scene_node_set_enabled(osd_state->preview_node, true); + cycle->preview_was_enabled = cycle->preview_node->enabled; + if (!cycle->preview_was_enabled) { + wlr_scene_node_set_enabled(cycle->preview_node, true); } if (rc.window_switcher.unshade && view->shaded) { view_set_shade(view, false); - osd_state->preview_was_shaded = true; + cycle->preview_was_shaded = true; } /* @@ -287,64 +289,64 @@ preview_cycled_view(struct view *view) * Create a permanent server->osd_preview_tree instead that can * also be used as parent for the preview outlines. */ - wlr_scene_node_reparent(osd_state->preview_node, + wlr_scene_node_reparent(cycle->preview_node, view->server->view_tree_always_on_top); /* Finally raise selected node to the top */ - wlr_scene_node_raise_to_top(osd_state->preview_node); + wlr_scene_node_raise_to_top(cycle->preview_node); } static void update_osd_on_output(struct server *server, struct output *output, - struct osd_impl *osd_impl, struct wl_array *views) + struct cycle_osd_impl *osd_impl, struct wl_array *views) { if (!output_is_usable(output)) { return; } - if (!output->osd_scene.tree) { + if (!output->cycle_osd.tree) { osd_impl->create(output, views); - assert(output->osd_scene.tree); + assert(output->cycle_osd.tree); } osd_impl->update(output); } static void -update_osd(struct server *server) +update_cycle(struct server *server) { struct wl_array views; wl_array_init(&views); view_array_append(server, &views, rc.window_switcher.criteria); - struct osd_impl *osd_impl = NULL; + struct cycle_osd_impl *osd_impl = NULL; switch (rc.window_switcher.style) { - case WINDOW_SWITCHER_CLASSIC: - osd_impl = &osd_classic_impl; + case CYCLE_OSD_STYLE_CLASSIC: + osd_impl = &cycle_osd_classic_impl; break; - case WINDOW_SWITCHER_THUMBNAIL: - osd_impl = &osd_thumbnail_impl; + case CYCLE_OSD_STYLE_THUMBNAIL: + osd_impl = &cycle_osd_thumbnail_impl; break; } - if (!wl_array_len(&views) || !server->osd_state.cycle_view) { - osd_finish(server, /*switch_focus*/ false); + if (!wl_array_len(&views) || !server->cycle.selected_view) { + cycle_finish(server, /*switch_focus*/ false); goto out; } if (rc.window_switcher.show) { /* Display the actual OSD */ switch (rc.window_switcher.output_criteria) { - case OSD_OUTPUT_ALL: { + case CYCLE_OSD_OUTPUT_ALL: { struct output *output; wl_list_for_each(output, &server->outputs, link) { update_osd_on_output(server, output, osd_impl, &views); } break; } - case OSD_OUTPUT_POINTER: + case CYCLE_OSD_OUTPUT_POINTER: update_osd_on_output(server, output_nearest_to_cursor(server), osd_impl, &views); break; - case OSD_OUTPUT_KEYBOARD: { + case CYCLE_OSD_OUTPUT_KEYBOARD: { struct output *output; if (server->active_view) { output = server->active_view->output; @@ -359,13 +361,13 @@ update_osd(struct server *server) } if (rc.window_switcher.preview) { - preview_cycled_view(server->osd_state.cycle_view); + preview_selected_view(server->cycle.selected_view); } /* Outline current window */ if (rc.window_switcher.outlines) { - if (view_is_focusable(server->osd_state.cycle_view)) { - osd_update_preview_outlines(server->osd_state.cycle_view); + if (view_is_focusable(server->cycle.selected_view)) { + update_preview_outlines(server->cycle.selected_view); } } diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index e02f8ed1..f954701e 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -20,8 +20,8 @@ #include "theme.h" #include "workspaces.h" -struct osd_classic_item { - struct osd_item base; +struct cycle_osd_classic_item { + struct cycle_osd_item base; struct wlr_scene_tree *normal_tree, *active_tree; }; @@ -34,7 +34,7 @@ create_fields_scene(struct server *server, struct view *view, struct window_switcher_classic_theme *switcher_theme = &theme->osd_window_switcher_classic; - struct window_switcher_field *field; + struct cycle_osd_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; @@ -51,7 +51,7 @@ create_fields_scene(struct server *server, struct view *view, height = icon_size; } else { struct buf buf = BUF_INIT; - osd_field_get_content(field, &buf, view); + cycle_osd_field_get_content(field, &buf, view); if (!string_null_or_empty(buf.data)) { struct scaled_font_buffer *font_buffer = @@ -76,9 +76,9 @@ create_fields_scene(struct server *server, struct view *view, } static void -osd_classic_create(struct output *output, struct wl_array *views) +cycle_osd_classic_create(struct output *output, struct wl_array *views) { - assert(!output->osd_scene.tree && wl_list_empty(&output->osd_scene.items)); + assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); struct server *server = output->server; struct theme *theme = server->theme; @@ -102,7 +102,7 @@ osd_classic_create(struct output *output, struct wl_array *views) h += switcher_theme->item_height; } - output->osd_scene.tree = wlr_scene_tree_create(output->osd_tree); + output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree); float *text_color = theme->osd_label_text_color; float *bg_color = theme->osd_bg_color; @@ -116,7 +116,7 @@ osd_classic_create(struct output *output, struct wl_array *views) .width = w, .height = h, }; - lab_scene_rect_create(output->osd_scene.tree, &bg_opts); + lab_scene_rect_create(output->cycle_osd.tree, &bg_opts); int y = padding; @@ -134,7 +134,7 @@ osd_classic_create(struct output *output, struct wl_array *views) } struct scaled_font_buffer *font_buffer = - scaled_font_buffer_create(output->osd_scene.tree); + scaled_font_buffer_create(output->cycle_osd.tree); wlr_scene_node_set_position(&font_buffer->scene_buffer->node, x, y + (switcher_theme->item_height - font_height(&font)) / 2); scaled_font_buffer_update(font_buffer, workspace_name, 0, @@ -157,12 +157,12 @@ osd_classic_create(struct output *output, struct wl_array *views) /* Draw text for each node */ struct view **view; wl_array_for_each(view, views) { - struct osd_classic_item *item = znew(*item); - wl_list_append(&output->osd_scene.items, &item->base.link); + struct cycle_osd_classic_item *item = znew(*item); + wl_list_append(&output->cycle_osd.items, &item->base.link); item->base.view = *view; - item->base.tree = wlr_scene_tree_create(output->osd_scene.tree); + item->base.tree = wlr_scene_tree_create(output->cycle_osd.tree); node_descriptor_create(&item->base.tree->node, - LAB_NODE_OSD_ITEM, NULL, item); + LAB_NODE_CYCLE_OSD_ITEM, NULL, item); /* * OSD border * +---------------------------------+ @@ -218,23 +218,23 @@ osd_classic_create(struct output *output, struct wl_array *views) error:; /* Center OSD */ - wlr_scene_node_set_position(&output->osd_scene.tree->node, + wlr_scene_node_set_position(&output->cycle_osd.tree->node, output_box.x + (output_box.width - w) / 2, output_box.y + (output_box.height - h) / 2); } static void -osd_classic_update(struct output *output) +cycle_osd_classic_update(struct output *output) { - struct osd_classic_item *item; - wl_list_for_each(item, &output->osd_scene.items, base.link) { - bool active = item->base.view == output->server->osd_state.cycle_view; + struct cycle_osd_classic_item *item; + wl_list_for_each(item, &output->cycle_osd.items, base.link) { + bool active = item->base.view == output->server->cycle.selected_view; wlr_scene_node_set_enabled(&item->normal_tree->node, !active); wlr_scene_node_set_enabled(&item->active_tree->node, active); } } -struct osd_impl osd_classic_impl = { - .create = osd_classic_create, - .update = osd_classic_update, +struct cycle_osd_impl cycle_osd_classic_impl = { + .create = cycle_osd_classic_create, + .update = cycle_osd_classic_update, }; diff --git a/src/cycle/osd-field.c b/src/cycle/osd-field.c index 4497573e..cfc32811 100644 --- a/src/cycle/osd-field.c +++ b/src/cycle/osd-field.c @@ -204,11 +204,11 @@ static const struct field_converter field_converter[LAB_FIELD_COUNT] = { [LAB_FIELD_TITLE] = { 'T', field_set_title }, [LAB_FIELD_TITLE_SHORT] = { 't', field_set_title_short }, /* fmt_char can never be matched so prevents LAB_FIELD_CUSTOM recursion */ - [LAB_FIELD_CUSTOM] = { '\0', osd_field_set_custom }, + [LAB_FIELD_CUSTOM] = { '\0', cycle_osd_field_set_custom }, }; void -osd_field_set_custom(struct buf *buf, struct view *view, const char *format) +cycle_osd_field_set_custom(struct buf *buf, struct view *view, const char *format) { if (!format) { wlr_log(WLR_ERROR, "Missing format for custom window switcher field"); @@ -286,7 +286,7 @@ reset_format: } void -osd_field_arg_from_xml_node(struct window_switcher_field *field, +cycle_osd_field_arg_from_xml_node(struct cycle_osd_field *field, const char *nodename, const char *content) { if (!strcmp(nodename, "content")) { @@ -332,7 +332,7 @@ osd_field_arg_from_xml_node(struct window_switcher_field *field, } bool -osd_field_is_valid(struct window_switcher_field *field) +cycle_osd_field_is_valid(struct cycle_osd_field *field) { if (field->content == LAB_FIELD_NONE) { wlr_log(WLR_ERROR, "Invalid OSD field: no content set"); @@ -350,7 +350,7 @@ osd_field_is_valid(struct window_switcher_field *field) } void -osd_field_get_content(struct window_switcher_field *field, +cycle_osd_field_get_content(struct cycle_osd_field *field, struct buf *buf, struct view *view) { if (field->content == LAB_FIELD_NONE) { @@ -363,7 +363,7 @@ osd_field_get_content(struct window_switcher_field *field, } void -osd_field_free(struct window_switcher_field *field) +cycle_osd_field_free(struct cycle_osd_field *field) { zfree(field->format); zfree(field); diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index 724fc62a..5dd157c1 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -19,8 +19,8 @@ #include "theme.h" #include "view.h" -struct osd_thumbnail_item { - struct osd_item base; +struct cycle_osd_thumbnail_item { + struct cycle_osd_item base; struct scaled_font_buffer *normal_label; struct scaled_font_buffer *active_label; struct lab_scene_rect *active_bg; @@ -102,7 +102,7 @@ create_label(struct wlr_scene_tree *parent, struct view *view, const float *text_color, const float *bg_color, int y) { struct buf buf = BUF_INIT; - osd_field_set_custom(&buf, view, + cycle_osd_field_set_custom(&buf, view, rc.window_switcher.thumbnail_label_format); struct scaled_font_buffer *buffer = scaled_font_buffer_create(parent); @@ -115,7 +115,7 @@ create_label(struct wlr_scene_tree *parent, struct view *view, return buffer; } -static struct osd_thumbnail_item * +static struct cycle_osd_thumbnail_item * create_item_scene(struct wlr_scene_tree *parent, struct view *view, struct output *output) { @@ -136,10 +136,10 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, return NULL; } - struct osd_thumbnail_item *item = znew(*item); - wl_list_append(&output->osd_scene.items, &item->base.link); + struct cycle_osd_thumbnail_item *item = znew(*item); + wl_list_append(&output->cycle_osd.items, &item->base.link); struct wlr_scene_tree *tree = wlr_scene_tree_create(parent); - node_descriptor_create(&tree->node, LAB_NODE_OSD_ITEM, NULL, item); + node_descriptor_create(&tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item); item->base.tree = tree; item->base.view = view; @@ -226,9 +226,9 @@ get_items_geometry(struct output *output, struct theme *theme, } static void -osd_thumbnail_create(struct output *output, struct wl_array *views) +cycle_osd_thumbnail_create(struct output *output, struct wl_array *views) { - assert(!output->osd_scene.tree && wl_list_empty(&output->osd_scene.items)); + assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); struct server *server = output->server; struct theme *theme = server->theme; @@ -236,7 +236,7 @@ osd_thumbnail_create(struct output *output, struct wl_array *views) &theme->osd_window_switcher_thumbnail; int padding = theme->osd_border_width + switcher_theme->padding; - output->osd_scene.tree = wlr_scene_tree_create(output->osd_tree); + output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree); int nr_views = wl_array_len(views); assert(nr_views > 0); @@ -247,8 +247,8 @@ osd_thumbnail_create(struct output *output, struct wl_array *views) struct view **view; int index = 0; wl_array_for_each(view, views) { - struct osd_thumbnail_item *item = create_item_scene( - output->osd_scene.tree, *view, output); + struct cycle_osd_thumbnail_item *item = create_item_scene( + output->cycle_osd.tree, *view, output); if (!item) { break; } @@ -268,7 +268,7 @@ osd_thumbnail_create(struct output *output, struct wl_array *views) .height = nr_rows * switcher_theme->item_height + 2 * padding, }; struct lab_scene_rect *bg = - lab_scene_rect_create(output->osd_scene.tree, &bg_opts); + lab_scene_rect_create(output->cycle_osd.tree, &bg_opts); wlr_scene_node_lower_to_bottom(&bg->tree->node); /* center */ @@ -277,15 +277,15 @@ osd_thumbnail_create(struct output *output, struct wl_array *views) &output_box); int lx = output_box.x + (output_box.width - bg_opts.width) / 2; int ly = output_box.y + (output_box.height - bg_opts.height) / 2; - wlr_scene_node_set_position(&output->osd_scene.tree->node, lx, ly); + wlr_scene_node_set_position(&output->cycle_osd.tree->node, lx, ly); } static void -osd_thumbnail_update(struct output *output) +cycle_osd_thumbnail_update(struct output *output) { - struct osd_thumbnail_item *item; - wl_list_for_each(item, &output->osd_scene.items, base.link) { - bool active = (item->base.view == output->server->osd_state.cycle_view); + struct cycle_osd_thumbnail_item *item; + wl_list_for_each(item, &output->cycle_osd.items, base.link) { + bool active = (item->base.view == output->server->cycle.selected_view); wlr_scene_node_set_enabled(&item->active_bg->tree->node, active); wlr_scene_node_set_enabled( &item->active_label->scene_buffer->node, active); @@ -294,7 +294,7 @@ osd_thumbnail_update(struct output *output) } } -struct osd_impl osd_thumbnail_impl = { - .create = osd_thumbnail_create, - .update = osd_thumbnail_update, +struct cycle_osd_impl cycle_osd_thumbnail_impl = { + .create = cycle_osd_thumbnail_create, + .update = cycle_osd_thumbnail_update, }; diff --git a/src/debug.c b/src/debug.c index cd2ce3d2..7a39824f 100644 --- a/src/debug.c +++ b/src/debug.c @@ -21,7 +21,7 @@ #define IGNORE_SSD true #define IGNORE_MENU true -#define IGNORE_OSD_PREVIEW_OUTLINE true +#define IGNORE_CYCLE_PREVIEW_OUTLINE true #define IGNORE_SNAPPING_OVERLAY true static struct view *last_view; @@ -118,7 +118,7 @@ get_special(struct server *server, struct wlr_scene_node *node) if (node->parent == &server->scene->tree) { struct output *output; wl_list_for_each(output, &server->outputs, link) { - if (node == &output->osd_tree->node) { + if (node == &output->cycle_osd_tree->node) { return "output->osd_tree"; } if (node == &output->layer_popup_tree->node) { @@ -150,10 +150,10 @@ get_special(struct server *server, struct wlr_scene_node *node) /* Created on-demand */ return "seat->im_relay->popup_tree"; } - if (server->osd_state.preview_outline - && node == &server->osd_state.preview_outline->tree->node) { + if (server->cycle.preview_outline + && node == &server->cycle.preview_outline->tree->node) { /* Created on-demand */ - return "osd_state->preview_outline"; + return "cycle_state->preview_outline"; } #if HAVE_XWAYLAND if (node == &server->unmanaged_tree->node) { @@ -216,13 +216,11 @@ dump_tree(struct server *server, struct wlr_scene_node *node, } printf("%.*s %*c %4d %4d [%p]\n", max_width - 1, type, padding, ' ', x, y, node); - struct lab_scene_rect *osd_preview_outline = - server->osd_state.preview_outline; if ((IGNORE_MENU && node == &server->menu_tree->node) || (IGNORE_SSD && last_view && ssd_debug_is_root_node(last_view->ssd, node)) - || (IGNORE_OSD_PREVIEW_OUTLINE && osd_preview_outline - && node == &osd_preview_outline->tree->node) + || (IGNORE_CYCLE_PREVIEW_OUTLINE && server->cycle.preview_outline + && node == &server->cycle.preview_outline->tree->node) || (IGNORE_SNAPPING_OVERLAY && server->seat.overlay.rect && node == &server->seat.overlay.rect->tree->node)) { printf("%*c%s\n", pos + 4 + INDENT_SIZE, ' ', ""); diff --git a/src/desktop.c b/src/desktop.c index 76c3275e..abff9c13 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -74,7 +74,7 @@ desktop_focus_view(struct view *view, bool raise) return; } - if (view->server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { + if (view->server->input_mode == LAB_INPUT_STATE_CYCLE) { wlr_log(WLR_DEBUG, "not focusing window while window switching"); return; } @@ -340,10 +340,10 @@ get_cursor_context(struct server *server) ret.node = node; ret.type = LAB_NODE_MENUITEM; return ret; - case LAB_NODE_OSD_ITEM: + case LAB_NODE_CYCLE_OSD_ITEM: /* Always return the top scene node for osd items */ ret.node = node; - ret.type = LAB_NODE_OSD_ITEM; + ret.type = LAB_NODE_CYCLE_OSD_ITEM; return ret; case LAB_NODE_BUTTON_FIRST...LAB_NODE_BUTTON_LAST: case LAB_NODE_SSD_ROOT: diff --git a/src/input/cursor.c b/src/input/cursor.c index 2660ef6d..500ac08e 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -920,7 +920,7 @@ static void process_release_mousebinding(struct server *server, struct cursor_context *ctx, uint32_t button) { - if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { + if (server->input_mode == LAB_INPUT_STATE_CYCLE) { return; } @@ -989,7 +989,7 @@ static bool process_press_mousebinding(struct server *server, struct cursor_context *ctx, uint32_t button) { - if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { + if (server->input_mode == LAB_INPUT_STATE_CYCLE) { return false; } @@ -1157,9 +1157,9 @@ cursor_process_button_release(struct seat *seat, uint32_t button, } return notify; } - if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { - if (ctx.type == LAB_NODE_OSD_ITEM) { - osd_on_cursor_release(server, ctx.node); + if (server->input_mode == LAB_INPUT_STATE_CYCLE) { + if (ctx.type == LAB_NODE_CYCLE_OSD_ITEM) { + cycle_on_cursor_release(server, ctx.node); } return notify; } diff --git a/src/input/keyboard.c b/src/input/keyboard.c index ab47dd4c..0171dcd0 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -141,17 +141,16 @@ handle_modifiers(struct wl_listener *listener, void *data) overlay_update(seat); } - bool window_switcher_active = server->input_mode - == LAB_INPUT_STATE_WINDOW_SWITCHER; + bool cycling = server->input_mode == LAB_INPUT_STATE_CYCLE; - if ((window_switcher_active || seat->workspace_osd_shown_by_modifier) + if ((cycling || seat->workspace_osd_shown_by_modifier) && !keyboard_get_all_modifiers(seat)) { - if (window_switcher_active) { + if (cycling) { if (key_state_nr_bound_keys()) { should_cancel_cycling_on_next_key_release = true; } else { should_cancel_cycling_on_next_key_release = false; - osd_finish(server, /*switch_focus*/ true); + cycle_finish(server, /*switch_focus*/ true); } } if (seat->workspace_osd_shown_by_modifier) { @@ -388,7 +387,7 @@ handle_key_release(struct server *server, uint32_t evdev_keycode) */ if (should_cancel_cycling_on_next_key_release) { should_cancel_cycling_on_next_key_release = false; - osd_finish(server, /*switch_focus*/ true); + cycle_finish(server, /*switch_focus*/ true); } /* @@ -461,19 +460,19 @@ handle_cycle_view_key(struct server *server, struct keyinfo *keyinfo) for (int i = 0; i < keyinfo->translated.nr_syms; i++) { if (keyinfo->translated.syms[i] == XKB_KEY_Escape) { /* Esc deactivates window switcher */ - osd_finish(server, /*switch_focus*/ false); + cycle_finish(server, /*switch_focus*/ false); return true; } if (keyinfo->translated.syms[i] == XKB_KEY_Up || keyinfo->translated.syms[i] == XKB_KEY_Left) { /* Up/Left cycles the window backward */ - osd_cycle(server, LAB_CYCLE_DIR_BACKWARD); + cycle_step(server, LAB_CYCLE_DIR_BACKWARD); return true; } if (keyinfo->translated.syms[i] == XKB_KEY_Down || keyinfo->translated.syms[i] == XKB_KEY_Right) { /* Down/Right cycles the window forward */ - osd_cycle(server, LAB_CYCLE_DIR_FORWARD); + cycle_step(server, LAB_CYCLE_DIR_FORWARD); return true; } } @@ -523,7 +522,7 @@ handle_compositor_keybindings(struct keyboard *keyboard, key_state_store_pressed_key_as_bound(event->keycode); handle_menu_keys(server, &keyinfo.translated); return LAB_KEY_HANDLED_TRUE; - } else if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { + } else if (server->input_mode == LAB_INPUT_STATE_CYCLE) { if (handle_cycle_view_key(server, &keyinfo)) { key_state_store_pressed_key_as_bound(event->keycode); return LAB_KEY_HANDLED_TRUE; diff --git a/src/node.c b/src/node.c index 1b3dce99..19b4b5ae 100644 --- a/src/node.c +++ b/src/node.c @@ -59,13 +59,13 @@ node_menuitem_from_node(struct wlr_scene_node *wlr_scene_node) return (struct menuitem *)node_descriptor->data; } -struct osd_item * -node_osd_item_from_node(struct wlr_scene_node *wlr_scene_node) +struct cycle_osd_item * +node_cycle_osd_item_from_node(struct wlr_scene_node *wlr_scene_node) { assert(wlr_scene_node->data); struct node_descriptor *node_descriptor = wlr_scene_node->data; - assert(node_descriptor->type == LAB_NODE_OSD_ITEM); - return (struct osd_item *)node_descriptor->data; + assert(node_descriptor->type == LAB_NODE_CYCLE_OSD_ITEM); + return (struct cycle_osd_item *)node_descriptor->data; } struct ssd_button * diff --git a/src/output.c b/src/output.c index 3859ee15..f8c19c56 100644 --- a/src/output.c +++ b/src/output.c @@ -182,7 +182,7 @@ handle_output_destroy(struct wl_listener *listener, void *data) wlr_scene_node_destroy(&output->layer_tree[i]->node); } wlr_scene_node_destroy(&output->layer_popup_tree->node); - wlr_scene_node_destroy(&output->osd_tree->node); + wlr_scene_node_destroy(&output->cycle_osd_tree->node); wlr_scene_node_destroy(&output->session_lock_tree->node); if (output->workspace_osd) { wlr_scene_node_destroy(&output->workspace_osd->node); @@ -542,7 +542,7 @@ handle_new_output(struct wl_listener *listener, void *data) wl_signal_add(&wlr_output->events.request_state, &output->request_state); wl_list_init(&output->regions); - wl_list_init(&output->osd_scene.items); + wl_list_init(&output->cycle_osd.items); /* * Create layer-trees (background, bottom, top and overlay) and @@ -553,7 +553,7 @@ handle_new_output(struct wl_listener *listener, void *data) wlr_scene_tree_create(&server->scene->tree); } output->layer_popup_tree = wlr_scene_tree_create(&server->scene->tree); - output->osd_tree = wlr_scene_tree_create(&server->scene->tree); + output->cycle_osd_tree = wlr_scene_tree_create(&server->scene->tree); output->session_lock_tree = wlr_scene_tree_create(&server->scene->tree); /* @@ -577,7 +577,7 @@ handle_new_output(struct wl_listener *listener, void *data) wlr_scene_node_place_below(&output->layer_tree[3]->node, menu_node); wlr_scene_node_place_below(&output->layer_popup_tree->node, menu_node); - wlr_scene_node_raise_to_top(&output->osd_tree->node); + wlr_scene_node_raise_to_top(&output->cycle_osd_tree->node); wlr_scene_node_raise_to_top(&output->session_lock_tree->node); /* diff --git a/src/server.c b/src/server.c index abaaaf0b..11037a48 100644 --- a/src/server.c +++ b/src/server.c @@ -562,21 +562,21 @@ server_init(struct server *server) * z-order for nodes which cover the whole work-area. For per-output * scene-trees, see handle_new_output() in src/output.c * - * | Type | Scene Tree | Per Output | Example - * | ----------------- | ---------------- | ---------- | ------- - * | ext-session | lock-screen | Yes | swaylock - * | osd | osd_tree | Yes | - * | compositor-menu | menu_tree | No | root-menu - * | layer-shell | layer-popups | Yes | - * | layer-shell | overlay-layer | Yes | - * | layer-shell | top-layer | Yes | waybar - * | xwayland-OR | unmanaged | No | dmenu - * | xdg-popups | xdg-popups | No | - * | toplevels windows | always-on-top | No | - * | toplevels windows | normal | No | firefox - * | toplevels windows | always-on-bottom | No | pcmanfm-qt --desktop - * | layer-shell | bottom-layer | Yes | waybar - * | layer-shell | background-layer | Yes | swaybg + * | Type | Scene Tree | Per Output | Example + * | ------------------- | ---------------- | ---------- | ------- + * | ext-session | lock-screen | Yes | swaylock + * | window switcher OSD | cycle_osd_tree | Yes | + * | compositor-menu | menu_tree | No | root-menu + * | layer-shell | layer-popups | Yes | + * | layer-shell | overlay-layer | Yes | + * | layer-shell | top-layer | Yes | waybar + * | xwayland-OR | unmanaged | No | dmenu + * | xdg-popups | xdg-popups | No | + * | toplevels windows | always-on-top | No | + * | toplevels windows | normal | No | firefox + * | toplevels windows | always-on-bottom | No | pcmanfm-qt --desktop + * | layer-shell | bottom-layer | Yes | waybar + * | layer-shell | background-layer | Yes | swaybg */ server->view_tree_always_on_bottom = wlr_scene_tree_create(&server->scene->tree); diff --git a/src/view.c b/src/view.c index acb1ec0b..662e85fb 100644 --- a/src/view.c +++ b/src/view.c @@ -823,7 +823,7 @@ view_minimize(struct view *view, bool minimized) { assert(view); - if (view->server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { + if (view->server->input_mode == LAB_INPUT_STATE_CYCLE) { wlr_log(WLR_ERROR, "not minimizing window while window switching"); return; } @@ -2620,7 +2620,7 @@ view_destroy(struct view *view) zfree(view->tiled_region_evacuate); } - osd_on_view_destroy(view); + cycle_on_view_destroy(view); undecorate(view); view_set_icon(view, NULL, NULL); From acb3da7903887a7ff658391c0d68fcda9e434433 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 21 Nov 2025 01:25:19 +0900 Subject: [PATCH 03/50] cursor: generalize seat->pressed This commit moduralize seat_{set,reset}_pressed() into cursor_context_save() so that we can also have seat->hovered later. --- include/input/cursor.h | 15 ++++++++ include/labwc.h | 10 +---- src/dnd.c | 2 +- src/input/cursor.c | 86 ++++++++++++++++++++++++++++++++++++------ src/seat.c | 40 -------------------- src/view.c | 4 -- src/xdg.c | 4 +- src/xwayland.c | 4 +- 8 files changed, 97 insertions(+), 68 deletions(-) diff --git a/include/input/cursor.h b/include/input/cursor.h index 13ab72d1..12b96aac 100644 --- a/include/input/cursor.h +++ b/include/input/cursor.h @@ -38,6 +38,14 @@ struct cursor_context { double sx, sy; }; +/* Used to persistently store cursor context (e.g. in seat->pressed) */ +struct cursor_context_saved { + struct cursor_context ctx; + struct wl_listener view_destroy; + struct wl_listener node_destroy; + struct wl_listener surface_destroy; +}; + /** * get_cursor_context - find view, surface and scene_node at cursor * @@ -65,6 +73,13 @@ void cursor_set(struct seat *seat, enum lab_cursors cursor); void cursor_set_visible(struct seat *seat, bool visible); +/* + * Safely store a cursor context to saved_ctx. saved_ctx is cleared when either + * of its node, surface and view is destroyed. + */ +void cursor_context_save(struct cursor_context_saved *saved_ctx, + const struct cursor_context *ctx); + /** * cursor_get_resize_edges - calculate resize edge based on cursor position * @cursor - the current cursor (usually server->seat.cursor) diff --git a/include/labwc.h b/include/labwc.h index d6fd78ff..999db395 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -65,8 +65,7 @@ struct seat { struct input_method_relay *input_method_relay; /** - * This is usually zeroed and is only set on button press while the - * mouse is over a view or surface, and zeroed on button release. + * Cursor context saved when a mouse button is pressed on a view/surface. * It is used to send cursor motion events to a surface even though * the cursor has left the surface in the meantime. * @@ -76,10 +75,8 @@ struct seat { * It is also used to: * - determine the target view for action in "Drag" mousebind * - validate view move/resize requests from CSD clients - * - * Both (view && !surface) and (surface && !view) are possible. */ - struct cursor_context pressed; + struct cursor_context_saved pressed; struct lab_set bound_buttons; @@ -139,7 +136,6 @@ struct seat { struct wl_list tablet_pads; struct wl_listener constraint_commit; - struct wl_listener pressed_surface_destroy; struct wlr_virtual_pointer_manager_v1 *virtual_pointer; struct wl_listener new_virtual_pointer; @@ -392,8 +388,6 @@ void seat_pointer_end_grab(struct seat *seat, struct wlr_surface *surface); void seat_focus_lock_surface(struct seat *seat, struct wlr_surface *surface); void seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer); -void seat_set_pressed(struct seat *seat, struct cursor_context *ctx); -void seat_reset_pressed(struct seat *seat); void seat_output_layout_changed(struct seat *seat); /* diff --git a/src/dnd.c b/src/dnd.c index 8ed7c311..ff65cffa 100644 --- a/src/dnd.c +++ b/src/dnd.c @@ -34,7 +34,7 @@ handle_drag_start(struct wl_listener *listener, void *data) struct wlr_drag *drag = data; seat->drag.active = true; - seat_reset_pressed(seat); + cursor_context_save(&seat->pressed, NULL); if (drag->icon) { /* Cleans up automatically on drag->icon->events.destroy */ wlr_scene_drag_icon_create(seat->drag.icons, drag->icon); diff --git a/src/input/cursor.c b/src/input/cursor.c index 500ac08e..5a09bf50 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -437,8 +437,72 @@ cursor_update_image(struct seat *seat) cursor_names[cursor]); } +static void +clear_cursor_context(struct cursor_context_saved *saved_ctx) +{ + if (saved_ctx->node_destroy.notify) { + wl_list_remove(&saved_ctx->node_destroy.link); + } + if (saved_ctx->surface_destroy.notify) { + wl_list_remove(&saved_ctx->surface_destroy.link); + } + if (saved_ctx->view_destroy.notify) { + wl_list_remove(&saved_ctx->view_destroy.link); + } + *saved_ctx = (struct cursor_context_saved) {0}; +} + +static void +handle_ctx_node_destroy(struct wl_listener *listener, void *data) +{ + struct cursor_context_saved *saved_ctx = + wl_container_of(listener, saved_ctx, node_destroy); + clear_cursor_context(saved_ctx); +} + +static void +handle_ctx_surface_destroy(struct wl_listener *listener, void *data) +{ + struct cursor_context_saved *saved_ctx = + wl_container_of(listener, saved_ctx, surface_destroy); + clear_cursor_context(saved_ctx); +} + +static void +handle_ctx_view_destroy(struct wl_listener *listener, void *data) +{ + struct cursor_context_saved *saved_ctx = + wl_container_of(listener, saved_ctx, view_destroy); + clear_cursor_context(saved_ctx); +} + +void +cursor_context_save(struct cursor_context_saved *saved_ctx, + const struct cursor_context *ctx) +{ + assert(saved_ctx); + + clear_cursor_context(saved_ctx); + if (!ctx) { + return; + } + saved_ctx->ctx = *ctx; + if (ctx->node) { + saved_ctx->node_destroy.notify = handle_ctx_node_destroy; + wl_signal_add(&ctx->node->events.destroy, &saved_ctx->node_destroy); + } + if (ctx->surface) { + saved_ctx->surface_destroy.notify = handle_ctx_surface_destroy; + wl_signal_add(&ctx->surface->events.destroy, &saved_ctx->surface_destroy); + } + if (ctx->view) { + saved_ctx->view_destroy.notify = handle_ctx_view_destroy; + wl_signal_add(&ctx->view->events.destroy, &saved_ctx->view_destroy); + } +} + static bool -update_pressed_surface(struct seat *seat, struct cursor_context *ctx) +update_pressed_surface(struct seat *seat, const struct cursor_context *ctx) { /* * In most cases, we don't want to leave one surface and enter @@ -454,10 +518,10 @@ update_pressed_surface(struct seat *seat, struct cursor_context *ctx) if (!wlr_seat_pointer_has_grab(seat->seat)) { return false; } - if (seat->pressed.surface && ctx->surface != seat->pressed.surface) { + if (seat->pressed.ctx.surface && ctx->surface != seat->pressed.ctx.surface) { struct wlr_surface *toplevel = get_toplevel(ctx->surface); - if (toplevel && toplevel == get_toplevel(seat->pressed.surface)) { - seat_set_pressed(seat, ctx); + if (toplevel && toplevel == get_toplevel(seat->pressed.ctx.surface)) { + cursor_context_save(&seat->pressed, ctx); return true; } } @@ -487,7 +551,7 @@ cursor_update_common(struct server *server, struct cursor_context *ctx, } /* TODO: verify drag_icon logic */ - if (seat->pressed.surface && ctx->surface != seat->pressed.surface + if (seat->pressed.ctx.surface && ctx->surface != seat->pressed.ctx.surface && !update_pressed_surface(seat, ctx) && !seat->drag.active) { if (cursor_has_moved) { @@ -499,7 +563,7 @@ cursor_update_common(struct server *server, struct cursor_context *ctx, * if the cursor moves outside of the surface. */ int lx, ly; - wlr_scene_node_coords(seat->pressed.node, &lx, &ly); + wlr_scene_node_coords(seat->pressed.ctx.node, &lx, &ly); *sx = server->seat.cursor->x - lx; *sy = server->seat.cursor->y - ly; return true; @@ -597,8 +661,8 @@ cursor_process_motion(struct server *server, uint32_t time, double *sx, double * * moving/resizing the wrong view */ mousebind->pressed_in_context = false; - actions_run(seat->pressed.view, server, - &mousebind->actions, &seat->pressed); + actions_run(seat->pressed.ctx.view, server, + &mousebind->actions, &seat->pressed.ctx); } } @@ -1073,7 +1137,7 @@ cursor_process_button_press(struct seat *seat, uint32_t button, uint32_t time_ms if (ctx.view || ctx.surface) { /* Store cursor context for later action processing */ - seat_set_pressed(seat, &ctx); + cursor_context_save(&seat->pressed, &ctx); } if (server->input_mode == LAB_INPUT_STATE_MENU) { @@ -1138,12 +1202,12 @@ cursor_process_button_release(struct seat *seat, uint32_t button, { struct server *server = seat->server; struct cursor_context ctx = get_cursor_context(server); - struct wlr_surface *pressed_surface = seat->pressed.surface; + struct wlr_surface *pressed_surface = seat->pressed.ctx.surface; /* Always notify button release event when it's not bound */ const bool notify = !lab_set_contains(&seat->bound_buttons, button); - seat_reset_pressed(seat); + cursor_context_save(&seat->pressed, NULL); if (server->input_mode == LAB_INPUT_STATE_MENU) { /* TODO: take into account overflow of time_msec */ diff --git a/src/seat.c b/src/seat.c index 239155f9..a5cdf3ee 100644 --- a/src/seat.c +++ b/src/seat.c @@ -848,46 +848,6 @@ seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer) seat->focused_layer = layer; } -static void -pressed_surface_destroy(struct wl_listener *listener, void *data) -{ - struct seat *seat = wl_container_of(listener, seat, - pressed_surface_destroy); - - /* - * Using data directly prevents 'unused variable' - * warning when compiling without asserts - */ - assert(data == seat->pressed.surface); - - seat_reset_pressed(seat); -} - -void -seat_set_pressed(struct seat *seat, struct cursor_context *ctx) -{ - assert(ctx); - assert(ctx->view || ctx->surface); - seat_reset_pressed(seat); - - seat->pressed = *ctx; - - if (ctx->surface) { - seat->pressed_surface_destroy.notify = pressed_surface_destroy; - wl_signal_add(&ctx->surface->events.destroy, - &seat->pressed_surface_destroy); - } -} - -void -seat_reset_pressed(struct seat *seat) -{ - if (seat->pressed.surface) { - wl_list_remove(&seat->pressed_surface_destroy.link); - } - seat->pressed = (struct cursor_context){0}; -} - void seat_output_layout_changed(struct seat *seat) { diff --git a/src/view.c b/src/view.c index 662e85fb..473a1b37 100644 --- a/src/view.c +++ b/src/view.c @@ -2612,10 +2612,6 @@ view_destroy(struct view *view) server->session_lock_manager->last_active_view = NULL; } - if (server->seat.pressed.view == view) { - seat_reset_pressed(&server->seat); - } - if (view->tiled_region_evacuate) { zfree(view->tiled_region_evacuate); } diff --git a/src/xdg.c b/src/xdg.c index f11d522c..2bb6d660 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -400,7 +400,7 @@ handle_request_move(struct wl_listener *listener, void *data) * want. */ struct view *view = wl_container_of(listener, view, request_move); - if (view == view->server->seat.pressed.view) { + if (view == view->server->seat.pressed.ctx.view) { interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE); } } @@ -418,7 +418,7 @@ handle_request_resize(struct wl_listener *listener, void *data) */ struct wlr_xdg_toplevel_resize_event *event = data; struct view *view = wl_container_of(listener, view, request_resize); - if (view == view->server->seat.pressed.view) { + if (view == view->server->seat.pressed.ctx.view) { interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); } } diff --git a/src/xwayland.c b/src/xwayland.c index efd8d8be..91e40897 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -289,7 +289,7 @@ handle_request_move(struct wl_listener *listener, void *data) * want. */ struct view *view = wl_container_of(listener, view, request_move); - if (view == view->server->seat.pressed.view) { + if (view == view->server->seat.pressed.ctx.view) { interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE); } } @@ -307,7 +307,7 @@ handle_request_resize(struct wl_listener *listener, void *data) */ struct wlr_xwayland_resize_event *event = data; struct view *view = wl_container_of(listener, view, request_resize); - if (view == view->server->seat.pressed.view) { + if (view == view->server->seat.pressed.ctx.view) { interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); } } From 7fb060e88ce8b6e2946487c26d5c8551c5b88a19 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 16 Nov 2025 03:39:49 +0900 Subject: [PATCH 04/50] cursor: clarify the semantics of update_pressed_surface() This should not change any behaviors. This is mainly just a preparation for the next commit. --- src/input/cursor.c | 49 ++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/input/cursor.c b/src/input/cursor.c index 5a09bf50..d3221513 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -530,11 +530,11 @@ update_pressed_surface(struct seat *seat, const struct cursor_context *ctx) /* * Common logic shared by cursor_update_focus(), process_cursor_motion() - * and cursor_axis() + * and process_cursor_axis() */ -static bool -cursor_update_common(struct server *server, struct cursor_context *ctx, - bool cursor_has_moved, double *sx, double *sy) +static void +cursor_update_common(struct server *server, const struct cursor_context *ctx, + struct cursor_context *notified_ctx) { struct seat *seat = &server->seat; struct wlr_seat *wlr_seat = seat->seat; @@ -547,14 +547,14 @@ cursor_update_common(struct server *server, struct cursor_context *ctx, * interactive move/resize, window switcher and * menu interaction. */ - return false; + return; } /* TODO: verify drag_icon logic */ if (seat->pressed.ctx.surface && ctx->surface != seat->pressed.ctx.surface && !update_pressed_surface(seat, ctx) && !seat->drag.active) { - if (cursor_has_moved) { + if (notified_ctx) { /* * Button has been pressed while over another * surface and is still held down. Just send @@ -564,11 +564,15 @@ cursor_update_common(struct server *server, struct cursor_context *ctx, */ int lx, ly; wlr_scene_node_coords(seat->pressed.ctx.node, &lx, &ly); - *sx = server->seat.cursor->x - lx; - *sy = server->seat.cursor->y - ly; - return true; + *notified_ctx = seat->pressed.ctx; + notified_ctx->sx = server->seat.cursor->x - lx; + notified_ctx->sy = server->seat.cursor->y - ly; } - return false; + return; + } + + if (notified_ctx) { + *notified_ctx = *ctx; } if (ctx->surface) { @@ -580,11 +584,6 @@ cursor_update_common(struct server *server, struct cursor_context *ctx, wlr_seat_pointer_notify_enter(wlr_seat, ctx->surface, ctx->sx, ctx->sy); seat->server_cursor = LAB_CURSOR_CLIENT; - if (cursor_has_moved) { - *sx = ctx->sx; - *sy = ctx->sy; - return true; - } } else { /* * Cursor is over a server (labwc) surface. Clear focus @@ -602,7 +601,6 @@ cursor_update_common(struct server *server, struct cursor_context *ctx, cursor_set(seat, cursor); } } - return false; } enum lab_edge @@ -669,8 +667,13 @@ cursor_process_motion(struct server *server, uint32_t time, double *sx, double * struct wlr_surface *old_focused_surface = seat->seat->pointer_state.focused_surface; - bool notify = cursor_update_common(server, &ctx, - /* cursor_has_moved */ true, sx, sy); + /* + * Cursor context that is actually interacting with cursor and should + * be notified to the client. E.g. it is cleared when menu is open, + * and the pressed view is set while out-of-surface dragging. + */ + struct cursor_context notified_ctx = {0}; + cursor_update_common(server, &ctx, ¬ified_ctx); struct wlr_surface *new_focused_surface = seat->seat->pointer_state.focused_surface; @@ -686,7 +689,9 @@ cursor_process_motion(struct server *server, uint32_t time, double *sx, double * new_focused_surface, rc.raise_on_focus); } - return notify; + *sx = notified_ctx.sx; + *sy = notified_ctx.sy; + return notified_ctx.surface; } static void @@ -705,8 +710,7 @@ _cursor_update_focus(struct server *server) ctx.surface, rc.raise_on_focus); } - double sx, sy; - cursor_update_common(server, &ctx, /*cursor_has_moved*/ false, &sx, &sy); + cursor_update_common(server, &ctx, NULL); } void @@ -1411,8 +1415,7 @@ process_cursor_axis(struct server *server, enum wl_pointer_axis orientation, /* Bindings swallow mouse events if activated */ if (ctx.surface && !consumed) { /* Make sure we are sending the events to the surface under the cursor */ - double sx, sy; - cursor_update_common(server, &ctx, /*cursor_has_moved*/ false, &sx, &sy); + cursor_update_common(server, &ctx, NULL); return true; } From e4ebc30c90dbb7779274a4f2df071a91c47026c9 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 30 Nov 2025 20:45:30 +0900 Subject: [PATCH 05/50] cursor: update focus on entering SSD if followMouse=yes This fixes a known regression in 885919fc that cursor entering the titlebar (and other SSD parts) doesn't update the keyboard focus even when followMouse=yes. --- include/labwc.h | 3 +++ src/input/cursor.c | 32 +++++++++++++++++++------------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index 999db395..62863def 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -78,6 +78,9 @@ struct seat { */ struct cursor_context_saved pressed; + /* Cursor context of the last cursor motion */ + struct cursor_context_saved last_cursor_ctx; + struct lab_set bound_buttons; struct { diff --git a/src/input/cursor.c b/src/input/cursor.c index d3221513..2a681a6e 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -664,9 +664,6 @@ cursor_process_motion(struct server *server, uint32_t time, double *sx, double * } } - struct wlr_surface *old_focused_surface = - seat->seat->pointer_state.focused_surface; - /* * Cursor context that is actually interacting with cursor and should * be notified to the client. E.g. it is cleared when menu is open, @@ -675,19 +672,28 @@ cursor_process_motion(struct server *server, uint32_t time, double *sx, double * struct cursor_context notified_ctx = {0}; cursor_update_common(server, &ctx, ¬ified_ctx); - struct wlr_surface *new_focused_surface = - seat->seat->pointer_state.focused_surface; - - if (rc.focus_follow_mouse && new_focused_surface - && old_focused_surface != new_focused_surface) { + if (rc.focus_follow_mouse) { /* - * If followMouse=yes, update the keyboard focus when the - * cursor enters a surface + * If followMouse=yes, entering a surface or view updates + * keyboard focus. Note that moving the cursor between a + * surface and a SSD within the same view doesn't update + * keyboard focus, and that entering a surface/view doesn't + * update keyboard focus if implicit grab is active. */ - desktop_focus_view_or_surface(seat, - view_from_wlr_surface(new_focused_surface), - new_focused_surface, rc.raise_on_focus); + bool entering = false; + if (notified_ctx.view) { + entering = notified_ctx.view + != seat->last_cursor_ctx.ctx.view; + } else if (notified_ctx.surface) { + entering = notified_ctx.surface + != seat->last_cursor_ctx.ctx.surface; + } + if (entering) { + desktop_focus_view_or_surface(seat, notified_ctx.view, + notified_ctx.surface, rc.raise_on_focus); + } } + cursor_context_save(&seat->last_cursor_ctx, ¬ified_ctx); *sx = notified_ctx.sx; *sy = notified_ctx.sy; From c1f3286cfe3f7cd4e54715f4ce2b8b18e168f46c Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 30 Nov 2025 20:04:25 +0900 Subject: [PATCH 06/50] src/cycle/cycle.c: remove node->enabled check before _set_enabled() wlr_scene_node_set_enabled() just returns early when it doesn't change enabled state. --- src/cycle/cycle.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index b9a72a0e..d49b5497 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -276,9 +276,7 @@ preview_selected_view(struct view *view) /* Store node enabled / minimized state and force-enable if disabled */ cycle->preview_was_enabled = cycle->preview_node->enabled; - if (!cycle->preview_was_enabled) { wlr_scene_node_set_enabled(cycle->preview_node, true); - } if (rc.window_switcher.unshade && view->shaded) { view_set_shade(view, false); cycle->preview_was_shaded = true; From 2b28c41b234bc1ada4f7db6cbaa0f960055e1090 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 30 Nov 2025 20:05:42 +0900 Subject: [PATCH 07/50] cycle: use dummy node to remember the scene position of previewed window With this approach, we only need to store cycle->dummy_node instead of cycle->preview_anchor and cycle->preview_parent and we don't need to care about annoying cases like when the previewed window has no siblings or when a window tracked by cycle->preview_anchor is destroyed. --- include/labwc.h | 3 +-- src/cycle/cycle.c | 47 +++++++++++++---------------------------------- 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index 62863def..abe07a49 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -306,8 +306,7 @@ struct server { bool preview_was_shaded; bool preview_was_enabled; struct wlr_scene_node *preview_node; - struct wlr_scene_tree *preview_parent; - struct wlr_scene_node *preview_anchor; + struct wlr_scene_node *preview_dummy; struct lab_scene_rect *preview_outline; } cycle; diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index d49b5497..306607dd 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -133,17 +133,6 @@ cycle_on_view_destroy(struct view *view) destroy_osd_scenes(server); update_cycle(server); } - - if (view->scene_tree) { - struct wlr_scene_node *node = &view->scene_tree->node; - if (cycle->preview_anchor == node) { - /* - * If we are the anchor for the current OSD selected view, - * replace the anchor with the node before us. - */ - cycle->preview_anchor = lab_wlr_scene_get_prev_node(node); - } - } } void @@ -161,15 +150,10 @@ restore_preview_node(struct server *server) { if (server->cycle.preview_node) { wlr_scene_node_reparent(server->cycle.preview_node, - server->cycle.preview_parent); - - if (server->cycle.preview_anchor) { - wlr_scene_node_place_above(server->cycle.preview_node, - server->cycle.preview_anchor); - } else { - /* Selected view was the first node */ - wlr_scene_node_lower_to_bottom(server->cycle.preview_node); - } + server->cycle.preview_dummy->parent); + wlr_scene_node_place_above(server->cycle.preview_node, + server->cycle.preview_dummy); + wlr_scene_node_destroy(server->cycle.preview_dummy); /* Node was disabled / minimized before, disable again */ if (!server->cycle.preview_was_enabled) { @@ -180,8 +164,7 @@ restore_preview_node(struct server *server) view_set_shade(view, true); } server->cycle.preview_node = NULL; - server->cycle.preview_parent = NULL; - server->cycle.preview_anchor = NULL; + server->cycle.preview_dummy = NULL; server->cycle.preview_was_shaded = false; } } @@ -227,7 +210,7 @@ cycle_finish(struct server *server, bool switch_focus) struct view *selected_view = server->cycle.selected_view; server->cycle.preview_node = NULL; - server->cycle.preview_anchor = NULL; + server->cycle.preview_dummy = NULL; server->cycle.selected_view = NULL; server->cycle.preview_was_shaded = false; @@ -261,22 +244,18 @@ preview_selected_view(struct view *view) /* Move previous selected node back to its original place */ restore_preview_node(server); - /* Store some pointers so we can reset the preview later on */ cycle->preview_node = &view->scene_tree->node; - cycle->preview_parent = view->scene_tree->node.parent; - /* Remember the sibling right before the selected node */ - cycle->preview_anchor = lab_wlr_scene_get_prev_node( - cycle->preview_node); - while (cycle->preview_anchor && !cycle->preview_anchor->data) { - /* Ignore non-view nodes */ - cycle->preview_anchor = lab_wlr_scene_get_prev_node( - cycle->preview_anchor); - } + /* Create a dummy node at the original place of the previewed window */ + struct wlr_scene_rect *dummy_rect = wlr_scene_rect_create( + cycle->preview_node->parent, 0, 0, (float [4]) {0}); + wlr_scene_node_place_below(&dummy_rect->node, cycle->preview_node); + wlr_scene_node_set_enabled(&dummy_rect->node, false); + cycle->preview_dummy = &dummy_rect->node; /* Store node enabled / minimized state and force-enable if disabled */ cycle->preview_was_enabled = cycle->preview_node->enabled; - wlr_scene_node_set_enabled(cycle->preview_node, true); + wlr_scene_node_set_enabled(cycle->preview_node, true); if (rc.window_switcher.unshade && view->shaded) { view_set_shade(view, false); cycle->preview_was_shaded = true; From 7527717caa61cd64135f9bf3fb35150b2f36ffd0 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 30 Nov 2025 22:03:56 +0900 Subject: [PATCH 08/50] cycle: move & rename destroy_osd_scenes() --- src/cycle/cycle.c | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 306607dd..cb329150 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -18,23 +18,7 @@ #include "view.h" static void update_cycle(struct server *server); - -static void -destroy_osd_scenes(struct server *server) -{ - struct output *output; - wl_list_for_each(output, &server->outputs, link) { - struct cycle_osd_item *item, *tmp; - wl_list_for_each_safe(item, tmp, &output->cycle_osd.items, link) { - wl_list_remove(&item->link); - free(item); - } - if (output->cycle_osd.tree) { - wlr_scene_node_destroy(&output->cycle_osd.tree->node); - output->cycle_osd.tree = NULL; - } - } -} +static void destroy_cycle(struct server *server); static void update_preview_outlines(struct view *view) @@ -130,7 +114,7 @@ cycle_on_view_destroy(struct view *view) if (cycle->selected_view) { /* Recreate the OSD to reflect the view has now gone. */ - destroy_osd_scenes(server); + destroy_cycle(server); update_cycle(server); } } @@ -214,7 +198,7 @@ cycle_finish(struct server *server, bool switch_focus) server->cycle.selected_view = NULL; server->cycle.preview_was_shaded = false; - destroy_osd_scenes(server); + destroy_cycle(server); if (server->cycle.preview_outline) { /* Destroy the whole multi_rect so we can easily react to new themes */ @@ -351,3 +335,20 @@ update_cycle(struct server *server) out: wl_array_release(&views); } + +static void +destroy_cycle(struct server *server) +{ + struct output *output; + wl_list_for_each(output, &server->outputs, link) { + struct cycle_osd_item *item, *tmp; + wl_list_for_each_safe(item, tmp, &output->cycle_osd.items, link) { + wl_list_remove(&item->link); + free(item); + } + if (output->cycle_osd.tree) { + wlr_scene_node_destroy(&output->cycle_osd.tree->node); + output->cycle_osd.tree = NULL; + } + } +} From 1783b805e1387eaea78e3402c21f268f1b854116 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 30 Nov 2025 22:07:18 +0900 Subject: [PATCH 09/50] cycle: factor out get_osd_impl() --- src/cycle/cycle.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index cb329150..9d5750d4 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -257,18 +257,29 @@ preview_selected_view(struct view *view) wlr_scene_node_raise_to_top(cycle->preview_node); } +static struct cycle_osd_impl * +get_osd_impl(void) +{ + switch (rc.window_switcher.style) { + case CYCLE_OSD_STYLE_CLASSIC: + return &cycle_osd_classic_impl; + case CYCLE_OSD_STYLE_THUMBNAIL: + return &cycle_osd_thumbnail_impl; + } + return NULL; +} + static void -update_osd_on_output(struct server *server, struct output *output, - struct cycle_osd_impl *osd_impl, struct wl_array *views) +update_osd_on_output(struct output *output, struct wl_array *views) { if (!output_is_usable(output)) { return; } if (!output->cycle_osd.tree) { - osd_impl->create(output, views); + get_osd_impl()->create(output, views); assert(output->cycle_osd.tree); } - osd_impl->update(output); + get_osd_impl()->update(output); } static void @@ -278,16 +289,6 @@ update_cycle(struct server *server) wl_array_init(&views); view_array_append(server, &views, rc.window_switcher.criteria); - struct cycle_osd_impl *osd_impl = NULL; - switch (rc.window_switcher.style) { - case CYCLE_OSD_STYLE_CLASSIC: - osd_impl = &cycle_osd_classic_impl; - break; - case CYCLE_OSD_STYLE_THUMBNAIL: - osd_impl = &cycle_osd_thumbnail_impl; - break; - } - if (!wl_array_len(&views) || !server->cycle.selected_view) { cycle_finish(server, /*switch_focus*/ false); goto out; @@ -299,13 +300,12 @@ update_cycle(struct server *server) case CYCLE_OSD_OUTPUT_ALL: { struct output *output; wl_list_for_each(output, &server->outputs, link) { - update_osd_on_output(server, output, osd_impl, &views); + update_osd_on_output(output, &views); } break; } case CYCLE_OSD_OUTPUT_POINTER: - update_osd_on_output(server, - output_nearest_to_cursor(server), osd_impl, &views); + update_osd_on_output(output_nearest_to_cursor(server), &views); break; case CYCLE_OSD_OUTPUT_KEYBOARD: { struct output *output; @@ -315,7 +315,7 @@ update_cycle(struct server *server) /* Fallback to pointer, if there is no active_view */ output = output_nearest_to_cursor(server); } - update_osd_on_output(server, output, osd_impl, &views); + update_osd_on_output(output, &views); break; } } From b6c1a9ea5922f6c86bc21bdaab8bc1c58edfa346 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 30 Nov 2025 22:10:58 +0900 Subject: [PATCH 10/50] cycle: clarify the lifecycle of window switcher This commit clarifies the lifecycle of the window switcher (cycle) by: - init_cycle(): initializes the window switcher (e.g. OSD). - update_cycle(): updates the window switcher states including OSD, preview and outlines. - destroy_cycle(): clears all the window switcher states. This commit temporarily regresses by not trying to preserve the selected view when a view is destroyed. This will be addressed in the next commit. --- include/cycle.h | 4 +- src/cycle/cycle.c | 121 +++++++++++++++++++++++----------------------- src/view.c | 4 +- 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/include/cycle.h b/include/cycle.h index d7d20991..d4ac72fc 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -56,8 +56,8 @@ void cycle_step(struct server *server, enum lab_cycle_dir direction); /* Closes the OSD */ void cycle_finish(struct server *server, bool switch_focus); -/* Notify OSD about a destroying view */ -void cycle_on_view_destroy(struct view *view); +/* Re-initialize the window switcher */ +void cycle_reinitialize(struct server *server); /* Focus the clicked window and close OSD */ void cycle_on_cursor_release(struct server *server, struct wlr_scene_node *node); diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 9d5750d4..85d37faa 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -17,6 +17,7 @@ #include "theme.h" #include "view.h" +static bool init_cycle(struct server *server); static void update_cycle(struct server *server); static void destroy_cycle(struct server *server); @@ -80,10 +81,8 @@ get_next_selected_view(struct server *server, struct view *start_view, } void -cycle_on_view_destroy(struct view *view) +cycle_reinitialize(struct server *server) { - assert(view); - struct server *server = view->server; struct cycle_state *cycle = &server->cycle; if (server->input_mode != LAB_INPUT_STATE_CYCLE) { @@ -91,31 +90,15 @@ cycle_on_view_destroy(struct view *view) return; } - if (cycle->selected_view == view) { - /* - * If we are the current OSD selected view, cycle - * to the next because we are dying. - */ - - /* Also resets preview node */ - cycle->selected_view = get_next_selected_view(server, - cycle->selected_view, LAB_CYCLE_DIR_BACKWARD); - - /* - * If we cycled back to ourselves, then we have no more windows. - * Just close the OSD for good. - */ - if (cycle->selected_view == view - || !cycle->selected_view) { - /* cycle_finish() additionally resets selected_view to NULL */ - cycle_finish(server, /*switch_focus*/ false); - } - } - - if (cycle->selected_view) { - /* Recreate the OSD to reflect the view has now gone. */ - destroy_cycle(server); + destroy_cycle(server); + if (init_cycle(server)) { + /* TODO: try to select the same view */ + cycle->selected_view = get_next_selected_view(server, NULL, + LAB_CYCLE_DIR_FORWARD); update_cycle(server); + } else { + /* Failed to re-init window switcher, exit */ + cycle_finish(server, /*switch_focus*/ false); } } @@ -149,6 +132,7 @@ restore_preview_node(struct server *server) } server->cycle.preview_node = NULL; server->cycle.preview_dummy = NULL; + server->cycle.preview_was_enabled = false; server->cycle.preview_was_shaded = false; } } @@ -160,6 +144,10 @@ cycle_begin(struct server *server, enum lab_cycle_dir direction) return; } + if (!init_cycle(server)) { + return; + } + server->cycle.selected_view = get_next_selected_view(server, server->cycle.selected_view, direction); @@ -188,23 +176,11 @@ cycle_finish(struct server *server, bool switch_focus) return; } - restore_preview_node(server); - /* FIXME: this sets focus to the old surface even with switch_focus=true */ - seat_focus_override_end(&server->seat); - struct view *selected_view = server->cycle.selected_view; - server->cycle.preview_node = NULL; - server->cycle.preview_dummy = NULL; - server->cycle.selected_view = NULL; - server->cycle.preview_was_shaded = false; - destroy_cycle(server); - if (server->cycle.preview_outline) { - /* Destroy the whole multi_rect so we can easily react to new themes */ - wlr_scene_node_destroy(&server->cycle.preview_outline->tree->node); - server->cycle.preview_outline = NULL; - } + /* FIXME: this sets focus to the old surface even with switch_focus=true */ + seat_focus_override_end(&server->seat); /* Hiding OSD may need a cursor change */ cursor_update_focus(server); @@ -270,42 +246,40 @@ get_osd_impl(void) } static void -update_osd_on_output(struct output *output, struct wl_array *views) +create_osd_on_output(struct output *output, struct wl_array *views) { if (!output_is_usable(output)) { return; } - if (!output->cycle_osd.tree) { - get_osd_impl()->create(output, views); - assert(output->cycle_osd.tree); - } - get_osd_impl()->update(output); + get_osd_impl()->create(output, views); + assert(output->cycle_osd.tree); } -static void -update_cycle(struct server *server) +/* Return false on failure */ +static bool +init_cycle(struct server *server) { struct wl_array views; wl_array_init(&views); view_array_append(server, &views, rc.window_switcher.criteria); - - if (!wl_array_len(&views) || !server->cycle.selected_view) { - cycle_finish(server, /*switch_focus*/ false); - goto out; + if (wl_array_len(&views) <= 0) { + wlr_log(WLR_DEBUG, "no views to switch between"); + wl_array_release(&views); + return false; } if (rc.window_switcher.show) { - /* Display the actual OSD */ + /* Create OSD */ switch (rc.window_switcher.output_criteria) { case CYCLE_OSD_OUTPUT_ALL: { struct output *output; wl_list_for_each(output, &server->outputs, link) { - update_osd_on_output(output, &views); + create_osd_on_output(output, &views); } break; } case CYCLE_OSD_OUTPUT_POINTER: - update_osd_on_output(output_nearest_to_cursor(server), &views); + create_osd_on_output(output_nearest_to_cursor(server), &views); break; case CYCLE_OSD_OUTPUT_KEYBOARD: { struct output *output; @@ -315,14 +289,32 @@ update_cycle(struct server *server) /* Fallback to pointer, if there is no active_view */ output = output_nearest_to_cursor(server); } - update_osd_on_output(output, &views); + create_osd_on_output(output, &views); break; } } } + wl_array_release(&views); + return true; +} + +static void +update_cycle(struct server *server) +{ + struct cycle_state *cycle = &server->cycle; + + if (rc.window_switcher.show) { + struct output *output; + wl_list_for_each(output, &server->outputs, link) { + if (output->cycle_osd.tree) { + get_osd_impl()->update(output); + } + } + } + if (rc.window_switcher.preview) { - preview_selected_view(server->cycle.selected_view); + preview_selected_view(cycle->selected_view); } /* Outline current window */ @@ -331,11 +323,9 @@ update_cycle(struct server *server) update_preview_outlines(server->cycle.selected_view); } } - -out: - wl_array_release(&views); } +/* Resets all the states in server->cycle */ static void destroy_cycle(struct server *server) { @@ -351,4 +341,13 @@ destroy_cycle(struct server *server) output->cycle_osd.tree = NULL; } } + + restore_preview_node(server); + + if (server->cycle.preview_outline) { + wlr_scene_node_destroy(&server->cycle.preview_outline->tree->node); + server->cycle.preview_outline = NULL; + } + + server->cycle.selected_view = NULL; } diff --git a/src/view.c b/src/view.c index 473a1b37..d56c3fea 100644 --- a/src/view.c +++ b/src/view.c @@ -2616,7 +2616,9 @@ view_destroy(struct view *view) zfree(view->tiled_region_evacuate); } - cycle_on_view_destroy(view); + /* TODO: call this on map/unmap instead */ + cycle_reinitialize(server); + undecorate(view); view_set_icon(view, NULL, NULL); From 9f5ff391ccf37a3ad5be33a08f8ff9df161c7ba9 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 30 Nov 2025 17:47:40 +0900 Subject: [PATCH 11/50] cycle: remember cycled window list in server->cycle.views This allows changing the cycled order in the future, e.g. focused order vs created order. Functionally, this commit also changes the initially selected window; before this commit, the previous/next of the topmost window was always selected, but now the previous/next of the active window is selected first if it is in the cycled list. This won't change behaviors for most users, but this ensures that the user can go back to the focused window with Alt-Tab + Alt-Shift-Tab even when it is not the topmost window. This commit fixes the TODO in the previous commit by trying to preserve the selected view when a view is destroyed during window cycling. --- include/cycle.h | 2 +- include/labwc.h | 1 + include/view.h | 12 +--- src/cycle/cycle.c | 114 +++++++++++++++++++++++--------------- src/cycle/osd-classic.c | 18 +++--- src/cycle/osd-thumbnail.c | 12 ++-- src/server.c | 1 + src/view.c | 42 -------------- 8 files changed, 92 insertions(+), 110 deletions(-) diff --git a/include/cycle.h b/include/cycle.h index d4ac72fc..aaecff50 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -87,7 +87,7 @@ struct cycle_osd_impl { * Create a scene-tree of OSD for an output. * This sets output->cycle_osd.{items,tree}. */ - void (*create)(struct output *output, struct wl_array *views); + void (*create)(struct output *output); /* * Update output->cycle_osd.tree to highlight * server->cycle_state.selected_view. diff --git a/include/labwc.h b/include/labwc.h index abe07a49..40bff876 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -303,6 +303,7 @@ struct server { /* Set when in cycle (alt-tab) mode */ struct cycle_state { struct view *selected_view; + struct wl_list views; bool preview_was_shaded; bool preview_was_enabled; struct wlr_scene_node *preview_node; diff --git a/include/view.h b/include/view.h index 9ad11cd9..d8c67e42 100644 --- a/include/view.h +++ b/include/view.h @@ -134,6 +134,9 @@ struct view { const struct view_impl *impl; struct wl_list link; + /* This is cleared when the view is not in the cycle list */ + struct wl_list cycle_link; + /* * The primary output that the view is displayed on. Specifically: * @@ -384,15 +387,6 @@ struct view *view_next(struct wl_list *head, struct view *view, struct view *view_prev(struct wl_list *head, struct view *view, enum lab_view_criteria criteria); -/* - * Same as `view_next()` except that they iterate one whole cycle rather than - * stopping at the list-head - */ -struct view *view_next_no_head_stop(struct wl_list *head, struct view *from, - enum lab_view_criteria criteria); -struct view *view_prev_no_head_stop(struct wl_list *head, struct view *from, - enum lab_view_criteria criteria); - /** * view_array_append() - Append views that match criteria to array * @server: server context diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 85d37faa..292e6436 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -4,8 +4,8 @@ #include #include #include -#include "common/array.h" #include "common/lab-scene-rect.h" +#include "common/list.h" #include "common/scene-helpers.h" #include "config/rcxml.h" #include "labwc.h" @@ -48,36 +48,36 @@ update_preview_outlines(struct view *view) wlr_scene_node_set_position(&rect->tree->node, geo.x, geo.y); } -/* - * Returns the view to select next in the window switcher. - * If !start_view, the second focusable view is returned. - */ +/* Returns the view to select next in the window switcher. */ static struct view * -get_next_selected_view(struct server *server, struct view *start_view, - enum lab_cycle_dir dir) +get_next_selected_view(struct server *server, enum lab_cycle_dir dir) { - struct view *(*iter)(struct wl_list *head, struct view *view, - enum lab_view_criteria criteria); - bool forwards = dir == LAB_CYCLE_DIR_FORWARD; - iter = forwards ? view_next_no_head_stop : view_prev_no_head_stop; + struct cycle_state *cycle = &server->cycle; + assert(cycle->selected_view); + assert(!wl_list_empty(&server->cycle.views)); - enum lab_view_criteria criteria = rc.window_switcher.criteria; - - /* - * Views are listed in stacking order, topmost first. Usually the - * topmost view is already focused, so when iterating in the forward - * direction we pre-select the view second from the top: - * - * View #1 (on top, currently focused) - * View #2 (pre-selected) - * View #3 - * ... - */ - if (!start_view && forwards) { - start_view = iter(&server->views, NULL, criteria); + struct wl_list *link; + if (dir == LAB_CYCLE_DIR_FORWARD) { + link = cycle->selected_view->cycle_link.next; + if (link == &server->cycle.views) { + link = link->next; + } + } else { + link = cycle->selected_view->cycle_link.prev; + if (link == &server->cycle.views) { + link = link->prev; + } } + struct view *view = wl_container_of(link, view, cycle_link); + return view; +} - return iter(&server->views, start_view, criteria); +static struct view * +get_first_view(struct wl_list *views) +{ + assert(!wl_list_empty(views)); + struct view *view = wl_container_of(views->next, view, cycle_link); + return view; } void @@ -90,11 +90,25 @@ cycle_reinitialize(struct server *server) return; } + struct view *selected_view = cycle->selected_view; + struct view *selected_view_prev = + get_next_selected_view(server, LAB_CYCLE_DIR_BACKWARD); + destroy_cycle(server); if (init_cycle(server)) { - /* TODO: try to select the same view */ - cycle->selected_view = get_next_selected_view(server, NULL, - LAB_CYCLE_DIR_FORWARD); + /* + * Preserve the selected view (or its previous view) if it's + * still in the cycle list + */ + if (selected_view->cycle_link.next) { + cycle->selected_view = selected_view; + } else if (selected_view_prev->cycle_link.next) { + cycle->selected_view = selected_view_prev; + } else { + /* should be unreachable */ + wlr_log(WLR_ERROR, "could not find view to select"); + cycle->selected_view = get_first_view(&server->cycle.views); + } update_cycle(server); } else { /* Failed to re-init window switcher, exit */ @@ -148,8 +162,16 @@ cycle_begin(struct server *server, enum lab_cycle_dir direction) return; } - server->cycle.selected_view = get_next_selected_view(server, - server->cycle.selected_view, direction); + struct view *active_view = server->active_view; + if (active_view && active_view->cycle_link.next) { + /* Select the active view it's in the cycle list */ + server->cycle.selected_view = active_view; + } else { + /* Otherwise, select the first view in the cycle list */ + server->cycle.selected_view = get_first_view(&server->cycle.views); + } + /* Pre-select the next view in the given direction */ + server->cycle.selected_view = get_next_selected_view(server, direction); seat_focus_override_begin(&server->seat, LAB_INPUT_STATE_CYCLE, LAB_CURSOR_DEFAULT); @@ -164,8 +186,7 @@ cycle_step(struct server *server, enum lab_cycle_dir direction) { assert(server->input_mode == LAB_INPUT_STATE_CYCLE); - server->cycle.selected_view = get_next_selected_view(server, - server->cycle.selected_view, direction); + server->cycle.selected_view = get_next_selected_view(server, direction); update_cycle(server); } @@ -246,12 +267,12 @@ get_osd_impl(void) } static void -create_osd_on_output(struct output *output, struct wl_array *views) +create_osd_on_output(struct output *output) { if (!output_is_usable(output)) { return; } - get_osd_impl()->create(output, views); + get_osd_impl()->create(output); assert(output->cycle_osd.tree); } @@ -259,12 +280,12 @@ create_osd_on_output(struct output *output, struct wl_array *views) static bool init_cycle(struct server *server) { - struct wl_array views; - wl_array_init(&views); - view_array_append(server, &views, rc.window_switcher.criteria); - if (wl_array_len(&views) <= 0) { + struct view *view; + for_each_view(view, &server->views, rc.window_switcher.criteria) { + wl_list_append(&server->cycle.views, &view->cycle_link); + } + if (wl_list_empty(&server->cycle.views)) { wlr_log(WLR_DEBUG, "no views to switch between"); - wl_array_release(&views); return false; } @@ -274,12 +295,12 @@ init_cycle(struct server *server) case CYCLE_OSD_OUTPUT_ALL: { struct output *output; wl_list_for_each(output, &server->outputs, link) { - create_osd_on_output(output, &views); + create_osd_on_output(output); } break; } case CYCLE_OSD_OUTPUT_POINTER: - create_osd_on_output(output_nearest_to_cursor(server), &views); + create_osd_on_output(output_nearest_to_cursor(server)); break; case CYCLE_OSD_OUTPUT_KEYBOARD: { struct output *output; @@ -289,13 +310,12 @@ init_cycle(struct server *server) /* Fallback to pointer, if there is no active_view */ output = output_nearest_to_cursor(server); } - create_osd_on_output(output, &views); + create_osd_on_output(output); break; } } } - wl_array_release(&views); return true; } @@ -349,5 +369,11 @@ destroy_cycle(struct server *server) server->cycle.preview_outline = NULL; } + struct view *view, *tmp; + wl_list_for_each_safe(view, tmp, &server->cycle.views, cycle_link) { + wl_list_remove(&view->cycle_link); + view->cycle_link = (struct wl_list){0}; + } + server->cycle.selected_view = NULL; } diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index f954701e..67cfb8db 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -4,11 +4,11 @@ #include #include #include -#include "common/array.h" #include "common/buf.h" #include "common/font.h" #include "common/lab-scene-rect.h" #include "common/list.h" +#include "common/mem.h" #include "common/string-helpers.h" #include "config/rcxml.h" #include "cycle.h" @@ -18,6 +18,7 @@ #include "scaled-buffer/scaled-font-buffer.h" #include "scaled-buffer/scaled-icon-buffer.h" #include "theme.h" +#include "view.h" #include "workspaces.h" struct cycle_osd_classic_item { @@ -76,7 +77,7 @@ create_fields_scene(struct server *server, struct view *view, } static void -cycle_osd_classic_create(struct output *output, struct wl_array *views) +cycle_osd_classic_create(struct output *output) { assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); @@ -87,6 +88,7 @@ cycle_osd_classic_create(struct output *output, struct wl_array *views) int padding = theme->osd_border_width + switcher_theme->padding; bool show_workspace = wl_list_length(&rc.workspace_config.workspaces) > 1; const char *workspace_name = server->workspaces.current->name; + int nr_views = wl_list_length(&server->cycle.views); struct wlr_box output_box; wlr_output_layout_get_box(server->output_layout, output->wlr_output, @@ -96,7 +98,7 @@ cycle_osd_classic_create(struct output *output, struct wl_array *views) if (switcher_theme->width_is_percent) { w = output_box.width * switcher_theme->width / 100; } - int h = wl_array_len(views) * switcher_theme->item_height + 2 * padding; + int h = nr_views * switcher_theme->item_height + 2 * padding; if (show_workspace) { /* workspace indicator */ h += switcher_theme->item_height; @@ -155,11 +157,11 @@ cycle_osd_classic_create(struct output *output, struct wl_array *views) } /* Draw text for each node */ - struct view **view; - wl_array_for_each(view, views) { + struct view *view; + wl_list_for_each(view, &server->cycle.views, cycle_link) { struct cycle_osd_classic_item *item = znew(*item); wl_list_append(&output->cycle_osd.items, &item->base.link); - item->base.view = *view; + item->base.view = view; item->base.tree = wlr_scene_tree_create(output->cycle_osd.tree); node_descriptor_create(&item->base.tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item); @@ -207,9 +209,9 @@ cycle_osd_classic_create(struct output *output, struct wl_array *views) w - 2 * padding, switcher_theme->item_height, (float[4]) {0}); wlr_scene_node_set_position(&hitbox->node, padding, y); - create_fields_scene(server, *view, item->normal_tree, + 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, + create_fields_scene(server, view, item->active_tree, text_color, active_bg_color, field_widths_sum, x, y); y += switcher_theme->item_height; diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index 5dd157c1..2245d1ad 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -5,11 +5,11 @@ #include #include #include "config/rcxml.h" -#include "common/array.h" #include "common/box.h" #include "common/buf.h" #include "common/lab-scene-rect.h" #include "common/list.h" +#include "common/mem.h" #include "cycle.h" #include "labwc.h" #include "node.h" @@ -226,7 +226,7 @@ get_items_geometry(struct output *output, struct theme *theme, } static void -cycle_osd_thumbnail_create(struct output *output, struct wl_array *views) +cycle_osd_thumbnail_create(struct output *output) { assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); @@ -238,17 +238,17 @@ cycle_osd_thumbnail_create(struct output *output, struct wl_array *views) output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree); - int nr_views = wl_array_len(views); + int nr_views = wl_list_length(&server->cycle.views); assert(nr_views > 0); int nr_rows, nr_cols; get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols); /* items */ - struct view **view; + struct view *view; int index = 0; - wl_array_for_each(view, views) { + wl_list_for_each(view, &server->cycle.views, cycle_link) { struct cycle_osd_thumbnail_item *item = create_item_scene( - output->cycle_osd.tree, *view, output); + output->cycle_osd.tree, view, output); if (!item) { break; } diff --git a/src/server.c b/src/server.c index 11037a48..9f271b10 100644 --- a/src/server.c +++ b/src/server.c @@ -549,6 +549,7 @@ server_init(struct server *server) wl_list_init(&server->views); wl_list_init(&server->unmanaged_surfaces); + wl_list_init(&server->cycle.views); server->scene = wlr_scene_create(); if (!server->scene) { diff --git a/src/view.c b/src/view.c index d56c3fea..cc16536f 100644 --- a/src/view.c +++ b/src/view.c @@ -345,48 +345,6 @@ view_prev(struct wl_list *head, struct view *view, enum lab_view_criteria criter return NULL; } -struct view * -view_next_no_head_stop(struct wl_list *head, struct view *from, - enum lab_view_criteria criteria) -{ - assert(head); - - struct wl_list *elm = from ? &from->link : head; - - struct wl_list *end = elm; - for (elm = elm->next; elm != end; elm = elm->next) { - if (elm == head) { - continue; - } - struct view *view = wl_container_of(elm, view, link); - if (matches_criteria(view, criteria)) { - return view; - } - } - return from; -} - -struct view * -view_prev_no_head_stop(struct wl_list *head, struct view *from, - enum lab_view_criteria criteria) -{ - assert(head); - - struct wl_list *elm = from ? &from->link : head; - - struct wl_list *end = elm; - for (elm = elm->prev; elm != end; elm = elm->prev) { - if (elm == head) { - continue; - } - struct view *view = wl_container_of(elm, view, link); - if (matches_criteria(view, criteria)) { - return view; - } - } - return from; -} - void view_array_append(struct server *server, struct wl_array *views, enum lab_view_criteria criteria) From 5ea617a39353c5a2dd9089c76e7d2e3b9b7cd1b7 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Tue, 25 Nov 2025 23:25:47 -0500 Subject: [PATCH 12/50] box: factor out box_center() --- include/common/box.h | 11 +++++++++++ src/common/box.c | 19 +++++++++++++++++++ src/view.c | 18 +----------------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/include/common/box.h b/include/common/box.h index 45c0fc4d..9f18b45e 100644 --- a/include/common/box.h +++ b/include/common/box.h @@ -10,6 +10,17 @@ bool box_intersects(struct wlr_box *box_a, struct wlr_box *box_b); void box_union(struct wlr_box *box_dest, struct wlr_box *box_a, struct wlr_box *box_b); +/* + * Centers a content box (width & height) within a reference box, + * limiting it (if possible) to not extend outside a bounding box. + * + * The reference box and bounding box are often the same but could be + * different (e.g. when centering a view within its parent but limiting + * to usable output area). + */ +void box_center(int width, int height, const struct wlr_box *ref, + const struct wlr_box *bound, int *x, int *y); + /* * Fits and centers a content box (width & height) within a bounding box. * The content box is downscaled if necessary (preserving aspect ratio) but diff --git a/src/common/box.c b/src/common/box.c index 2520f733..5cc91bf3 100644 --- a/src/common/box.c +++ b/src/common/box.c @@ -35,6 +35,25 @@ box_union(struct wlr_box *box_dest, struct wlr_box *box_a, struct wlr_box *box_b box_dest->height = y2 - y1; } +void +box_center(int width, int height, const struct wlr_box *ref, + const struct wlr_box *bound, int *x, int *y) +{ + *x = ref->x + (ref->width - width) / 2; + *y = ref->y + (ref->height - height) / 2; + + if (*x < bound->x) { + *x = bound->x; + } else if (*x + width > bound->x + bound->width) { + *x = bound->x + bound->width - width; + } + if (*y < bound->y) { + *y = bound->y; + } else if (*y + height > bound->y + bound->height) { + *y = bound->y + bound->height - height; + } +} + struct wlr_box box_fit_within(int width, int height, struct wlr_box *bound) { diff --git a/src/view.c b/src/view.c index cc16536f..57014b8d 100644 --- a/src/view.c +++ b/src/view.c @@ -816,23 +816,7 @@ view_compute_centered_position(struct view *view, const struct wlr_box *ref, int height = h + margin.top + margin.bottom; /* If reference box is NULL then center to usable area */ - if (!ref) { - ref = &usable; - } - *x = ref->x + (ref->width - width) / 2; - *y = ref->y + (ref->height - height) / 2; - - /* Fit the view within the usable area */ - if (*x < usable.x) { - *x = usable.x; - } else if (*x + width > usable.x + usable.width) { - *x = usable.x + usable.width - width; - } - if (*y < usable.y) { - *y = usable.y; - } else if (*y + height > usable.y + usable.height) { - *y = usable.y + usable.height - height; - } + box_center(width, height, ref ? ref : &usable, &usable, x, y); *x += margin.left; *y += margin.top; From 27aa8173f30139512ebed06cd8fbeb314fc703d3 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Mon, 1 Dec 2025 13:58:18 -0500 Subject: [PATCH 13/50] view: move view_moved() out of view_impl_apply_geometry() --- src/view-impl-common.c | 5 ----- src/xdg.c | 1 + src/xwayland.c | 1 + 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/view-impl-common.c b/src/view-impl-common.c index 8f4e2795..e5e10878 100644 --- a/src/view-impl-common.c +++ b/src/view-impl-common.c @@ -65,7 +65,6 @@ view_impl_apply_geometry(struct view *view, int w, int h) { struct wlr_box *current = &view->current; struct wlr_box *pending = &view->pending; - struct wlr_box old = *current; /* * Anchor right edge if resizing via left edge. @@ -100,8 +99,4 @@ view_impl_apply_geometry(struct view *view, int w, int h) current->width = w; current->height = h; - - if (!wlr_box_equal(current, &old)) { - view_moved(view); - } } diff --git a/src/xdg.c b/src/xdg.c index 2bb6d660..5949638e 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -238,6 +238,7 @@ handle_commit(struct wl_listener *listener, void *data) if (update_required) { view_impl_apply_geometry(view, size.width, size.height); + view_moved(view); /* * Some views (e.g., terminals that scale as multiples of rows diff --git a/src/xwayland.c b/src/xwayland.c index 91e40897..9c58f574 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -274,6 +274,7 @@ handle_commit(struct wl_listener *listener, void *data) */ if (current->width != state->width || current->height != state->height) { view_impl_apply_geometry(view, state->width, state->height); + view_moved(view); } } From 96617311cd8711a30a8972fe4eb0b40e6b5e46e9 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Sun, 30 Nov 2025 18:34:25 -0500 Subject: [PATCH 14/50] view: use wlr_output_layout_get_box() --- src/view.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/view.c b/src/view.c index 57014b8d..cf0c521c 100644 --- a/src/view.c +++ b/src/view.c @@ -1223,13 +1223,8 @@ view_apply_fullscreen_geometry(struct view *view) assert(output_is_usable(view->output)); struct wlr_box box = { 0 }; - wlr_output_effective_resolution(view->output->wlr_output, - &box.width, &box.height); - double ox = 0, oy = 0; - wlr_output_layout_output_coords(view->server->output_layout, - view->output->wlr_output, &ox, &oy); - box.x -= ox; - box.y -= oy; + wlr_output_layout_get_box(view->server->output_layout, + view->output->wlr_output, &box); view_move_resize(view, box); } From 7f7b5f57ece78d693b8a1f558385be4bcbff9be4 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Mon, 1 Dec 2025 15:13:07 -0500 Subject: [PATCH 15/50] xdg: center small fullscreen views and add black background fill --- include/view.h | 3 +++ src/view.c | 6 +++++ src/xdg.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/include/view.h b/include/view.h index d8c67e42..2f8aac85 100644 --- a/include/view.h +++ b/include/view.h @@ -296,6 +296,9 @@ struct xdg_toplevel_view { struct view base; struct wlr_xdg_surface *xdg_surface; + /* Optional black background fill behind fullscreen view */ + struct wlr_scene_rect *fullscreen_bg; + /* Events unique to xdg-toplevel views */ struct wl_listener set_app_id; struct wl_listener request_show_window_menu; diff --git a/src/view.c b/src/view.c index cf0c521c..bd8f0a64 100644 --- a/src/view.c +++ b/src/view.c @@ -1699,6 +1699,12 @@ view_set_fullscreen(struct view *view, bool fullscreen) view_apply_special_geometry(view); } output_set_has_fullscreen_view(view->output, view->fullscreen); + /* + * Entering/leaving fullscreen might result in a different + * scene node ending up under the cursor even if view_moved() + * isn't called. Update cursor focus explicitly for that case. + */ + cursor_update_focus(view->server); } static bool diff --git a/src/xdg.c b/src/xdg.c index 5949638e..b7bae379 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -10,6 +10,7 @@ #include #include "buffer.h" #include "common/array.h" +#include "common/box.h" #include "common/macros.h" #include "common/mem.h" #include "config/rcxml.h" @@ -129,6 +130,58 @@ do_late_positioning(struct view *view) } } +static void +disable_fullscreen_bg(struct view *view) +{ + struct xdg_toplevel_view *xdg_view = xdg_toplevel_view_from_view(view); + if (xdg_view->fullscreen_bg) { + wlr_scene_node_set_enabled(&xdg_view->fullscreen_bg->node, false); + } +} + +/* + * Centers any fullscreen view smaller than the full output size. + * This should be called immediately before view_moved(). + */ +static void +center_fullscreen_if_needed(struct view *view) +{ + if (!view->fullscreen || !output_is_usable(view->output)) { + disable_fullscreen_bg(view); + return; + } + + struct wlr_box output_box = {0}; + wlr_output_layout_get_box(view->server->output_layout, + view->output->wlr_output, &output_box); + box_center(view->current.width, view->current.height, &output_box, + &output_box, &view->current.x, &view->current.y); + + /* Reset pending x/y to computed position also */ + view->pending.x = view->current.x; + view->pending.y = view->current.y; + + if (view->current.width >= output_box.width + && view->current.width >= output_box.height) { + disable_fullscreen_bg(view); + return; + } + + struct xdg_toplevel_view *xdg_view = xdg_toplevel_view_from_view(view); + if (!xdg_view->fullscreen_bg) { + const float black[4] = {0, 0, 0, 1}; + xdg_view->fullscreen_bg = + wlr_scene_rect_create(view->scene_tree, 0, 0, black); + wlr_scene_node_lower_to_bottom(&xdg_view->fullscreen_bg->node); + } + + wlr_scene_node_set_position(&xdg_view->fullscreen_bg->node, + output_box.x - view->current.x, output_box.y - view->current.y); + wlr_scene_rect_set_size(xdg_view->fullscreen_bg, + output_box.width, output_box.height); + wlr_scene_node_set_enabled(&xdg_view->fullscreen_bg->node, true); +} + /* TODO: reorder so this forward declaration isn't needed */ static void set_pending_configure_serial(struct view *view, uint32_t serial); @@ -238,6 +291,7 @@ handle_commit(struct wl_listener *listener, void *data) if (update_required) { view_impl_apply_geometry(view, size.width, size.height); + center_fullscreen_if_needed(view); view_moved(view); /* @@ -336,9 +390,11 @@ handle_configure_timeout(void *data) } view->current.x = view->pending.x; view->current.y = view->pending.y; - view_moved(view); } + center_fullscreen_if_needed(view); + view_moved(view); + /* Re-sync pending view with current state */ snap_constraints_update(view); view->pending = view->current; @@ -546,6 +602,12 @@ xdg_toplevel_view_configure(struct view *view, struct wlr_box geo) } else if (view->pending_configure_serial == 0) { view->current.x = geo.x; view->current.y = geo.y; + /* + * It's a bit difficult to think of a corner case where + * center_fullscreen_if_needed() would actually be needed + * here, but including it anyway for completeness. + */ + center_fullscreen_if_needed(view); view_moved(view); } } @@ -664,6 +726,10 @@ xdg_toplevel_view_set_fullscreen(struct view *view, bool fullscreen) if (serial > 0) { set_pending_configure_serial(view, serial); } + /* Disable background fill immediately on leaving fullscreen */ + if (!fullscreen) { + disable_fullscreen_bg(view); + } } static void @@ -786,6 +852,9 @@ handle_map(struct wl_listener *listener, void *data) set_initial_position(view); } + /* Disable background fill at map (paranoid?) */ + disable_fullscreen_bg(view); + /* * Set initial "current" position directly before * calling view_moved() to reduce flicker From d20d5675f52d25411315578c0043a788f0c20346 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Mon, 1 Dec 2025 21:49:31 -0500 Subject: [PATCH 16/50] xwayland: don't send _NET_WM_PING to new windows No one remembers why we were doing this, and we do not handle the ping_timeout event, so it seems pointless. Also it appears to confuse some older X clients. --- src/xwayland.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/xwayland.c b/src/xwayland.c index 9c58f574..0fd2168e 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -1051,7 +1051,6 @@ handle_new_surface(struct wl_listener *listener, void *data) struct server *server = wl_container_of(listener, server, xwayland_new_surface); struct wlr_xwayland_surface *xsurface = data; - wlr_xwayland_surface_ping(xsurface); /* * We do not create 'views' for xwayland override_redirect surfaces, From ba60f4d5d00179385da3ed1ece01e93623cdbf41 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Mon, 1 Dec 2025 21:53:00 -0500 Subject: [PATCH 17/50] xdg: don't send xdg_wm_base::ping on creation of new surface Although this wasn't causing any known issues, here too we weren't handling the ping_timeout event, so it seems useless. --- src/xdg.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/xdg.c b/src/xdg.c index b7bae379..668dfe45 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -999,8 +999,6 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data) assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); - wlr_xdg_surface_ping(xdg_surface); - struct xdg_toplevel_view *xdg_toplevel_view = znew(*xdg_toplevel_view); struct view *view = &xdg_toplevel_view->base; From 499c9109546563baa92bd2106ece0df59968c1b8 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Tue, 2 Dec 2025 20:26:50 +0900 Subject: [PATCH 18/50] src/interactive.c: fix typo --- src/interactive.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interactive.c b/src/interactive.c index 8ca258c1..df32f46f 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -42,7 +42,7 @@ interactive_anchor_to_cursor(struct server *server, struct wlr_box *geo) if (wlr_box_empty(geo)) { return; } - /* Resize grab_box while anchoring it to grab_box.{x,y} */ + /* Resize grab_box while anchoring it to grab_{x,y} */ server->grab_box.x = max_move_scale(server->grab_x, server->grab_box.x, server->grab_box.width, geo->width); server->grab_box.y = max_move_scale(server->grab_y, server->grab_box.y, From 71d29cbebb8e8abae6692fc64d187ae258dad4c9 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 3 Dec 2025 00:36:30 +0900 Subject: [PATCH 19/50] cycle: some minor updates to documentation and deprecation warnings --- docs/labwc-config.5.scd | 2 +- src/config/rcxml.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 9b0f8291..d772edb3 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -363,7 +363,7 @@ this is for compatibility with Openbox. *unshade* [yes|no] Temporarily unshade windows when switching between them and permanently unshade on the final selection. Default is yes. -** +** *show* [yes|no] Draw the OnScreenDisplay when switching between windows. Default is yes. diff --git a/src/config/rcxml.c b/src/config/rcxml.c index dc11b1c8..ed51e20d 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1235,7 +1235,7 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(nodename, "show.windowSwitcher")) { set_bool(content, &rc.window_switcher.show); wlr_log(WLR_ERROR, " is deprecated." - " Use "); + " Use "); } else if (!strcasecmp(nodename, "style.windowSwitcher")) { if (!strcasecmp(content, "classic")) { rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; @@ -1243,7 +1243,7 @@ entry(xmlNode *node, char *nodename, char *content) rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL; } wlr_log(WLR_ERROR, " is deprecated." - " Use "); + " Use "); } else if (!strcasecmp(nodename, "preview.windowSwitcher")) { set_bool(content, &rc.window_switcher.preview); From c4277ab507d17f31dca2a2c24f5b937c750450d8 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 3 Dec 2025 00:41:04 +0900 Subject: [PATCH 20/50] cycle: update config to I think `` is a bit unclear and hard to interpret as "show OSD in the output with keyboard focus". Also, we use "cursor" instead of "pointer" in other configurations like `` and ``. So let's replace `output="all|pointer|keyboard"` with `output="all|cursor|focused"`. In documentation, I reordered them to `output="all|focused|cursor"` as "focused" feels like a bit more sophisticated and general policy. --- docs/labwc-config.5.scd | 6 +++--- include/config/types.h | 4 ++-- src/config/rcxml.c | 10 +++++----- src/cycle/cycle.c | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index d772edb3..fa4f7e58 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -371,10 +371,10 @@ this is for compatibility with Openbox. "classic" displays window information like icons and titles in a vertical list. "thumbnail" shows window thumbnail, icon and title in grids. - *output* [all|pointer|keyboard] Configures which monitor(s) show the OSD. + *output* [all|focused|cursor] Configures which monitor(s) show the OSD. "all" displays the OSD on all monitors. - "pointer" displays the OSD on the monitor containing the mouse pointer. - "keyboard" displays the OSD on the monitor with keyboard focus. + "focused" displays the OSD on the monitor with keyboard focus. + "cursor" displays the OSD on the monitor containing the mouse pointer. Default is "all". *thumbnailLabelFormat* Format to be used for the thumbnail label according to *custom* diff --git a/include/config/types.h b/include/config/types.h index 7cb9feab..757796a6 100644 --- a/include/config/types.h +++ b/include/config/types.h @@ -114,8 +114,8 @@ enum cycle_osd_style { enum cycle_osd_output_criteria { CYCLE_OSD_OUTPUT_ALL, - CYCLE_OSD_OUTPUT_POINTER, - CYCLE_OSD_OUTPUT_KEYBOARD, + CYCLE_OSD_OUTPUT_CURSOR, + CYCLE_OSD_OUTPUT_FOCUSED, }; #endif /* LABWC_CONFIG_TYPES_H */ diff --git a/src/config/rcxml.c b/src/config/rcxml.c index ed51e20d..c5d94d4f 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1222,13 +1222,13 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) { if (!strcasecmp(content, "all")) { rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL; - } else if (!strcasecmp(content, "pointer")) { - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_POINTER; - } else if (!strcasecmp(content, "keyboard")) { - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_KEYBOARD; + } else if (!strcasecmp(content, "cursor")) { + rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_CURSOR; + } else if (!strcasecmp(content, "focused")) { + rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_FOCUSED; } else { wlr_log(WLR_ERROR, "Invalid windowSwitcher output %s: " - "should be one of all|pointer|keyboard", content); + "should be one of all|focused|cursor", content); } /* The following two are for backward compatibility only. */ diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 292e6436..8eabaf6c 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -299,10 +299,10 @@ init_cycle(struct server *server) } break; } - case CYCLE_OSD_OUTPUT_POINTER: + case CYCLE_OSD_OUTPUT_CURSOR: create_osd_on_output(output_nearest_to_cursor(server)); break; - case CYCLE_OSD_OUTPUT_KEYBOARD: { + case CYCLE_OSD_OUTPUT_FOCUSED: { struct output *output; if (server->active_view) { output = server->active_view->output; From d748dc78bc0e50a5ae6abf366f19b36d02303585 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Tue, 2 Dec 2025 13:33:07 -0500 Subject: [PATCH 21/50] xwayland: flush XCB connection to mitigate race between Raise and input --- include/xwayland.h | 2 ++ src/view.c | 12 ++++++++++++ src/xwayland.c | 10 ++++++++++ 3 files changed, 24 insertions(+) diff --git a/include/xwayland.h b/include/xwayland.h index 5fa20e11..bbb9fa1c 100644 --- a/include/xwayland.h +++ b/include/xwayland.h @@ -77,5 +77,7 @@ void xwayland_update_workarea(struct server *server); void xwayland_reset_cursor(struct server *server); +void xwayland_flush(struct server *server); + #endif /* HAVE_XWAYLAND */ #endif /* LABWC_XWAYLAND_H */ diff --git a/src/view.c b/src/view.c index bd8f0a64..c439b443 100644 --- a/src/view.c +++ b/src/view.c @@ -35,6 +35,7 @@ #if HAVE_XWAYLAND #include +#include "xwayland.h" #endif struct view * @@ -2247,6 +2248,17 @@ view_move_to_front(struct view *view) move_to_front(view); } +#if HAVE_XWAYLAND + /* + * view_move_to_front() is typically called on each mouse press + * via Raise action. This means we are restacking windows just + * about at the same time we send the mouse press input to the + * X server, and creates a race where the mouse press could go + * to an incorrect X window depending on timing. To mitigate the + * race, perform an explicit flush after restacking. + */ + xwayland_flush(view->server); +#endif cursor_update_focus(view->server); desktop_update_top_layer_visibility(view->server); } diff --git a/src/xwayland.c b/src/xwayland.c index 0fd2168e..564fa3e2 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -1438,3 +1438,13 @@ xwayland_update_workarea(struct server *server) }; wlr_xwayland_set_workareas(server->xwayland, &workarea, 1); } + +void +xwayland_flush(struct server *server) +{ + if (!server->xwayland || !server->xwayland->xwm) { + return; + } + + xcb_flush(wlr_xwayland_get_xwm_connection(server->xwayland)); +} From a5db8e477aec3cb96044805d25454bd97af94342 Mon Sep 17 00:00:00 2001 From: Maik Broemme Date: Fri, 28 Nov 2025 20:51:00 +0100 Subject: [PATCH 22/50] Add directional options to `Resize` action: This introduces an optional "direction" argument to the Resize action, mirroring Fluxbox's StartResizing [corner] behavior. Supported values (case-insensitive) are: up-left, up, up-right, left, right, down-left, down, down-right. If no direction is specified, the existing behavior is preserved and the resize edges are inferred from the current pointer position. The action documentation has been updated to describe the new argument. --- docs/labwc-actions.5.scd | 6 +++++- src/action.c | 27 +++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 4a76a374..96e44cb6 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -51,9 +51,13 @@ Actions are used in menus and keyboard/mouse bindings. another window or screen edge. If set to "no", only move to the next screen edge. Default is yes. -** +** Begin interactive resize of window under cursor. + *direction* [up|down|left|right|up-left|up-right|down-left|down-right] + Edge or corner from which to start resizing. If this is not provided, + the direction is inferred from the cursor position. + ** Resize window relative to its current size. Values of left, right, top or bottom tell how much to resize on that edge of window, diff --git a/src/action.c b/src/action.c index ceeffd67..7ddba0be 100644 --- a/src/action.c +++ b/src/action.c @@ -414,6 +414,20 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char goto cleanup; } break; + case ACTION_TYPE_RESIZE: + if (!strcmp(argument, "direction")) { + enum lab_edge edge = lab_edge_parse(content, + /*tiled*/ true, /*any*/ false); + if (edge == LAB_EDGE_NONE || edge == LAB_EDGE_CENTER) { + wlr_log(WLR_ERROR, + "Invalid argument for action %s: '%s' (%s)", + action_names[action->type], argument, content); + } else { + action_arg_add_int(action, argument, edge); + } + goto cleanup; + } + break; case ACTION_TYPE_RESIZE_RELATIVE: if (!strcmp(argument, "left") || !strcmp(argument, "right") || !strcmp(argument, "top") || !strcmp(argument, "bottom")) { @@ -1223,8 +1237,17 @@ run_action(struct view *view, struct server *server, struct action *action, break; case ACTION_TYPE_RESIZE: if (view) { - enum lab_edge resize_edges = cursor_get_resize_edges( - server->seat.cursor, ctx); + /* + * If a direction was specified in the config, honour it. + * Otherwise, fall back to determining the resize edges from + * the current cursor position (existing behaviour). + */ + enum lab_edge resize_edges = + action_get_int(action, "direction", LAB_EDGE_NONE); + if (resize_edges == LAB_EDGE_NONE) { + resize_edges = cursor_get_resize_edges( + server->seat.cursor, ctx); + } interactive_begin(view, LAB_INPUT_STATE_RESIZE, resize_edges); } From 06505d24c8d3a5e5fa1d0212789aa104ff97b8ed Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 3 Dec 2025 18:26:44 +0900 Subject: [PATCH 23/50] rcxml: discourage empty strings in rc.xml configuration Background: I rewrote the config parser in 9462457..2f414a4, but it broke certain configurations by changing how empty strings are handled: they were mostly just ignored before my parser rewrite, but after that, they are interpreted as just empty strings (output="" is considered as 'output named ""'). Though that was unintentional, I believe ignoring empty strings was not a good idea in the first place, as we already allow empty strings for certain configurations (e.g. ``), which makes the parser's behavior inconsistent. Change: So let's clarify that we intend to read empty strings as empty strings. As a preparation, this commit adds warnings for empty strings we are currently ignoring, so that users can be informed that we intend to just read empty strings (e.g. ``) as empty strings in the future. I removed existing empty strings in `rc.xml.all` to avoid warnings when reading it. --- docs/rc.xml.all | 49 +++++++++++++++++++++++++--------------------- src/config/rcxml.c | 5 ++++- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 97698aac..cd73f61e 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -33,8 +33,8 @@ - - + + labwc icon:iconify,max,close @@ -218,9 +218,9 @@ space automatically, so is only intended for other, specialist cases. - If output is left empty, the margin will be applied to all outputs. + If 'output' is not provided, the margin will be applied to all outputs. - + --> @@ -525,7 +525,11 @@ If mouseEmulation is enabled, all touch up/down/motion events are translated to mouse button and motion events. --> - + + + + no + - + + @@ -586,7 +591,7 @@ - sendEventsMode [yes|no|disabledOnExternalMouse] - calibrationMatrix [six float values split by space] - scrollFactor [float] - + The following ... block may not be complete for your requirements. Default values are device specific. Only set an option if you require to override the default. Valid values must be inserted. @@ -595,21 +600,21 @@ - - - - + + + + yes - - - - - - - - - - + + + + + + + + + + 1.0 @@ -678,7 +683,7 @@ 400 2.0 0.2 - true + yes diff --git a/src/config/rcxml.c b/src/config/rcxml.c index c5d94d4f..84054e82 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -714,6 +714,8 @@ fill_libinput_category(xmlNode *node) char *key, *content; LAB_XML_FOR_EACH(node, child, key, content) { if (string_null_or_empty(content)) { + wlr_log(WLR_ERROR, "Empty string is not allowed for " + "<%s>. Ignoring.", key); continue; } if (!strcmp(key, "category")) { @@ -1076,7 +1078,8 @@ entry(xmlNode *node, char *nodename, char *content) return true; } else if (str_space_only(content)) { - /* ignore empty leaf nodes other than above */ + wlr_log(WLR_ERROR, "Empty string is not allowed for %s. " + "Ignoring.", nodename); /* handle non-empty leaf nodes */ } else if (!strcmp(nodename, "decoration.core")) { From 4d47b68aae8bf326095da9086373c6d78656c019 Mon Sep 17 00:00:00 2001 From: Standreas Date: Fri, 5 Dec 2025 19:46:18 +0100 Subject: [PATCH 24/50] docs/rc.xml.all: add scrollMethod option --- docs/rc.xml.all | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rc.xml.all b/docs/rc.xml.all index cd73f61e..13f4c8df 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -588,6 +588,7 @@ - accelProfile [flat|adaptive] - tapButtonMap [lrm|lmr] - clickMethod [none|buttonAreas|clickfinger] + - scrollMethod [twoFinger|edge|none] - sendEventsMode [yes|no|disabledOnExternalMouse] - calibrationMatrix [six float values split by space] - scrollFactor [float] From 94d33f91197bc7e7061b8b775d6a71a78517d1ff Mon Sep 17 00:00:00 2001 From: elviosak <33790211+elviosak@users.noreply.github.com> Date: Sat, 6 Dec 2025 04:09:28 -0300 Subject: [PATCH 25/50] snapping: replace with (#3241) / configure the size of snapping area on output edges with/without adjacent outputs. --- docs/labwc-config.5.scd | 15 +++++++++------ docs/rc.xml.all | 4 ++-- include/config/rcxml.h | 3 ++- src/config/rcxml.c | 12 ++++++++++-- src/interactive.c | 34 ++++++++++++++++++++++++++++------ 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index fa4f7e58..74442362 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -498,13 +498,16 @@ activated with SnapToEdge actions or, optionally, by dragging windows to the edges of an output. Edge snapping causes a window to occupy half of its output, extending outward from the snapped edge. -**++ +**++ +**++ ** - If an interactive move ends with the cursor within ** pixels of an - output edge, the window is snapped to the edge. If it's also within - ** pixels of an output corner, the window is snapped to the - corner instead. A ** of 0 disables snapping. - Default is 10 for ** and 50 for **. + If an interactive move ends with the cursor within *inner* or *outer* pixels + of an output edge, the window is snapped to the edge. *inner* edges are edges + with an adjacent output and *outer* edges are edges without an adjacent output. + If it's also within ** pixels of an output corner, the window is + snapped to the corner instead. + If *inner* and *outer* is 0, snapping is disabled. + Default is 10 for ** and **, and 50 for **. ** [yes|no] Show an overlay when snapping to a window to an edge. Default is yes. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 13f4c8df..f3d046d4 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -161,8 +161,8 @@ - - 10 + + 50 diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 55c0f877..3e4f15a2 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -151,7 +151,8 @@ struct rcxml { int unmaximize_threshold; /* window snapping */ - int snap_edge_range; + int snap_edge_range_inner; + int snap_edge_range_outer; int snap_edge_corner_range; bool snap_overlay_enabled; int snap_overlay_delay_inner; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 84054e82..b54e528a 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1180,7 +1180,14 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(nodename, "unMaximizeThreshold.resistance")) { rc.unmaximize_threshold = atoi(content); } else if (!strcasecmp(nodename, "range.snapping")) { - rc.snap_edge_range = atoi(content); + rc.snap_edge_range_inner = atoi(content); + rc.snap_edge_range_outer = atoi(content); + wlr_log(WLR_ERROR, " is deprecated. " + "Use instead."); + } else if (!strcasecmp(nodename, "inner.range.snapping")) { + rc.snap_edge_range_inner = atoi(content); + } else if (!strcasecmp(nodename, "outer.range.snapping")) { + rc.snap_edge_range_outer = atoi(content); } else if (!strcasecmp(nodename, "cornerRange.snapping")) { rc.snap_edge_corner_range = atoi(content); } else if (!strcasecmp(nodename, "enabled.overlay.snapping")) { @@ -1459,7 +1466,8 @@ rcxml_init(void) rc.unsnap_threshold = 20; rc.unmaximize_threshold = 150; - rc.snap_edge_range = 10; + rc.snap_edge_range_inner = 10; + rc.snap_edge_range_outer = 10; rc.snap_edge_corner_range = 50; rc.snap_overlay_enabled = true; rc.snap_overlay_delay_inner = 500; diff --git a/src/interactive.c b/src/interactive.c index df32f46f..03c4ad53 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -186,7 +186,7 @@ edge_from_cursor(struct seat *seat, struct output **dest_output, return false; } - if (rc.snap_edge_range == 0) { + if (rc.snap_edge_range_inner == 0 && rc.snap_edge_range_outer == 0) { return false; } @@ -197,9 +197,31 @@ edge_from_cursor(struct seat *seat, struct output **dest_output, } *dest_output = output; - /* Translate into output local coordinates */ double cursor_x = seat->cursor->x; double cursor_y = seat->cursor->y; + + int top_range = rc.snap_edge_range_outer; + int bottom_range = rc.snap_edge_range_outer; + int left_range = rc.snap_edge_range_outer; + int right_range = rc.snap_edge_range_outer; + if (wlr_output_layout_adjacent_output(seat->server->output_layout, WLR_DIRECTION_UP, + output->wlr_output, cursor_x, cursor_y)) { + top_range = rc.snap_edge_range_inner; + } + if (wlr_output_layout_adjacent_output(seat->server->output_layout, WLR_DIRECTION_DOWN, + output->wlr_output, cursor_x, cursor_y)) { + bottom_range = rc.snap_edge_range_inner; + } + if (wlr_output_layout_adjacent_output(seat->server->output_layout, WLR_DIRECTION_LEFT, + output->wlr_output, cursor_x, cursor_y)) { + left_range = rc.snap_edge_range_inner; + } + if (wlr_output_layout_adjacent_output(seat->server->output_layout, WLR_DIRECTION_RIGHT, + output->wlr_output, cursor_x, cursor_y)) { + right_range = rc.snap_edge_range_inner; + } + + /* Translate into output local coordinates */ wlr_output_layout_output_coords(seat->server->output_layout, output->wlr_output, &cursor_x, &cursor_y); @@ -210,13 +232,13 @@ edge_from_cursor(struct seat *seat, struct output **dest_output, int left = cursor_x - area->x; int right = area->x + area->width - cursor_x; - if (top < rc.snap_edge_range) { + if (top < top_range) { *edge1 = LAB_EDGE_TOP; - } else if (bottom < rc.snap_edge_range) { + } else if (bottom < bottom_range) { *edge1 = LAB_EDGE_BOTTOM; - } else if (left < rc.snap_edge_range) { + } else if (left < left_range) { *edge1 = LAB_EDGE_LEFT; - } else if (right < rc.snap_edge_range) { + } else if (right < right_range) { *edge1 = LAB_EDGE_RIGHT; } else { return false; From 61096463feff055e49b4613419dbe6f16d6b9d8f Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Thu, 4 Dec 2025 03:02:03 -0500 Subject: [PATCH 26/50] cursor: allow movement until entering the constraint surface Fixes an issue where the cursor would get stuck (immovable) outside the window of a Wine/Wayland game, if it was already outside when the game started (common with a 4:3 game on a 16:9 screen). Now one can manually move the cursor into the game window, at which point it becomes locked. This is a minimal/interim fix. Ideally we should warp the cursor into the constraint area automatically, but that would be a bit more work. The change to apply_constraint() just turns an assert-failure into a safe no-op return, since the function is now entered for "locked" as well as "confined" constraint types. --- src/input/cursor.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/input/cursor.c b/src/input/cursor.c index 2a681a6e..86297c9a 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -840,10 +840,12 @@ apply_constraint(struct seat *seat, struct wlr_pointer *pointer, double *x, doub if (!seat->server->active_view) { return; } - if (!seat->current_constraint || pointer->base.type != WLR_INPUT_DEVICE_POINTER) { + if (!seat->current_constraint + || pointer->base.type != WLR_INPUT_DEVICE_POINTER + || seat->current_constraint->type + != WLR_POINTER_CONSTRAINT_V1_CONFINED) { return; } - assert(seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED); double sx = seat->cursor->x; double sy = seat->cursor->y; @@ -866,7 +868,9 @@ cursor_locked(struct seat *seat, struct wlr_pointer *pointer) { return seat->current_constraint && pointer->base.type == WLR_INPUT_DEVICE_POINTER - && seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED; + && seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED + && seat->current_constraint->surface + == seat->seat->pointer_state.focused_surface; } static void From a1119f0cb218f189f4c884481b10e3a40ee3ee34 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Sat, 6 Dec 2025 14:28:04 +0000 Subject: [PATCH 27/50] docs/rc.xml: populate to avoid empty string --- docs/rc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rc.xml b/docs/rc.xml index ec9a2f6f..49d36369 100644 --- a/docs/rc.xml +++ b/docs/rc.xml @@ -6,7 +6,7 @@ - + Clearlooks-3.4 8 From a1d8ebc0de69b263556359ff47ac5507a9b0befd Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Sun, 7 Dec 2025 00:08:07 -0500 Subject: [PATCH 28/50] node: update node_descriptor_create() comment --- include/node.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/node.h b/include/node.h index 7b4936d6..6e459f69 100644 --- a/include/node.h +++ b/include/node.h @@ -24,6 +24,7 @@ struct node_descriptor { * @type: node descriptor type * @view: associated view * @data: struct to point to as follows: + * - LAB_NODE_CYCLE_OSD_ITEM struct cycle_osd_item * - LAB_NODE_LAYER_SURFACE struct lab_layer_surface * - LAB_NODE_LAYER_POPUP struct lab_layer_popup * - LAB_NODE_MENUITEM struct menuitem From 11959c5c5e5b6d9e18b7fddf698c6586dcd148c3 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Sun, 7 Dec 2025 00:09:45 -0500 Subject: [PATCH 29/50] rcxml: declare variable at initialization --- src/config/rcxml.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config/rcxml.c b/src/config/rcxml.c index b54e528a..d3694337 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1659,9 +1659,8 @@ load_default_window_switcher_fields(void) #endif }; - struct cycle_osd_field *field; for (size_t i = 0; i < ARRAY_SIZE(fields); i++) { - field = znew(*field); + struct cycle_osd_field *field = znew(*field); field->content = fields[i].content; field->width = fields[i].width; wl_list_append(&rc.window_switcher.fields, &field->link); From 1b22bf3d5a3de94cb2a89a728b76319ec71c39b5 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Sun, 7 Dec 2025 00:10:57 -0500 Subject: [PATCH 30/50] osd-classic: remove unused variable (buf) --- src/cycle/osd-classic.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index 67cfb8db..9acf3064 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -144,7 +144,6 @@ cycle_osd_classic_create(struct output *output) y += switcher_theme->item_height; } - struct buf buf = BUF_INIT; int nr_fields = wl_list_length(&rc.window_switcher.fields); /* This is the width of the area available for text fields */ @@ -216,7 +215,6 @@ cycle_osd_classic_create(struct output *output) y += switcher_theme->item_height; } - buf_reset(&buf); error:; /* Center OSD */ From 10a3281322e857034c1e65494bc1f3a1d0097386 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Sun, 7 Dec 2025 00:12:27 -0500 Subject: [PATCH 31/50] osd-classic: initialize struct fields in order --- src/cycle/osd-classic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index 9acf3064..944b9063 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -193,9 +193,9 @@ cycle_osd_classic_create(struct output *output) /* Highlight around selected window's item */ struct lab_scene_rect_options highlight_opts = { .border_colors = (float *[1]) {active_border_color}, - .bg_color = active_bg_color, .nr_borders = 1, .border_width = switcher_theme->item_active_border_width, + .bg_color = active_bg_color, .width = w - 2 * padding, .height = switcher_theme->item_height, }; From 2f37002498ecc53ae0ae3c70a37c9a828333a8d7 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Sat, 6 Dec 2025 12:49:09 -0500 Subject: [PATCH 32/50] cursor: don't synthesize relative motion events from absolute events It seems to have been inherited behavior from tinywl, but it's not clear what purpose it serves, and it causes a couple of issues: - A new absolute position that's discontinuous with the previous cursor position can produce unexpectedly large relative motion deltas. This can occur for example when multiple input devices are active, or in nested/VM scenarios when the pointer leaves the windowed output and re-enters at a different point. - When the cursor position is locked via constraint, the computed deltas continue to get larger as the absolute event position diverges further from the locked position. This led to the mouse pointer going crazy in applications that use the relative events, such as games under Wine/ Wayland. --- src/input/cursor.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/input/cursor.c b/src/input/cursor.c index 86297c9a..29409bea 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -985,11 +985,6 @@ handle_motion_absolute(struct wl_listener *listener, void *data) double dx = lx - seat->cursor->x; double dy = ly - seat->cursor->y; - wlr_relative_pointer_manager_v1_send_relative_motion( - seat->server->relative_pointer_manager, - seat->seat, (uint64_t)event->time_msec * 1000, - dx, dy, dx, dy); - preprocess_cursor_motion(seat, event->pointer, event->time_msec, dx, dy); } From eecb5d0947ea34d139e49c2be2f9ebaf4bf3323d Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Sat, 6 Dec 2025 20:05:36 +0000 Subject: [PATCH 33/50] labwc-config(5): clarify touchpad and touchscreen sections --- docs/labwc-config.5.scd | 58 +++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 74442362..4e7d7ad8 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -703,6 +703,45 @@ extending outward from the snapped edge. invisible zones just beyond the window that serve as click targets for mouse actions. Default is 8. +# INPUT CONFIGURATION + +This section describes configuration of input devices including: + +- Keyboards +- Mice +- Touchpads (sometimes referred to as laptop trackpads) +- Touchscreens +- Tablets +- Tablet tools (like stylus pens) + +It aims to clarify related terminology and separation of concerns. + +Keyboards are configured in the ** section below and are simple in +this regard. + +Touchpads and mice are harder. They are both considered to be *pointer* devices +by the compositor, and can be configured in the ** and ** +sections. Any setting that is supported by libinput is configured in the +** section, and anything else is in **. Touchpad devices can +generate gesture events, like swipe and pinch. There are some related settings +(e.g. *threeFingerDrag* and *twoFingerScroll*) in the ** section. + +In the Wayland Compositor domain, events associated with touchscreens are +sometimes simply referred to as *touch* events. Touchscreens can be configured +in both the ** and ** sections. Note that touchscreen gestures +are not interpreted by libinput, nor labwc. Any touch point is passed to the +client (application) for any interpretation of gestures. + +Tablets are considered special by libinput although in the eyes of the Wayland +protocol they are merely a *touch* capability. Tablets and associated tablet +tools are configured in the **, ** and ** +sections. Note that the term *tablet* in libinput (and labwc) refers to graphics +tablets only (e.g. Wacom Intuos), not to tablet devices like the Apple iPad. + +References: +- https://wayland.freedesktop.org/libinput/doc/latest/tablet-support.html +- https://wayland.freedesktop.org/libinput/doc/latest/gestures.html + ## KEYBOARD ** [on|off] @@ -811,6 +850,9 @@ extending outward from the snapped edge. ## MOUSE +This section relates to mice and touchpads - which are both considered pointer +input-devices by the Wayland protocol. + ** Set double click time in milliseconds. Default is 500. @@ -921,6 +963,11 @@ extending outward from the snapped edge. ## TOUCH +This section relates to touchscreens and *not* touchpads. + +Note: To rotate touch events with output rotation, use the libinput +*calibrationMatrix* setting. + ``` ``` @@ -1083,14 +1130,9 @@ extending outward from the snapped edge. attribute is provided, a 'default' device profile will created that will act as the fallback for all libinput devices. Category can be set to any of the following types: - - *touch* - Devices which have a defined width/height, but do not - support multitouch (i.e. they cannot track multiple locations where - the screen has been touched). Drawing tablets typically fall into this - type. - - *touchpad* - Same as 'touch' but support multitouch. This typically - includes laptop track pads with two-finger scroll and swipe gestures. - - *non-touch* - Anything not described above, for example traditional - mouse pointers. + - *touch* - Includes touchscreens and drawing-tablets. + - *touchpad* - Includes touchpads (also known as laptop trackpads) + - *non-touch* - Includes traditional mice - *default* - Defines a device-category applicable to all devices not matched by anything else. This can be useful for a fallback, or if you want the same settings to be applied to all devices. From 8fdf375af3148aa8edc17b5104780c309da4a1b0 Mon Sep 17 00:00:00 2001 From: Maik Broemme Date: Mon, 8 Dec 2025 18:54:23 +0100 Subject: [PATCH 34/50] window-switcher: add `order` parameter to allow stable window list ordering Add a new configuration option to control the window switcher traversal order. `order="focus"` cycling is convenient for quick toggling, but some users - me as well - prefer a stable taskbar-like order which can now be achieved with `order="age"`. --- docs/labwc-config.5.scd | 7 ++++++- include/config/rcxml.h | 1 + include/config/types.h | 5 +++++ include/labwc.h | 1 + include/view.h | 1 + src/config/rcxml.c | 10 ++++++++++ src/cycle/cycle.c | 20 +++++++++++++++++++- src/xdg.c | 1 + src/xwayland.c | 1 + 9 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 4e7d7ad8..4cb89cd5 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -349,7 +349,7 @@ this is for compatibility with Openbox. ``` -** +** *preview* [yes|no] Preview the contents of the selected window when switching between windows. Default is yes. @@ -363,6 +363,11 @@ this is for compatibility with Openbox. *unshade* [yes|no] Temporarily unshade windows when switching between them and permanently unshade on the final selection. Default is yes. + *order* [focus|age] The order in which windows are cycled. *focus* cycles by + recent focus history, starting with the previously focused window. *age* cycles + by creation/open order, a stable taskbar-style ordering that doesn’t change on + focus. Default is *focus*. + ** *show* [yes|no] Draw the OnScreenDisplay when switching between windows. Default is yes. diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 3e4f15a2..94eb6ebe 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -186,6 +186,7 @@ struct rcxml { enum cycle_osd_style style; enum cycle_osd_output_criteria output_criteria; char *thumbnail_label_format; + enum window_switcher_order order; } window_switcher; struct wl_list window_rules; /* struct window_rule.link */ diff --git a/include/config/types.h b/include/config/types.h index 757796a6..99c5929e 100644 --- a/include/config/types.h +++ b/include/config/types.h @@ -107,6 +107,11 @@ enum lab_window_type { LAB_WINDOW_TYPE_LEN }; +enum window_switcher_order { + WINDOW_SWITCHER_ORDER_FOCUS, + WINDOW_SWITCHER_ORDER_AGE, +}; + enum cycle_osd_style { CYCLE_OSD_STYLE_CLASSIC, CYCLE_OSD_STYLE_THUMBNAIL, diff --git a/include/labwc.h b/include/labwc.h index 40bff876..3d3ca2a3 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -188,6 +188,7 @@ struct server { struct wl_listener xdg_toplevel_icon_set_icon; struct wl_list views; + uint64_t next_view_creation_id; struct wl_list unmanaged_surfaces; struct seat seat; diff --git a/include/view.h b/include/view.h index 2f8aac85..fae462db 100644 --- a/include/view.h +++ b/include/view.h @@ -174,6 +174,7 @@ struct view { bool mapped; bool been_mapped; + uint64_t creation_id; enum lab_ssd_mode ssd_mode; enum ssd_preference ssd_preference; bool shaded; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index d3694337..eacd3d64 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1240,6 +1240,15 @@ entry(xmlNode *node, char *nodename, char *content) wlr_log(WLR_ERROR, "Invalid windowSwitcher output %s: " "should be one of all|focused|cursor", content); } + } else if (!strcasecmp(nodename, "order.windowSwitcher")) { + if (!strcasecmp(content, "focus")) { + rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS; + } else if (!strcasecmp(content, "age")) { + rc.window_switcher.order = WINDOW_SWITCHER_ORDER_AGE; + } else { + wlr_log(WLR_ERROR, "Invalid windowSwitcher order %s: " + "should be one of focus|age", content); + } /* The following two are for backward compatibility only. */ } else if (!strcasecmp(nodename, "show.windowSwitcher")) { @@ -1485,6 +1494,7 @@ rcxml_init(void) rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE | LAB_VIEW_CRITERIA_ROOT_TOPLEVEL | LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER; + rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS; rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER; rc.resize_draw_contents = true; diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 8eabaf6c..adf9b05e 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -276,13 +276,31 @@ create_osd_on_output(struct output *output) assert(output->cycle_osd.tree); } +static void +insert_view_ordered_by_age(struct wl_list *views, struct view *new_view) +{ + struct wl_list *link = views; + struct view *view; + wl_list_for_each(view, views, cycle_link) { + if (view->creation_id >= new_view->creation_id) { + break; + } + link = &view->cycle_link; + } + wl_list_insert(link, &new_view->cycle_link); +} + /* Return false on failure */ static bool init_cycle(struct server *server) { struct view *view; for_each_view(view, &server->views, rc.window_switcher.criteria) { - wl_list_append(&server->cycle.views, &view->cycle_link); + if (rc.window_switcher.order == WINDOW_SWITCHER_ORDER_AGE) { + insert_view_ordered_by_age(&server->cycle.views, view); + } else { + wl_list_append(&server->cycle.views, &view->cycle_link); + } } if (wl_list_empty(&server->cycle.views)) { wlr_log(WLR_DEBUG, "no views to switch between"); diff --git a/src/xdg.c b/src/xdg.c index 668dfe45..9ff24541 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -1085,6 +1085,7 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data) CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup); wl_list_insert(&server->views, &view->link); + view->creation_id = server->next_view_creation_id++; } static void diff --git a/src/xwayland.c b/src/xwayland.c index 564fa3e2..17eb995e 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -1036,6 +1036,7 @@ xwayland_view_create(struct server *server, CONNECT_SIGNAL(xsurface, xwayland_view, map_request); wl_list_insert(&view->server->views, &view->link); + view->creation_id = view->server->next_view_creation_id++; if (xsurface->surface) { handle_associate(&xwayland_view->associate, NULL); From 333ae306b12c1e4857e1206ffd69420899586638 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:11:25 +0100 Subject: [PATCH 35/50] desktop-entry: better handle desktop files with dots in their name This fixes - among others - cases like `R.E.P.O..desktop`. Due to the duplicated dot at the end we were matching against a 0 sized string which was always true and thus would always match the desktop file, regardless of what the app-id was. A related issue is that an app-id of `osomething` would match a desktop file name called `R.E.P.O.desktop` due to the string being size of 1. Fix this by requiring the partial desktop filename string to be at least 3 characters wide. Fixes #3257 --- src/desktop-entry.c | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/desktop-entry.c b/src/desktop-entry.c index 7617fa26..5ba8a2f6 100644 --- a/src/desktop-entry.c +++ b/src/desktop-entry.c @@ -251,6 +251,9 @@ err: * (e.g. "thunderbird" matches "org.mozilla.Thunderbird.desktop" * and "XTerm" matches "xterm.desktop"). This is not per any spec * but is needed to find icons for existing applications. + * + * The second loop tries to match more partial strings, for + * example "gimp-2.0" would match "org.something.gimp.desktop". */ static struct sfdo_desktop_entry * get_db_entry_by_id_fuzzy(struct sfdo_desktop_db *db, const char *app_id) @@ -258,6 +261,7 @@ get_db_entry_by_id_fuzzy(struct sfdo_desktop_db *db, const char *app_id) size_t n_entries; struct sfdo_desktop_entry **entries = sfdo_desktop_db_get_entries(db, &n_entries); + /* Would match "org.foobar.xterm" when given app-id "XTerm" */ for (size_t i = 0; i < n_entries; i++) { struct sfdo_desktop_entry *entry = entries[i]; const char *desktop_id = sfdo_desktop_entry_get_id(entry, NULL); @@ -266,6 +270,8 @@ get_db_entry_by_id_fuzzy(struct sfdo_desktop_db *db, const char *app_id) const char *desktop_id_base = dot ? (dot + 1) : desktop_id; if (!strcasecmp(app_id, desktop_id_base)) { + wlr_log(WLR_DEBUG, "'%s' to '%s.desktop' via case-insensitive match", + app_id, desktop_id); return entry; } @@ -278,20 +284,31 @@ get_db_entry_by_id_fuzzy(struct sfdo_desktop_db *db, const char *app_id) const char *wm_class = sfdo_desktop_entry_get_startup_wm_class(entry, NULL); if (wm_class && !strcasecmp(app_id, wm_class)) { + wlr_log(WLR_DEBUG, "'%s' to '%s.desktop' via StartupWMClass", + app_id, desktop_id); return entry; } } - /* Try matching partial strings - catches GIMP, among others */ + /* Would match "org.foobar.xterm-unicode" when given app-id "XTerm" */ + const int app_id_len = strlen(app_id); for (size_t i = 0; i < n_entries; i++) { struct sfdo_desktop_entry *entry = entries[i]; const char *desktop_id = sfdo_desktop_entry_get_id(entry, NULL); const char *dot = strrchr(desktop_id, '.'); const char *desktop_id_base = dot ? (dot + 1) : desktop_id; - int alen = strlen(app_id); - int dlen = strlen(desktop_id_base); - - if (!strncasecmp(app_id, desktop_id_base, alen > dlen ? dlen : alen)) { + const int dlen = strlen(desktop_id_base); + const int cmp_len = MIN(app_id_len, dlen); + if (cmp_len < 3) { + /* + * Without this check, app-id "foot" would match + * "something.f" and any app-id would match "R.E.P.O." + */ + continue; + } + if (!strncasecmp(app_id, desktop_id_base, cmp_len)) { + wlr_log(WLR_DEBUG, "'%s' to '%s.desktop' via partial match", + app_id, desktop_id); return entry; } } @@ -304,10 +321,14 @@ get_desktop_entry(struct sfdo *sfdo, const char *app_id) { struct sfdo_desktop_entry *entry = sfdo_desktop_db_get_entry_by_id( sfdo->desktop_db, app_id, SFDO_NT); - if (!entry) { - entry = get_db_entry_by_id_fuzzy(sfdo->desktop_db, app_id); + if (entry) { + wlr_log(WLR_DEBUG, "matched '%s.desktop' via exact match", app_id); + return entry; + } + entry = get_db_entry_by_id_fuzzy(sfdo->desktop_db, app_id); + if (!entry) { + wlr_log(WLR_DEBUG, "failed to find .desktop file for '%s'", app_id); } - return entry; } From a059fbb7cbd1a50cbcd6dce3fd2b5faebad6aa15 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Sat, 6 Dec 2025 14:24:39 +0000 Subject: [PATCH 36/50] NEWS.md: interim update ...at start of cool-down period for 0.9.3 --- NEWS.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4a3bbc8f..c03b5842 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-11-15 | [unreleased] | 0.19.2 | 28825 | +| 2025-12-06 | [unreleased] | 0.19.2 | 28927 | | 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 | @@ -116,8 +116,36 @@ differently [#3099]. There is a pending fix [wlroots-5159]. [unreleased-commits] +This release contains a good amount of bug-fixes, code simplification and +small usability improvements. + +With the stability that comes with having tracked `wlroots 0.19` for a decent +length of time, this feels like the best version of labwc so far. + +In terms of new features, it is worth drawing attention to the click support in +the window-switcher on-screen-display by @tokyo4j [#3186] which has frequently +been requested by users. + +As a general note to users, we discourage the use of empty strings in the +`rc.xml` configuration file, for example ``. There +are only a few areas left where empty string are ignored (like under +``) but the intent for future releases is to consistently read empty +strings as empty strings. As a preparation, this release has added some warnings +for empty strings that are currently ignored, so that users can take action. +Also, the example `docs/rc.xml.all` has been updated to remove poor examples in +this regard. + +A big thank you to all involved in this release. + ### Added +- Replace `` with `` to + provide more granular control when configuring the size of snapping areas + (including ``) on output edges with and without adjacent outputs. + @elviosak [#3241] +- Add `direction` option to `Resize` action supporting the values `up-left`, + `up`, `up-right`, `left`, `right`, `down-left`, `down`, `down-right`. This + mirrors Fluxbox's `StartResizing [corner]` behavior. @mbroemme [#3239] - Allow the use of the `sendEventsMode` configuration option on keyboards in order to disable keyboard input. @cillian64 [#3208] @@ -131,8 +159,8 @@ differently [#3099]. There is a pending fix [wlroots-5159]. - Support the following new `` configuration options: - `` to specify the label text in each item in the thumbnail style window-switcher. @elviosak [#3187] - - `` to specify which monitor(s) to show - the OSD(s) on. @dntxi [#3201] + - `` to specify which monitor(s) to show + the OSD(s) on. @dntxi [#3201] [#3248] - Support window-switcher OSD item click to focus window @tokyo4j [#3186] - With the window-switcher custom field state specifiers 's' and 'S', show 's' for shaded window @domo141 [#2895] @@ -148,6 +176,18 @@ differently [#3099]. There is a pending fix [wlroots-5159]. ### Fixed +- Flush XCB connection to mitigate race between Raise and input. @jlindgren90 + [#3249] +- Fix disappearing XWayland popups with some (less commonly used) clients like + Imagemagick's `display` command, `xshogi`, `xedit` and `xfig` caused by + too many surface-pings. @jlindgren90 [#3152] [#3246] +- Center small fullscreen xdg-shell windows and add black background fill. This + increases spec compliance and improves the user experience with games like + SWAT4, Quake III and Splinter Cell 3. @jlindgren90 [#3233] +- When followMouse=yes, update focus on cursor entering SSD rather than just the + client surface. Fixes a regression in 885919f. @tokyo4j [#3211] +- Set all foreign-toplevel initial states correctly. This is not believed to fix + any particular user-issue, but just feels safer. @jlindgren90 [#3217] - Update layer-shell client top layer visiblity on unmap instead of destroy because it is possible for fullscreen xwayland windows to be unmapped without being destroyed, and in this case the top layer visibility needs to be updated @@ -181,6 +221,16 @@ differently [#3099]. There is a pending fix [wlroots-5159]. ### Changed +- `` is deprecated. Use `` + instead. @elviosak [#3241] +- When cycling through windows (typically with Alt-Tab) there are two minor + user-visible changes. For most users these will not be noticeable, but are + mentioned here for completeness. + - The initially selected window will now be the one that previously had + keyboard focus when cycling commenced rather than the second topmost one. + @tokyo4j [#3236] + - Windows that are spawned whilst cycling can no longer be cycled through. The + intent is to fix this in future releases. @tokyo4j [#3236] - Refactor window switcher configuration to put attributes `show` and `style` under `` rather than directly under ``. The old configuration syntax will remain supported for at least one release. @@ -2938,6 +2988,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3145]: https://github.com/labwc/labwc/pull/3145 [#3146]: https://github.com/labwc/labwc/pull/3146 [#3148]: https://github.com/labwc/labwc/pull/3148 +[#3152]: https://github.com/labwc/labwc/pull/3152 [#3153]: https://github.com/labwc/labwc/pull/3153 [#3157]: https://github.com/labwc/labwc/pull/3157 [#3158]: https://github.com/labwc/labwc/pull/3158 @@ -2951,3 +3002,12 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3199]: https://github.com/labwc/labwc/pull/3199 [#3201]: https://github.com/labwc/labwc/pull/3201 [#3208]: https://github.com/labwc/labwc/pull/3208 +[#3211]: https://github.com/labwc/labwc/pull/3211 +[#3217]: https://github.com/labwc/labwc/pull/3217 +[#3233]: https://github.com/labwc/labwc/pull/3233 +[#3236]: https://github.com/labwc/labwc/pull/3236 +[#3239]: https://github.com/labwc/labwc/pull/3239 +[#3241]: https://github.com/labwc/labwc/pull/3241 +[#3246]: https://github.com/labwc/labwc/pull/3246 +[#3248]: https://github.com/labwc/labwc/pull/3248 +[#3249]: https://github.com/labwc/labwc/pull/3249 From 1317439513f977cd5d00689748c7cdb7911d6866 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 12 Dec 2025 03:58:51 +0900 Subject: [PATCH 37/50] rcxml: minor updates for deprecation message --- src/config/rcxml.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/rcxml.c b/src/config/rcxml.c index eacd3d64..cc744228 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1226,7 +1226,7 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(content, "thumbnail")) { rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL; } else { - wlr_log(WLR_ERROR, "Invalid windowSwitcher style %s: " + wlr_log(WLR_ERROR, "Invalid windowSwitcher style '%s': " "should be one of classic|thumbnail", content); } } else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) { @@ -1237,7 +1237,7 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(content, "focused")) { rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_FOCUSED; } else { - wlr_log(WLR_ERROR, "Invalid windowSwitcher output %s: " + wlr_log(WLR_ERROR, "Invalid windowSwitcher output '%s': " "should be one of all|focused|cursor", content); } } else if (!strcasecmp(nodename, "order.windowSwitcher")) { @@ -1246,7 +1246,7 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(content, "age")) { rc.window_switcher.order = WINDOW_SWITCHER_ORDER_AGE; } else { - wlr_log(WLR_ERROR, "Invalid windowSwitcher order %s: " + wlr_log(WLR_ERROR, "Invalid windowSwitcher order '%s': " "should be one of focus|age", content); } From 7f0abed9c8c9859c4962c8feecfbe0b0a2ebb008 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Tue, 25 Nov 2025 23:03:46 +0200 Subject: [PATCH 38/50] docs: tab to space: 4, trailing newline: -1, fit some lines to 80-cols --- docs/labnag.1.scd | 1 - docs/labwc-actions.5.scd | 2 +- docs/labwc-config.5.scd | 57 +++++++++++++++++++++------------------- docs/labwc-theme.5.scd | 21 ++++++++------- docs/rc.xml.all | 4 +-- docs/shutdown | 3 ++- 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/docs/labnag.1.scd b/docs/labnag.1.scd index a2a36c10..6d4b959f 100644 --- a/docs/labnag.1.scd +++ b/docs/labnag.1.scd @@ -140,4 +140,3 @@ labnag \\ --button-border-size 2\\ -t 60 ``` - diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 96e44cb6..7788d551 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -379,7 +379,7 @@ Actions are used in menus and keyboard/mouse bindings. *x* [center|value] Specifies the horizontal warp position within the target area. "center": Moves the cursor to the horizontal center of the target area. Positive or negative integers warp the cursor to a position - offset by the specified number of pixels from the left or right edge of + offset by the specified number of pixels from the left or right edge of the target area, respectively. Default is "center" *y* [center|value] Equivalent for the vertical warp position within the diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 4cb89cd5..fa8e0602 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -363,17 +363,18 @@ this is for compatibility with Openbox. *unshade* [yes|no] Temporarily unshade windows when switching between them and permanently unshade on the final selection. Default is yes. - *order* [focus|age] The order in which windows are cycled. *focus* cycles by - recent focus history, starting with the previously focused window. *age* cycles - by creation/open order, a stable taskbar-style ordering that doesn’t change on - focus. Default is *focus*. + *order* [focus|age] The order in which windows are cycled. *focus* + cycles by recent focus history, starting with the previously focused + window. *age* cycles by creation/open order, a stable taskbar-style + ordering that doesn’t change on focus. Default is *focus*. ** *show* [yes|no] Draw the OnScreenDisplay when switching between windows. Default is yes. *style* [classic|thumbnail] Configures the style of the OSD. - "classic" displays window information like icons and titles in a vertical list. + "classic" displays window information like icons and titles in a + vertical list. "thumbnail" shows window thumbnail, icon and title in grids. *output* [all|focused|cursor] Configures which monitor(s) show the OSD. @@ -382,9 +383,9 @@ this is for compatibility with Openbox. "cursor" displays the OSD on the monitor containing the mouse pointer. Default is "all". - *thumbnailLabelFormat* Format to be used for the thumbnail label according to *custom* - field below, only applied when using **. - Default is "%T". + *thumbnailLabelFormat* Format to be used for the thumbnail label + according to *custom* field below, only applied when using + **. Default is "%T". ** Define window switcher fields when using **. @@ -506,13 +507,13 @@ extending outward from the snapped edge. **++ **++ ** - If an interactive move ends with the cursor within *inner* or *outer* pixels - of an output edge, the window is snapped to the edge. *inner* edges are edges - with an adjacent output and *outer* edges are edges without an adjacent output. - If it's also within ** pixels of an output corner, the window is - snapped to the corner instead. - If *inner* and *outer* is 0, snapping is disabled. - Default is 10 for ** and **, and 50 for **. + If an interactive move ends with the cursor within *inner* or *outer* + pixels of an output edge, the window is snapped to the edge. *inner* + edges are edges with an adjacent output and *outer* edges are edges + without an adjacent output. If it's also within ** pixels + of an output corner, the window is snapped to the corner instead. + If *inner* and *outer* is 0, snapping is disabled. Default is 10 for + ** and **, and 50 for **. ** [yes|no] Show an overlay when snapping to a window to an edge. Default is yes. @@ -533,8 +534,8 @@ extending outward from the snapped edge. ** [always|region|edge|never] Snapping windows can trigger corresponding tiling events for native Wayland clients. Clients may use these events to alter their rendering - based on knowledge that some edges of the window are confined to edges of - a snapping region or output. For example, rounded corners may become + based on knowledge that some edges of the window are confined to edges + of a snapping region or output. For example, rounded corners may become square when tiled, or media players may letter-box or pillar-box video rather than imposing rigid aspect ratios on windows that will violate the constraints of window snapping. @@ -624,15 +625,16 @@ extending outward from the snapped edge. ** [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. + *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. ** [yes|no] Should drop-shadows be rendered behind tiled windows. This won't take - effect if is smaller than window.active.shadow.size in theme. + effect if is smaller than window.active.shadow.size in + theme. Default is no. @@ -1048,7 +1050,7 @@ Note: To rotate touch events with output rotation, use the libinput ** Pen and pad buttons behave like regular mouse buttons.With mouse - emulation set to "no", which is the default, and if not specified + emulation set to "no", which is the default, and if not specified otherwise, the first pen button is mapped to the right mouse button, the second pen button to the middle mouse button and a third pen button is mapped to the side mouse button. @@ -1067,10 +1069,10 @@ Note: To rotate touch events with output rotation, use the libinput When using mouse emulation, all pen buttons emulate regular mouse buttons. The tip, stylus and pad buttons can be mapped to all - available mouse buttons. If not specified otherwise, the tip is + available mouse buttons. If not specified otherwise, the tip is mapped to left mouse click, the first pen button (Stylus) is mapped to right mouse button click and the second pen button (Stylus2) - emulates a middle mouse button click. Buttons of a tablet tool mouse + emulates a middle mouse button click. Buttons of a tablet tool mouse are by default mapped to their (regular) mouse counterparts. Supported map *buttons* for mouse emulation are: @@ -1180,11 +1182,12 @@ Note: To rotate touch events with output rotation, use the libinput a tap immediately followed by a finger down as the start of a drag. ** [yes|no|timeout] - Enable or disable drag lock for this category. Drag lock ignores a temporary - release of a finger during tap-and-dragging. + Enable or disable drag lock for this category. Drag lock ignores a + temporary release of a finger during tap-and-dragging. - *timeout* also enables drag lock, but with a timeout: if your fingers are - released for a certain amount of time, the drag gesture is cancelled. + *timeout* also enables drag lock, but with a timeout: if your fingers + are released for a certain amount of time, the drag gesture is + cancelled. In libinput < 1.27, the behavior of *yes* is equivalent to *timeout*. ** [yes|no|3|4] diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index 8097615b..653f3b1e 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -308,8 +308,8 @@ all are supported. See below for details. *osd.window-switcher.style-classic.width* - Width of window switcher in pixels. Width can also be a percentage of the - monitor width by adding '%' as suffix (e.g. 70%). Default is 600. + Width of window switcher in pixels. Width can also be a percentage of + the monitor width by adding '%' as suffix (e.g. 70%). Default is 600. *osd.window-switcher.style-classic.padding* Padding of window switcher in pixels. This is the space between the @@ -337,16 +337,17 @@ all are supported. *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 - is used. + If not set, the font size derived from + is used. *osd.window-switcher.style-thumbnail* - Theme for window switcher when using . - See below for details. + Theme for window switcher when using + . See below for details. *osd.window-switcher.style-thumbnail.width.max* - Maximum width of window switcher in pixels. Width can also be a percentage of - the monitor width by adding '%' as suffix (e.g. 70%). Default is 80%. + Maximum width of window switcher in pixels. Width can also be a + percentage of the monitor width by adding '%' as suffix (e.g. 70%). + Default is 80%. *osd.window-switcher.style-thumbnail.padding* Padding of window switcher in pixels. This is the space between the @@ -359,8 +360,8 @@ all are supported. Height of window switcher items in pixels. Default is 250. *osd.window-switcher.style-thumbnail.item.padding* - Padding of window switcher items in pixels. This is the space between the - border around selected items and window thumbnail. Default is 2. + Padding of window switcher items in pixels. This is the space between + the border around selected items and window thumbnail. Default is 2. *osd.window-switcher.style-thumbnail.item.active.border.width* Border width of selected window switcher items in pixels. Default is 2. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index f3d046d4..ea4e30ac 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -630,8 +630,8 @@ # must only apply to the first instance of the window with that # particular 'identifier' or 'title'. # - Matching is case-insensitive and is performed using shell wildcard - # patterns (see glob(7)) so '\*' (not between brackets) matches any string - # and '?' matches any single character. + # patterns (see glob(7)) so '\*' (not between brackets) matches any + # string and '?' matches any single character. diff --git a/docs/shutdown b/docs/shutdown index 970eaa26..feed6508 100644 --- a/docs/shutdown +++ b/docs/shutdown @@ -1,4 +1,5 @@ # Example shutdown file -# This file is executed as a shell script when labwc is preparing to terminate itself. +# This file is executed as a shell script when labwc is preparing to terminate +# itself. # For further details see labwc-config(5). From 7537501668bb93dd552043e4d8085b15803ea4b9 Mon Sep 17 00:00:00 2001 From: Juliana Sims <47213432+Marie-Joseph@users.noreply.github.com> Date: Tue, 16 Dec 2025 04:22:55 +0000 Subject: [PATCH 39/50] labwc-config(5): correct semantically-significant typo --- docs/labwc-config.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index fa8e0602..1c2a4af6 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -26,7 +26,7 @@ should (a) allow the first-identified configuration file to supersede any others, or (b) define rules for merging the information from more than one file. By default, labwc uses option (a), reading only the first file identified. With -the --merge-config option, the search order is reserved, but every configuration +the --merge-config option, the search order is reversed, but every configuration file encountered is processed in turn. Thus, user-specific files will augment system-wide configurations, with conflicts favoring the user-specific alternative. From d1ba186fe401d7b3f2b41068713ee91b62018877 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Wed, 10 Dec 2025 19:38:49 +0000 Subject: [PATCH 40/50] NEWS.md: update notes for 0.9.3 --- NEWS.md | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/NEWS.md b/NEWS.md index c03b5842..f64845b4 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-12-06 | [unreleased] | 0.19.2 | 28927 | +| 2025-12-19 | [0.9.3] | 0.19.2 | 28968 | | 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 | @@ -40,6 +40,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.3]: NEWS.md#093---2025-12-19 [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 @@ -89,8 +90,6 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: with the environment variable `LABWC_FALLBACK_OUTPUT=NOOP-fallback` to temporarily create a fallback-output when the last physical display disconnects. [#2914] [#2939] [wlroots-4878] [gtk-8792] -- Due to a single-pixel protocol issue, `waylock` and `chayang` do not work. - This will be fixed in `wlroots-0.19.1`. [#2943] [wlroots-5098] - Menu item can no longer be activated in any Gtk applications with a single press-drag-release mouse action. For context: This is due to ambiguity in the specifications and contrary implementations. For example, Gtk applications are @@ -98,24 +97,21 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: other compositors like Weston, Mutter and labwc. It has been decided not to block the release due to this regression as it is an eco-system wide issue that has existed for a long time. [#2787] -- VR headset support is disabled when compiled with wlroots `0.19.0` to work - 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]. +- It is strongly recommended to use at least wlroots 0.19.1 [#2943] + [wlroots-5098] [#2887] [wlroots-4878]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4878 [wlroots-5098]:https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5098 -[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.3 - 2025-12-19 + +[0.9.3-commits] + This release contains a good amount of bug-fixes, code simplification and small usability improvements. @@ -139,6 +135,8 @@ A big thank you to all involved in this release. ### Added +- Add `` to optionally order windows by age + rather than most recent focus. @mbroemme [#3229] - Replace `` with `` to provide more granular control when configuring the size of snapping areas (including ``) on output edges with and without adjacent outputs. @@ -176,6 +174,16 @@ A big thank you to all involved in this release. ### Fixed +- Handle desktop files with dots in their names better @Consolatis [#3267] +- Do not synthesize cursor relative motion events from absolute events to fix a + couple of problems: Firstly to avoid unexpectedly large relative motion deltas + with multiple input devices or in nested/VM scenarios, and secondly to fix + erratic mouse behavior in applications that use relative events whilst locking + with pointer constraints. @jlindgren90 [#3251] +- Allow cursor movement until entering constraint surface, to fix an issue where + the cursor would get stuck (immovable) outside the window of a Wine/Wayland + game, if it was already outside when the game started (which is common with + 4:3 games on a 16:9 screen). @jlindgren90 [#3252] - Flush XCB connection to mitigate race between Raise and input. @jlindgren90 [#3249] - Fix disappearing XWayland popups with some (less commonly used) clients like @@ -2511,7 +2519,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.2...HEAD +[unreleased-commits]: https://github.com/labwc/labwc/compare/0.9.3...HEAD +[0.9.3-commits]: https://github.com/labwc/labwc/compare/0.9.2...0.9.3 [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 @@ -3004,6 +3013,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3208]: https://github.com/labwc/labwc/pull/3208 [#3211]: https://github.com/labwc/labwc/pull/3211 [#3217]: https://github.com/labwc/labwc/pull/3217 +[#3229]: https://github.com/labwc/labwc/pull/3229 [#3233]: https://github.com/labwc/labwc/pull/3233 [#3236]: https://github.com/labwc/labwc/pull/3236 [#3239]: https://github.com/labwc/labwc/pull/3239 @@ -3011,3 +3021,6 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3246]: https://github.com/labwc/labwc/pull/3246 [#3248]: https://github.com/labwc/labwc/pull/3248 [#3249]: https://github.com/labwc/labwc/pull/3249 +[#3251]: https://github.com/labwc/labwc/pull/3251 +[#3252]: https://github.com/labwc/labwc/pull/3252 +[#3267]: https://github.com/labwc/labwc/pull/3267 From a98fd8f9742ef7621c8b479353409fd06b82cf75 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Fri, 19 Dec 2025 16:35:01 +0000 Subject: [PATCH 41/50] build: bump version to 0.9.3 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 335bf629..62925ddb 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'labwc', 'c', - version: '0.9.2', + version: '0.9.3', license: 'GPL-2.0-only', meson_version: '>=0.59.0', default_options: [ From e2d83ff7f5d2c653c7ce298aadd9790b57f86ff2 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 7 Dec 2025 17:44:36 +0900 Subject: [PATCH 42/50] rcxml: sync rcxml.window_switcher with XML format --- include/config/rcxml.h | 14 +++++++----- include/cycle.h | 2 +- src/config/rcxml.c | 46 +++++++++++++++++++-------------------- src/cycle/cycle.c | 8 +++---- src/cycle/osd-classic.c | 4 ++-- src/cycle/osd-thumbnail.c | 2 +- 6 files changed, 39 insertions(+), 37 deletions(-) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 94eb6ebe..edfb9e55 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -177,16 +177,18 @@ struct rcxml { /* Window Switcher */ struct { - bool show; bool preview; bool outlines; bool unshade; - enum lab_view_criteria criteria; - struct wl_list fields; /* struct window_switcher_field.link */ - enum cycle_osd_style style; - enum cycle_osd_output_criteria output_criteria; - char *thumbnail_label_format; enum window_switcher_order order; + enum lab_view_criteria criteria; + struct { + bool show; + enum cycle_osd_style style; + enum cycle_osd_output_criteria output_criteria; + char *thumbnail_label_format; + struct wl_list fields; /* struct cycle_osd_field.link */ + } osd; } window_switcher; struct wl_list window_rules; /* struct window_rule.link */ diff --git a/include/cycle.h b/include/cycle.h index aaecff50..ba58b88d 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -39,7 +39,7 @@ struct cycle_osd_field { enum cycle_osd_field_content content; int width; char *format; - struct wl_list link; /* struct rcxml.window_switcher.fields */ + struct wl_list link; /* struct rcxml.window_switcher.osd.fields */ }; struct buf; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index cc744228..bb868fb2 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -324,7 +324,7 @@ static void clear_window_switcher_fields(void) { struct cycle_osd_field *field, *field_tmp; - wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) { + wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.osd.fields, link) { wl_list_remove(&field->link); cycle_osd_field_free(field); } @@ -334,7 +334,7 @@ static void fill_window_switcher_field(xmlNode *node) { struct cycle_osd_field *field = znew(*field); - wl_list_append(&rc.window_switcher.fields, &field->link); + wl_list_append(&rc.window_switcher.osd.fields, &field->link); xmlNode *child; char *key, *content; @@ -1071,7 +1071,7 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(nodename, "prefix.desktops")) { xstrdup_replace(rc.workspace_config.prefix, content); } else if (!strcasecmp(nodename, "thumbnailLabelFormat.osd.windowSwitcher")) { - xstrdup_replace(rc.window_switcher.thumbnail_label_format, content); + xstrdup_replace(rc.window_switcher.osd.thumbnail_label_format, content); } else if (!lab_xml_node_is_leaf(node)) { /* parse children of nested nodes other than above */ @@ -1219,23 +1219,23 @@ entry(xmlNode *node, char *nodename, char *content) * thumnailLabelFormat is handled above to allow for an empty value */ } else if (!strcasecmp(nodename, "show.osd.windowSwitcher")) { - set_bool(content, &rc.window_switcher.show); + set_bool(content, &rc.window_switcher.osd.show); } else if (!strcasecmp(nodename, "style.osd.windowSwitcher")) { if (!strcasecmp(content, "classic")) { - rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC; } else if (!strcasecmp(content, "thumbnail")) { - rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL; } else { wlr_log(WLR_ERROR, "Invalid windowSwitcher style '%s': " "should be one of classic|thumbnail", content); } } else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) { if (!strcasecmp(content, "all")) { - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL; + rc.window_switcher.osd.output_criteria = CYCLE_OSD_OUTPUT_ALL; } else if (!strcasecmp(content, "cursor")) { - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_CURSOR; + rc.window_switcher.osd.output_criteria = CYCLE_OSD_OUTPUT_CURSOR; } else if (!strcasecmp(content, "focused")) { - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_FOCUSED; + rc.window_switcher.osd.output_criteria = CYCLE_OSD_OUTPUT_FOCUSED; } else { wlr_log(WLR_ERROR, "Invalid windowSwitcher output '%s': " "should be one of all|focused|cursor", content); @@ -1252,14 +1252,14 @@ entry(xmlNode *node, char *nodename, char *content) /* The following two are for backward compatibility only. */ } else if (!strcasecmp(nodename, "show.windowSwitcher")) { - set_bool(content, &rc.window_switcher.show); + set_bool(content, &rc.window_switcher.osd.show); wlr_log(WLR_ERROR, " is deprecated." " Use "); } else if (!strcasecmp(nodename, "style.windowSwitcher")) { if (!strcasecmp(content, "classic")) { - rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC; } else if (!strcasecmp(content, "thumbnail")) { - rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL; } wlr_log(WLR_ERROR, " is deprecated." " Use "); @@ -1282,7 +1282,7 @@ entry(xmlNode *node, char *nodename, char *content) /* The following three are for backward compatibility only */ } else if (!strcasecmp(nodename, "show.windowSwitcher.core")) { - set_bool(content, &rc.window_switcher.show); + set_bool(content, &rc.window_switcher.osd.show); } else if (!strcasecmp(nodename, "preview.windowSwitcher.core")) { set_bool(content, &rc.window_switcher.preview); } else if (!strcasecmp(nodename, "outlines.windowSwitcher.core")) { @@ -1290,7 +1290,7 @@ entry(xmlNode *node, char *nodename, char *content) /* The following three are for backward compatibility only */ } else if (!strcasecmp(nodename, "cycleViewOSD.core")) { - set_bool(content, &rc.window_switcher.show); + set_bool(content, &rc.window_switcher.osd.show); wlr_log(WLR_ERROR, " is deprecated." " Use "); } else if (!strcasecmp(nodename, "cycleViewPreview.core")) { @@ -1419,7 +1419,7 @@ rcxml_init(void) wl_list_init(&rc.libinput_categories); wl_list_init(&rc.workspace_config.workspaces); wl_list_init(&rc.regions); - wl_list_init(&rc.window_switcher.fields); + wl_list_init(&rc.window_switcher.osd.fields); wl_list_init(&rc.window_rules); wl_list_init(&rc.touch_configs); } @@ -1484,10 +1484,10 @@ rcxml_init(void) rc.snap_top_maximize = true; rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS; - rc.window_switcher.show = true; - rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL; - rc.window_switcher.thumbnail_label_format = xstrdup("%T"); + rc.window_switcher.osd.show = true; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC; + rc.window_switcher.osd.output_criteria = CYCLE_OSD_OUTPUT_ALL; + rc.window_switcher.osd.thumbnail_label_format = xstrdup("%T"); rc.window_switcher.preview = true; rc.window_switcher.outlines = true; rc.window_switcher.unshade = true; @@ -1673,7 +1673,7 @@ load_default_window_switcher_fields(void) struct cycle_osd_field *field = znew(*field); field->content = fields[i].content; field->width = fields[i].width; - wl_list_append(&rc.window_switcher.fields, &field->link); + wl_list_append(&rc.window_switcher.osd.fields, &field->link); } } @@ -1794,7 +1794,7 @@ post_processing(void) if (rc.workspace_config.popuptime == INT_MIN) { rc.workspace_config.popuptime = 1000; } - if (!wl_list_length(&rc.window_switcher.fields)) { + if (!wl_list_length(&rc.window_switcher.osd.fields)) { wlr_log(WLR_INFO, "load default window switcher fields"); load_default_window_switcher_fields(); } @@ -1888,7 +1888,7 @@ validate(void) /* OSD fields */ int field_width_sum = 0; struct cycle_osd_field *field, *field_tmp; - wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) { + wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.osd.fields, link) { field_width_sum += field->width; if (!cycle_osd_field_is_valid(field) || field_width_sum > 100) { wlr_log(WLR_ERROR, "Deleting invalid window switcher field %p", field); @@ -1964,7 +1964,7 @@ rcxml_finish(void) zfree(rc.fallback_app_icon_name); zfree(rc.workspace_config.prefix); zfree(rc.tablet.output_name); - zfree(rc.window_switcher.thumbnail_label_format); + zfree(rc.window_switcher.osd.thumbnail_label_format); clear_title_layout(); diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index adf9b05e..a775f78c 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -257,7 +257,7 @@ preview_selected_view(struct view *view) static struct cycle_osd_impl * get_osd_impl(void) { - switch (rc.window_switcher.style) { + switch (rc.window_switcher.osd.style) { case CYCLE_OSD_STYLE_CLASSIC: return &cycle_osd_classic_impl; case CYCLE_OSD_STYLE_THUMBNAIL: @@ -307,9 +307,9 @@ init_cycle(struct server *server) return false; } - if (rc.window_switcher.show) { + if (rc.window_switcher.osd.show) { /* Create OSD */ - switch (rc.window_switcher.output_criteria) { + switch (rc.window_switcher.osd.output_criteria) { case CYCLE_OSD_OUTPUT_ALL: { struct output *output; wl_list_for_each(output, &server->outputs, link) { @@ -342,7 +342,7 @@ update_cycle(struct server *server) { struct cycle_state *cycle = &server->cycle; - if (rc.window_switcher.show) { + if (rc.window_switcher.osd.show) { struct output *output; wl_list_for_each(output, &server->outputs, link) { if (output->cycle_osd.tree) { diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index 944b9063..8e01dc24 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -36,7 +36,7 @@ create_fields_scene(struct server *server, struct view *view, &theme->osd_window_switcher_classic; struct cycle_osd_field *field; - wl_list_for_each(field, &rc.window_switcher.fields, link) { + wl_list_for_each(field, &rc.window_switcher.osd.fields, link) { int field_width = field_widths_sum * field->width / 100.0; struct wlr_scene_node *node = NULL; int height = -1; @@ -144,7 +144,7 @@ cycle_osd_classic_create(struct output *output) y += switcher_theme->item_height; } - int nr_fields = wl_list_length(&rc.window_switcher.fields); + int nr_fields = wl_list_length(&rc.window_switcher.osd.fields); /* This is the width of the area available for text fields */ int field_widths_sum = w - 2 * padding diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index 2245d1ad..d17b1aa0 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -103,7 +103,7 @@ create_label(struct wlr_scene_tree *parent, struct view *view, { struct buf buf = BUF_INIT; cycle_osd_field_set_custom(&buf, view, - rc.window_switcher.thumbnail_label_format); + rc.window_switcher.osd.thumbnail_label_format); struct scaled_font_buffer *buffer = scaled_font_buffer_create(parent); scaled_font_buffer_update(buffer, buf.data, From 64af206114a35772a999b20e2dc192922ef7dbd6 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 12 Dec 2025 03:38:57 +0900 Subject: [PATCH 43/50] Rename cycle_osd_output_criteria to cycle_output_filter --- include/config/rcxml.h | 2 +- include/config/types.h | 8 ++++---- src/config/rcxml.c | 8 ++++---- src/cycle/cycle.c | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index edfb9e55..65b07172 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -185,7 +185,7 @@ struct rcxml { struct { bool show; enum cycle_osd_style style; - enum cycle_osd_output_criteria output_criteria; + enum cycle_output_filter output_filter; char *thumbnail_label_format; struct wl_list fields; /* struct cycle_osd_field.link */ } osd; diff --git a/include/config/types.h b/include/config/types.h index 99c5929e..540f609f 100644 --- a/include/config/types.h +++ b/include/config/types.h @@ -117,10 +117,10 @@ enum cycle_osd_style { CYCLE_OSD_STYLE_THUMBNAIL, }; -enum cycle_osd_output_criteria { - CYCLE_OSD_OUTPUT_ALL, - CYCLE_OSD_OUTPUT_CURSOR, - CYCLE_OSD_OUTPUT_FOCUSED, +enum cycle_output_filter { + CYCLE_OUTPUT_ALL, + CYCLE_OUTPUT_CURSOR, + CYCLE_OUTPUT_FOCUSED, }; #endif /* LABWC_CONFIG_TYPES_H */ diff --git a/src/config/rcxml.c b/src/config/rcxml.c index bb868fb2..68c1135a 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1231,11 +1231,11 @@ entry(xmlNode *node, char *nodename, char *content) } } else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) { if (!strcasecmp(content, "all")) { - rc.window_switcher.osd.output_criteria = CYCLE_OSD_OUTPUT_ALL; + rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_ALL; } else if (!strcasecmp(content, "cursor")) { - rc.window_switcher.osd.output_criteria = CYCLE_OSD_OUTPUT_CURSOR; + rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_CURSOR; } else if (!strcasecmp(content, "focused")) { - rc.window_switcher.osd.output_criteria = CYCLE_OSD_OUTPUT_FOCUSED; + rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_FOCUSED; } else { wlr_log(WLR_ERROR, "Invalid windowSwitcher output '%s': " "should be one of all|focused|cursor", content); @@ -1486,7 +1486,7 @@ rcxml_init(void) rc.window_switcher.osd.show = true; rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC; - rc.window_switcher.osd.output_criteria = CYCLE_OSD_OUTPUT_ALL; + rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_ALL; rc.window_switcher.osd.thumbnail_label_format = xstrdup("%T"); rc.window_switcher.preview = true; rc.window_switcher.outlines = true; diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index a775f78c..f14bff9d 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -309,18 +309,18 @@ init_cycle(struct server *server) if (rc.window_switcher.osd.show) { /* Create OSD */ - switch (rc.window_switcher.osd.output_criteria) { - case CYCLE_OSD_OUTPUT_ALL: { + switch (rc.window_switcher.osd.output_filter) { + case CYCLE_OUTPUT_ALL: { struct output *output; wl_list_for_each(output, &server->outputs, link) { create_osd_on_output(output); } break; } - case CYCLE_OSD_OUTPUT_CURSOR: + case CYCLE_OUTPUT_CURSOR: create_osd_on_output(output_nearest_to_cursor(server)); break; - case CYCLE_OSD_OUTPUT_FOCUSED: { + case CYCLE_OUTPUT_FOCUSED: { struct output *output; if (server->active_view) { output = server->active_view->output; From c9b088e3438a0a24aa66913b9fb02cdcd62621bb Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 12 Dec 2025 04:27:32 +0900 Subject: [PATCH 44/50] cycle: add and use get_outputs_by_filter() This function can be reused for filtering windows to cycle through. --- src/cycle/cycle.c | 64 ++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index f14bff9d..067c9929 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -266,14 +266,36 @@ get_osd_impl(void) return NULL; } -static void -create_osd_on_output(struct output *output) +static uint64_t +get_outputs_by_filter(struct server *server, + enum cycle_output_filter output_filter) { - if (!output_is_usable(output)) { - return; + struct output *output = NULL; + + switch (output_filter) { + case CYCLE_OUTPUT_ALL: + break; + case CYCLE_OUTPUT_CURSOR: + output = output_nearest_to_cursor(server); + break; + case CYCLE_OUTPUT_FOCUSED: { + struct view *view = server->active_view; + if (view && output_is_usable(view->output)) { + output = view->output; + } else { + /* Fallback to pointer */ + output = output_nearest_to_cursor(server); + } + break; + } + } + + if (output) { + return output->id_bit; + } else { + /* bitmask for all outputs */ + return UINT64_MAX; } - get_osd_impl()->create(output); - assert(output->cycle_osd.tree); } static void @@ -309,28 +331,18 @@ init_cycle(struct server *server) if (rc.window_switcher.osd.show) { /* Create OSD */ - switch (rc.window_switcher.osd.output_filter) { - case CYCLE_OUTPUT_ALL: { - struct output *output; - wl_list_for_each(output, &server->outputs, link) { - create_osd_on_output(output); + uint64_t osd_outputs = get_outputs_by_filter(server, + rc.window_switcher.osd.output_filter); + struct output *output; + wl_list_for_each(output, &server->outputs, link) { + if (!(osd_outputs & output->id_bit)) { + continue; } - break; - } - case CYCLE_OUTPUT_CURSOR: - create_osd_on_output(output_nearest_to_cursor(server)); - break; - case CYCLE_OUTPUT_FOCUSED: { - struct output *output; - if (server->active_view) { - output = server->active_view->output; - } else { - /* Fallback to pointer, if there is no active_view */ - output = output_nearest_to_cursor(server); + if (!output_is_usable(output)) { + continue; } - create_osd_on_output(output); - break; - } + get_osd_impl()->create(output); + assert(output->cycle_osd.tree); } } From 64aec6ff5d7c6efc6a128ab84e2468e64e6e4c28 Mon Sep 17 00:00:00 2001 From: Cameron Scott McCreery Date: Mon, 22 Dec 2025 16:17:43 -0500 Subject: [PATCH 45/50] workspaces: add config option for initial workspace selection --- docs/labwc-config.5.scd | 5 +++ docs/rc.xml.all | 1 + include/config/rcxml.h | 1 + src/config/rcxml.c | 3 ++ src/workspaces.c | 83 +++++++++++++++++++++++++++-------------- 5 files changed, 64 insertions(+), 29 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 1c2a4af6..85363354 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -575,6 +575,11 @@ extending outward from the snapped edge. is 1. The number attribute is optional. If the number attribute is specified, names.name is not required. +** + Define the initial starting workspace. This must match one of the names + defined in or must be an index equal to or lower than . + If not set, the first workspace is used. + ** Define the timeout after which to hide the workspace OSD. A setting of 0 disables the OSD. Default is 1000 ms. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index ea4e30ac..a99aa997 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -175,6 +175,7 @@ Workspaces can be configured like this: 1000 + Workspace 1 Workspace 1 Workspace 2 diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 65b07172..8fd11aa0 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -168,6 +168,7 @@ struct rcxml { struct { int popuptime; int min_nr_workspaces; + char *initial_workspace_name; char *prefix; struct wl_list workspaces; /* struct workspace.link */ } workspace_config; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 68c1135a..60e709f2 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1308,6 +1308,8 @@ entry(xmlNode *node, char *nodename, char *content) wl_list_append(&rc.workspace_config.workspaces, &workspace->link); } else if (!strcasecmp(nodename, "popupTime.desktops")) { rc.workspace_config.popuptime = atoi(content); + } else if (!strcasecmp(nodename, "initial.desktops")) { + xstrdup_replace(rc.workspace_config.initial_workspace_name, content); } else if (!strcasecmp(nodename, "number.desktops")) { rc.workspace_config.min_nr_workspaces = MAX(1, atoi(content)); } else if (!strcasecmp(nodename, "popupShow.resize")) { @@ -1963,6 +1965,7 @@ rcxml_finish(void) zfree(rc.icon_theme_name); zfree(rc.fallback_app_icon_name); zfree(rc.workspace_config.prefix); + zfree(rc.workspace_config.initial_workspace_name); zfree(rc.tablet.output_name); zfree(rc.window_switcher.osd.thumbnail_label_format); diff --git a/src/workspaces.c b/src/workspaces.c index f56d170a..7b0b8e96 100644 --- a/src/workspaces.c +++ b/src/workspaces.c @@ -182,6 +182,33 @@ _osd_update(struct server *server) } } +static struct workspace * +workspace_find_by_name(struct server *server, const char *name) +{ + struct workspace *workspace; + + /* by index */ + size_t parsed_index = parse_workspace_index(name); + if (parsed_index) { + size_t index = 0; + wl_list_for_each(workspace, &server->workspaces.all, link) { + if (parsed_index == ++index) { + return workspace; + } + } + } + + /* by name */ + wl_list_for_each(workspace, &server->workspaces.all, link) { + if (!strcmp(workspace->name, name)) { + return workspace; + } + } + + wlr_log(WLR_ERROR, "Workspace '%s' not found", name); + return NULL; +} + /* cosmic workspace handlers */ static void handle_cosmic_workspace_activate(struct wl_listener *listener, void *data) @@ -209,18 +236,11 @@ add_workspace(struct server *server, const char *name) workspace->name = xstrdup(name); workspace->tree = wlr_scene_tree_create(server->view_tree); wl_list_append(&server->workspaces.all, &workspace->link); - if (!server->workspaces.current) { - server->workspaces.current = workspace; - } else { - wlr_scene_node_set_enabled(&workspace->tree->node, false); - } - - bool active = server->workspaces.current == workspace; + wlr_scene_node_set_enabled(&workspace->tree->node, false); /* cosmic */ workspace->cosmic_workspace = lab_cosmic_workspace_create(server->workspaces.cosmic_group); lab_cosmic_workspace_set_name(workspace->cosmic_workspace, name); - lab_cosmic_workspace_set_active(workspace->cosmic_workspace, active); workspace->on_cosmic.activate.notify = handle_cosmic_workspace_activate; wl_signal_add(&workspace->cosmic_workspace->events.activate, @@ -231,7 +251,6 @@ add_workspace(struct server *server, const char *name) server->workspaces.ext_manager, /*id*/ NULL); lab_ext_workspace_assign_to_group(workspace->ext_workspace, server->workspaces.ext_group); lab_ext_workspace_set_name(workspace->ext_workspace, name); - lab_ext_workspace_set_active(workspace->ext_workspace, active); workspace->on_ext.activate.notify = handle_ext_workspace_activate; wl_signal_add(&workspace->ext_workspace->events.activate, @@ -398,6 +417,27 @@ workspaces_init(struct server *server) wl_list_for_each(conf, &rc.workspace_config.workspaces, link) { add_workspace(server, conf->name); } + + /* + * After adding workspaces, check if there is an initial workspace + * selected and set that as the initial workspace. + */ + char *initial_name = rc.workspace_config.initial_workspace_name; + struct workspace *initial = NULL; + struct workspace *first = wl_container_of( + server->workspaces.all.next, first, link); + + if (initial_name) { + initial = workspace_find_by_name(server, initial_name); + } + if (!initial) { + initial = first; + } + + server->workspaces.current = initial; + wlr_scene_node_set_enabled(&initial->tree->node, true); + lab_cosmic_workspace_set_active(initial->cosmic_workspace, true); + lab_ext_workspace_set_active(initial->ext_workspace, true); } /* @@ -507,21 +547,13 @@ workspaces_find(struct workspace *anchor, const char *name, bool wrap) if (!name) { return NULL; } - size_t index = 0; - struct workspace *target; - size_t wants_index = parse_workspace_index(name); - struct wl_list *workspaces = &anchor->server->workspaces.all; + struct server *server = anchor->server; + struct wl_list *workspaces = &server->workspaces.all; - if (wants_index) { - wl_list_for_each(target, workspaces, link) { - if (wants_index == ++index) { - return target; - } - } - } else if (!strcasecmp(name, "current")) { + if (!strcasecmp(name, "current")) { return anchor; } else if (!strcasecmp(name, "last")) { - return anchor->server->workspaces.last; + return server->workspaces.last; } else if (!strcasecmp(name, "left")) { return get_prev(anchor, workspaces, wrap); } else if (!strcasecmp(name, "right")) { @@ -530,15 +562,8 @@ workspaces_find(struct workspace *anchor, const char *name, bool wrap) return get_prev_occupied(anchor, workspaces, wrap); } else if (!strcasecmp(name, "right-occupied")) { return get_next_occupied(anchor, workspaces, wrap); - } else { - wl_list_for_each(target, workspaces, link) { - if (!strcasecmp(target->name, name)) { - return target; - } - } } - wlr_log(WLR_ERROR, "Workspace '%s' not found", name); - return NULL; + return workspace_find_by_name(server, name); } static void From caa9b90e80baf0773e697c116879ff808a4d239f Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Tue, 23 Dec 2025 12:04:50 +0100 Subject: [PATCH 46/50] docs: add example for GTK4 composing --- docs/environment | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/environment b/docs/environment index f9c59b17..abc9c8b4 100644 --- a/docs/environment +++ b/docs/environment @@ -22,6 +22,13 @@ # XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle # XKB_DEFAULT_OPTIONS=grp:shift_caps_toggle +## GTK4 started to require input methods like fcitx5 or ibus to handle +## simple compose sequences. If you do not use input methods, uncomment +## the following line to force GTK4 internal composing. For further +## information see https://labwc.github.io/integration.html#gtk +## +# GTK_IM_MODULE=simple + ## ## Set cursor theme and size. Find system icons themes with: ## `find /usr/share/icons/ -type d -name "cursors"` From a5c6ff499c7db6d405263c5fc6bed2c01b74366b Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 12 Dec 2025 04:10:54 +0900 Subject: [PATCH 47/50] cycle: support This commit deprecates and adds per-action argument . --- docs/labwc-actions.5.scd | 12 ++++++++---- docs/labwc-config.5.scd | 8 ++------ docs/rc.xml.all | 4 ++-- include/config/rcxml.h | 2 +- include/config/types.h | 5 +++++ include/cycle.h | 8 +++++++- include/labwc.h | 2 ++ src/action.c | 33 ++++++++++++++++++++++++--------- src/config/rcxml.c | 16 ++++++++++------ src/cycle/cycle.c | 23 +++++++++++++++++------ 10 files changed, 78 insertions(+), 35 deletions(-) diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 7788d551..56c840a6 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -125,13 +125,17 @@ Actions are used in menus and keyboard/mouse bindings. Resize and move the active window back to its untiled or unmaximized position if it had been maximized or tiled to a direction or region. -**++ -** +**++ +** Cycle focus to next/previous window, respectively. - Default keybind for NextWindow is Alt-Tab. + Default keybinds for NextWindow and PreviousWindow are Alt-Tab and + Shift-Alt-Tab. While cycling through windows, the arrow keys move the + selected window forwards/backwards and the escape key halts the cycling. - The arrow keys are used to move forwards/backwards while cycling. + *workspace* [all|current] + This determines whether to cycle through windows on all workspaces or the + current workspace. Default is "current". ** Re-load configuration and theme files. diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 85363354..a1cedb20 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -339,7 +339,7 @@ this is for compatibility with Openbox. ## WINDOW SWITCHER ``` - + @@ -349,17 +349,13 @@ this is for compatibility with Openbox. ``` -** +** *preview* [yes|no] Preview the contents of the selected window when switching between windows. Default is yes. *outlines* [yes|no] Draw an outline around the selected window when switching between windows. Default is yes. - *allWorkspaces* [yes|no] Show windows regardless of what workspace - 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. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index a99aa997..dd348300 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -77,7 +77,7 @@ - + @@ -119,7 +119,7 @@ then workspace name, then identifier/app-id, then the window title. It uses 100% of OSD window width. - + diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 8fd11aa0..6036be9c 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -182,7 +182,7 @@ struct rcxml { bool outlines; bool unshade; enum window_switcher_order order; - enum lab_view_criteria criteria; + enum cycle_workspace_filter workspace_filter; /* deprecated */ struct { bool show; enum cycle_osd_style style; diff --git a/include/config/types.h b/include/config/types.h index 540f609f..81d8fd36 100644 --- a/include/config/types.h +++ b/include/config/types.h @@ -117,6 +117,11 @@ enum cycle_osd_style { CYCLE_OSD_STYLE_THUMBNAIL, }; +enum cycle_workspace_filter { + CYCLE_WORKSPACE_ALL, + CYCLE_WORKSPACE_CURRENT, +}; + enum cycle_output_filter { CYCLE_OUTPUT_ALL, CYCLE_OUTPUT_CURSOR, diff --git a/include/cycle.h b/include/cycle.h index ba58b88d..2f2c5a2a 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -4,6 +4,7 @@ #include #include +#include "config/types.h" struct output; @@ -42,13 +43,18 @@ struct cycle_osd_field { struct wl_list link; /* struct rcxml.window_switcher.osd.fields */ }; +struct cycle_filter { + enum cycle_workspace_filter workspace; +}; + struct buf; struct view; struct server; struct wlr_scene_node; /* Begin window switcher */ -void cycle_begin(struct server *server, enum lab_cycle_dir direction); +void cycle_begin(struct server *server, enum lab_cycle_dir direction, + struct cycle_filter filter); /* Cycle the selected view in the window switcher */ void cycle_step(struct server *server, enum lab_cycle_dir direction); diff --git a/include/labwc.h b/include/labwc.h index 3d3ca2a3..ee10af12 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -5,6 +5,7 @@ #include #include #include "common/set.h" +#include "cycle.h" #include "input/cursor.h" #include "overlay.h" @@ -310,6 +311,7 @@ struct server { struct wlr_scene_node *preview_node; struct wlr_scene_node *preview_dummy; struct lab_scene_rect *preview_outline; + struct cycle_filter filter; } cycle; struct theme *theme; diff --git a/src/action.c b/src/action.c index 7ddba0be..9fb7ca22 100644 --- a/src/action.c +++ b/src/action.c @@ -366,6 +366,20 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char goto cleanup; } break; + case ACTION_TYPE_NEXT_WINDOW: + case ACTION_TYPE_PREVIOUS_WINDOW: + if (!strcasecmp(argument, "workspace")) { + if (!strcasecmp(content, "all")) { + action_arg_add_int(action, argument, CYCLE_WORKSPACE_ALL); + } else if (!strcasecmp(content, "current")) { + action_arg_add_int(action, argument, CYCLE_WORKSPACE_CURRENT); + } else { + wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", + action_names[action->type], argument, content); + } + goto cleanup; + } + break; case ACTION_TYPE_SHOW_MENU: if (!strcmp(argument, "menu")) { action_arg_add_str(action, argument, content); @@ -1126,19 +1140,20 @@ run_action(struct view *view, struct server *server, struct action *action, } break; case ACTION_TYPE_NEXT_WINDOW: + case ACTION_TYPE_PREVIOUS_WINDOW: { + enum lab_cycle_dir dir = (action->type == ACTION_TYPE_NEXT_WINDOW) ? + LAB_CYCLE_DIR_FORWARD : LAB_CYCLE_DIR_BACKWARD; + struct cycle_filter filter = { + .workspace = action_get_int(action, "workspace", + rc.window_switcher.workspace_filter), + }; if (server->input_mode == LAB_INPUT_STATE_CYCLE) { - cycle_step(server, LAB_CYCLE_DIR_FORWARD); + cycle_step(server, dir); } else { - cycle_begin(server, LAB_CYCLE_DIR_FORWARD); - } - break; - case ACTION_TYPE_PREVIOUS_WINDOW: - if (server->input_mode == LAB_INPUT_STATE_CYCLE) { - cycle_step(server, LAB_CYCLE_DIR_BACKWARD); - } else { - cycle_begin(server, LAB_CYCLE_DIR_BACKWARD); + cycle_begin(server, dir, filter); } break; + } case ACTION_TYPE_RECONFIGURE: kill(getpid(), SIGHUP); break; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 60e709f2..00217596 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1269,10 +1269,16 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(nodename, "outlines.windowSwitcher")) { set_bool(content, &rc.window_switcher.outlines); } else if (!strcasecmp(nodename, "allWorkspaces.windowSwitcher")) { - if (parse_bool(content, -1) == true) { - rc.window_switcher.criteria &= - ~LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; + int ret = parse_bool(content, -1); + if (ret < 0) { + wlr_log(WLR_ERROR, "Invalid value for : '%s'", content); + } else { + rc.window_switcher.workspace_filter = ret ? + CYCLE_WORKSPACE_ALL : CYCLE_WORKSPACE_CURRENT; } + wlr_log(WLR_ERROR, " is deprecated." + " Use instead."); } else if (!strcasecmp(nodename, "unshade.windowSwitcher")) { set_bool(content, &rc.window_switcher.unshade); @@ -1493,9 +1499,7 @@ rcxml_init(void) 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; + rc.window_switcher.workspace_filter = CYCLE_WORKSPACE_CURRENT; rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS; rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER; diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 067c9929..9ae5b9b0 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -17,7 +17,7 @@ #include "theme.h" #include "view.h" -static bool init_cycle(struct server *server); +static bool init_cycle(struct server *server, struct cycle_filter filter); static void update_cycle(struct server *server); static void destroy_cycle(struct server *server); @@ -93,9 +93,10 @@ cycle_reinitialize(struct server *server) struct view *selected_view = cycle->selected_view; struct view *selected_view_prev = get_next_selected_view(server, LAB_CYCLE_DIR_BACKWARD); + struct cycle_filter filter = cycle->filter; destroy_cycle(server); - if (init_cycle(server)) { + if (init_cycle(server, filter)) { /* * Preserve the selected view (or its previous view) if it's * still in the cycle list @@ -152,13 +153,14 @@ restore_preview_node(struct server *server) } void -cycle_begin(struct server *server, enum lab_cycle_dir direction) +cycle_begin(struct server *server, enum lab_cycle_dir direction, + struct cycle_filter filter) { if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { return; } - if (!init_cycle(server)) { + if (!init_cycle(server, filter)) { return; } @@ -314,10 +316,17 @@ insert_view_ordered_by_age(struct wl_list *views, struct view *new_view) /* Return false on failure */ static bool -init_cycle(struct server *server) +init_cycle(struct server *server, struct cycle_filter filter) { + enum lab_view_criteria criteria = + LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER + | LAB_VIEW_CRITERIA_ROOT_TOPLEVEL; + if (filter.workspace == CYCLE_WORKSPACE_CURRENT) { + criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; + } + struct view *view; - for_each_view(view, &server->views, rc.window_switcher.criteria) { + for_each_view(view, &server->views, criteria) { if (rc.window_switcher.order == WINDOW_SWITCHER_ORDER_AGE) { insert_view_ordered_by_age(&server->cycle.views, view); } else { @@ -328,6 +337,7 @@ init_cycle(struct server *server) wlr_log(WLR_DEBUG, "no views to switch between"); return false; } + server->cycle.filter = filter; if (rc.window_switcher.osd.show) { /* Create OSD */ @@ -406,4 +416,5 @@ destroy_cycle(struct server *server) } server->cycle.selected_view = NULL; + server->cycle.filter = (struct cycle_filter){0}; } From 610d8695612953910256e84b8f58a166292dc9d4 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 12 Dec 2025 04:34:38 +0900 Subject: [PATCH 48/50] cycle: add output="all|focused|cursor" filters windows by the output they are on. identifier="all|current" filters windows by their app-id. --- docs/labwc-actions.5.scd | 12 ++++++++++-- docs/rc.xml.all | 2 +- include/config/types.h | 5 +++++ include/cycle.h | 2 ++ src/action.c | 28 ++++++++++++++++++++++++++++ src/cycle/cycle.c | 15 +++++++++++++++ 6 files changed, 61 insertions(+), 3 deletions(-) diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 56c840a6..fe468cd1 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -125,8 +125,8 @@ Actions are used in menus and keyboard/mouse bindings. Resize and move the active window back to its untiled or unmaximized position if it had been maximized or tiled to a direction or region. -**++ -** +**++ +** Cycle focus to next/previous window, respectively. Default keybinds for NextWindow and PreviousWindow are Alt-Tab and @@ -137,6 +137,14 @@ Actions are used in menus and keyboard/mouse bindings. This determines whether to cycle through windows on all workspaces or the current workspace. Default is "current". + *output* [all|focused|cursor] + This determines whether to cycle through windows on all outputs, the focused + output, or the output under the cursor. Default is "all". + + *identifier* [all|current] + This determines whether to cycle through all windows or only windows of the + same application as the currently focused window. Default is "all". + ** Re-load configuration and theme files. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index dd348300..bc9566fe 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -98,7 +98,7 @@ Some contents are fixed-length and others are variable-length. See "man 5 labwc-config" for details. - + diff --git a/include/config/types.h b/include/config/types.h index 81d8fd36..fc293cd8 100644 --- a/include/config/types.h +++ b/include/config/types.h @@ -128,4 +128,9 @@ enum cycle_output_filter { CYCLE_OUTPUT_FOCUSED, }; +enum cycle_app_id_filter { + CYCLE_APP_ID_ALL, + CYCLE_APP_ID_CURRENT, +}; + #endif /* LABWC_CONFIG_TYPES_H */ diff --git a/include/cycle.h b/include/cycle.h index 2f2c5a2a..d5430cb4 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -45,6 +45,8 @@ struct cycle_osd_field { struct cycle_filter { enum cycle_workspace_filter workspace; + enum cycle_output_filter output; + enum cycle_app_id_filter app_id; }; struct buf; diff --git a/src/action.c b/src/action.c index 9fb7ca22..5a01d157 100644 --- a/src/action.c +++ b/src/action.c @@ -379,6 +379,30 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char } goto cleanup; } + if (!strcasecmp(argument, "output")) { + if (!strcasecmp(content, "all")) { + action_arg_add_int(action, argument, CYCLE_OUTPUT_ALL); + } else if (!strcasecmp(content, "cursor")) { + action_arg_add_int(action, argument, CYCLE_OUTPUT_CURSOR); + } else if (!strcasecmp(content, "focused")) { + action_arg_add_int(action, argument, CYCLE_OUTPUT_FOCUSED); + } else { + wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", + action_names[action->type], argument, content); + } + goto cleanup; + } + if (!strcasecmp(argument, "identifier")) { + if (!strcasecmp(content, "all")) { + action_arg_add_int(action, argument, CYCLE_APP_ID_ALL); + } else if (!strcasecmp(content, "current")) { + action_arg_add_int(action, argument, CYCLE_APP_ID_CURRENT); + } else { + wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", + action_names[action->type], argument, content); + } + goto cleanup; + } break; case ACTION_TYPE_SHOW_MENU: if (!strcmp(argument, "menu")) { @@ -1146,6 +1170,10 @@ run_action(struct view *view, struct server *server, struct action *action, struct cycle_filter filter = { .workspace = action_get_int(action, "workspace", rc.window_switcher.workspace_filter), + .output = action_get_int(action, "output", + CYCLE_OUTPUT_ALL), + .app_id = action_get_int(action, "identifier", + CYCLE_APP_ID_ALL), }; if (server->input_mode == LAB_INPUT_STATE_CYCLE) { cycle_step(server, dir); diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 9ae5b9b0..e0391f1d 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -325,8 +325,23 @@ init_cycle(struct server *server, struct cycle_filter filter) criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; } + uint64_t cycle_outputs = + get_outputs_by_filter(server, filter.output); + + const char *cycle_app_id = NULL; + if (filter.app_id == CYCLE_APP_ID_CURRENT && server->active_view) { + cycle_app_id = server->active_view->app_id; + } + struct view *view; for_each_view(view, &server->views, criteria) { + if (!(cycle_outputs & view->output->id_bit)) { + continue; + } + if (cycle_app_id && strcmp(view->app_id, cycle_app_id) != 0) { + continue; + } + if (rc.window_switcher.order == WINDOW_SWITCHER_ORDER_AGE) { insert_view_ordered_by_age(&server->cycle.views, view); } else { From ec9579fdc19749ac204ca4150920bb9606f08242 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Fri, 26 Dec 2025 16:07:27 +0100 Subject: [PATCH 49/50] CI: disable IRC notifications for now Libera started requiring an account for the IP ranges of GH CI. This currently results in the IRC notification job running for more than one hour because the IRC client it uses doesn't terminate properly after giving up and thus eats our CI minutes. The patch disables the notifications for now until we figure out a better way, either by registering a libera account for the bot and supplying the credentials via secret or by changing to GH webhooks to a publicly available service like pipe.pico.sh which then can be listened to by our existing IRC bot. --- .github/workflows/{irc.yml => irc.yml.disabled} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{irc.yml => irc.yml.disabled} (100%) diff --git a/.github/workflows/irc.yml b/.github/workflows/irc.yml.disabled similarity index 100% rename from .github/workflows/irc.yml rename to .github/workflows/irc.yml.disabled From 276d4e61f9ebbd6cbb74632c6ff50cc5a8556274 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Fri, 26 Dec 2025 18:06:21 +0100 Subject: [PATCH 50/50] config/rcxml.c: prevent wrong parse_bool() err message by only parsing the boolean when it is not `disabledOnExternalMouse`. Fixes: #3293 --- src/config/rcxml.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 00217596..c1b73c35 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -684,6 +684,10 @@ get_send_events_mode(const char *s) goto err; } + if (!strcasecmp(s, "disabledOnExternalMouse")) { + return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; + } + int ret = parse_bool(s, -1); if (ret >= 0) { return ret @@ -691,10 +695,6 @@ get_send_events_mode(const char *s) : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } - if (!strcasecmp(s, "disabledOnExternalMouse")) { - return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; - } - err: wlr_log(WLR_INFO, "Not a recognised send events mode"); return -1;