From a5db8e477aec3cb96044805d25454bd97af94342 Mon Sep 17 00:00:00 2001 From: Maik Broemme Date: Fri, 28 Nov 2025 20:51:00 +0100 Subject: [PATCH 01/14] 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 02/14] 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 03/14] 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 04/14] 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 05/14] 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 06/14] 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 07/14] 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 08/14] 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 09/14] 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 10/14] 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 11/14] 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 12/14] 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 13/14] 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 14/14] 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; }