Merge branch 'labwc:master' into menu_state

This commit is contained in:
David Barr 2025-08-26 19:11:13 +01:00 committed by GitHub
commit 06e3e712af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 188 additions and 142 deletions

View file

@ -14,13 +14,13 @@ _labnag_ [options...]
Create a button with the text _text_ that optionally executes _action_
when pressed. Multiple buttons can be defined by providing the flag
multiple times. Buttons will appear in the order they are provided from
lef to right.
left to right.
*-Z, --button-dismiss* <text> [<action>]
Create a button with the text _text_ that optionally executes _action_
when pressed, and dismisses labnag. Multiple buttons can be defined by
providing the flag multiple times. Buttons will appear in the order
they are provided from lef to right.
they are provided from left to right.
*-d, --debug*
Enable debugging.

View file

@ -171,6 +171,7 @@ this is for compatibility with Openbox.
```
<core>
<decoration>server</decoration>
<maximizedDecoration>titlebar</maximizedDecoration>
<gap>0</gap>
<adaptiveSync>no</adaptiveSync>
<allowTearing>no</allowTearing>
@ -186,6 +187,11 @@ this is for compatibility with Openbox.
that it is not always possible to turn off client side decorations.
Default is server.
*<core><maximizedDecoration>* [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.
*<core><gap>*
The distance in pixels between windows and output edges when using
movement actions, for example MoveToEdge. Default is 0.

View file

@ -11,6 +11,7 @@
<core>
<decoration>server</decoration>
<maximizedDecoration>titlebar</maximizedDecoration>
<gap>0</gap>
<adaptiveSync>no</adaptiveSync>
<allowTearing>no</allowTearing>

View file

@ -3,7 +3,7 @@
#define LABWC_DIRECTION_H
#include <wlr/types/wlr_output_layout.h>
#include "config/types.h"
#include "common/edge.h"
bool direction_from_edge(enum lab_edge edge, enum wlr_direction *direction);
enum wlr_direction direction_get_opposite(enum wlr_direction direction);

38
include/common/edge.h Normal file
View file

@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_EDGE_H
#define LABWC_EDGE_H
#include <wayland-server-core.h>
/**
* Represents an edge or direction (e.g. window tiling, window motion)
*/
enum lab_edge {
LAB_EDGE_INVALID = 0,
LAB_EDGE_LEFT = (1 << 0),
LAB_EDGE_RIGHT = (1 << 1),
LAB_EDGE_UP = (1 << 2),
LAB_EDGE_DOWN = (1 << 3),
LAB_EDGE_CENTER = (1 << 4), /* for window tiling */
LAB_EDGE_ANY = (1 << 5), /* for window rules */
/* for window tiling */
LAB_EDGE_UPLEFT = (LAB_EDGE_UP | LAB_EDGE_LEFT),
LAB_EDGE_UPRIGHT = (LAB_EDGE_UP | LAB_EDGE_RIGHT),
LAB_EDGE_DOWNLEFT = (LAB_EDGE_DOWN | LAB_EDGE_LEFT),
LAB_EDGE_DOWNRIGHT = (LAB_EDGE_DOWN | LAB_EDGE_RIGHT),
};
enum lab_edge lab_edge_parse(const char *direction, bool tiled, bool any);
/**
* lab_edge_invert() - select the opposite of a provided edge
*
* Returns LAB_EDGE_INVALID for edges other than UP/DOWN/LEFT/RIGHT.
*
* @edge: edge to be inverted
*/
enum lab_edge lab_edge_invert(enum lab_edge edge);
#endif /* LABWC_EDGE_H */

View file

@ -66,6 +66,7 @@ struct rcxml {
/* core */
bool xdg_shell_server_side_deco;
bool hide_maximized_window_titlebar;
int gap;
enum adaptive_sync_mode adaptive_sync;
enum tearing_mode allow_tearing;

View file

@ -11,28 +11,6 @@
* For the full config struct, see config/rcxml.h.
*/
/**
* Edges to which a view can be snapped. "Any" is used as
* a catch-all for every valid edge in order to simplify certain
* types of conditionals, but it is only valid for a selection
* of options in rc.xml.
*/
enum lab_edge {
LAB_EDGE_INVALID = 0,
LAB_EDGE_LEFT = (1 << 0),
LAB_EDGE_RIGHT = (1 << 1),
LAB_EDGE_UP = (1 << 2),
LAB_EDGE_DOWN = (1 << 3),
LAB_EDGE_CENTER = (1 << 4),
LAB_EDGE_ANY = (1 << 5),
LAB_EDGE_UPLEFT = (LAB_EDGE_UP | LAB_EDGE_LEFT),
LAB_EDGE_UPRIGHT = (LAB_EDGE_UP | LAB_EDGE_RIGHT),
LAB_EDGE_DOWNLEFT = (LAB_EDGE_DOWN | LAB_EDGE_LEFT),
LAB_EDGE_DOWNRIGHT = (LAB_EDGE_DOWN | LAB_EDGE_RIGHT),
};
/**
* Indicates whether tablet tool motion events should be reported using
* absolute or relative coordinates
@ -58,10 +36,10 @@ enum lab_rotation {
};
enum lab_ssd_mode {
LAB_SSD_MODE_INVALID,
LAB_SSD_MODE_NONE,
LAB_SSD_MODE_NONE = 0,
LAB_SSD_MODE_BORDER,
LAB_SSD_MODE_FULL,
LAB_SSD_MODE_INVALID,
};
enum lab_tristate {

View file

@ -7,6 +7,7 @@
#include <wayland-util.h>
#include <wlr/util/box.h>
#include <xkbcommon/xkbcommon.h>
#include "common/edge.h"
#include "config.h"
#include "config/types.h"
@ -169,8 +170,7 @@ struct view {
bool mapped;
bool been_mapped;
bool ssd_enabled;
bool ssd_titlebar_hidden;
enum lab_ssd_mode ssd_mode;
enum ssd_preference ssd_preference;
bool shaded;
bool minimized;
@ -422,15 +422,6 @@ void view_array_append(struct server *server, struct wl_array *views,
enum view_wants_focus view_wants_focus(struct view *view);
bool view_contains_window_type(struct view *view, enum lab_window_type window_type);
/**
* view_edge_invert() - select the opposite of a provided edge
*
* LAB_EDGE_CENTER and LAB_EDGE_INVALID both map to LAB_EDGE_INVALID.
*
* @edge: edge to be inverted
*/
enum lab_edge view_edge_invert(enum lab_edge edge);
/* If view is NULL, the size of SSD is not considered */
struct wlr_box view_get_edge_snap_box(struct view *view, struct output *output,
enum lab_edge edge);
@ -542,7 +533,7 @@ bool view_is_tiled(struct view *view);
bool view_is_tiled_and_notify_tiled(struct view *view);
bool view_is_floating(struct view *view);
void view_move_to_workspace(struct view *view, struct workspace *workspace);
enum lab_ssd_mode view_get_ssd_mode(struct view *view);
bool view_titlebar_visible(struct view *view);
void view_set_ssd_mode(struct view *view, enum lab_ssd_mode mode);
void view_set_decorations(struct view *view, enum lab_ssd_mode mode, bool force_ssd);
void view_toggle_fullscreen(struct view *view);
@ -601,7 +592,6 @@ void view_init(struct view *view);
void view_destroy(struct view *view);
enum view_axis view_axis_parse(const char *direction);
enum lab_edge view_edge_parse(const char *direction, bool tiled, bool any);
enum lab_placement_policy view_placement_parse(const char *policy);
/* xdg.c */

View file

@ -347,7 +347,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
if (!strcmp(argument, "direction")) {
bool tiled = (action->type == ACTION_TYPE_TOGGLE_SNAP_TO_EDGE
|| action->type == ACTION_TYPE_SNAP_TO_EDGE);
enum lab_edge edge = view_edge_parse(content, tiled, /*any*/ false);
enum lab_edge edge = lab_edge_parse(content, tiled, /*any*/ false);
if (edge == LAB_EDGE_INVALID) {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
@ -455,7 +455,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup;
}
if (!strcmp(argument, "direction")) {
enum lab_edge edge = view_edge_parse(content,
enum lab_edge edge = lab_edge_parse(content,
/*tiled*/ false, /*any*/ false);
if (edge == LAB_EDGE_INVALID) {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",

59
src/common/edge.c Normal file
View file

@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "common/edge.h"
#include <strings.h>
enum lab_edge
lab_edge_parse(const char *direction, bool tiled, bool any)
{
if (!direction) {
return LAB_EDGE_INVALID;
}
if (!strcasecmp(direction, "left")) {
return LAB_EDGE_LEFT;
} else if (!strcasecmp(direction, "up")) {
return LAB_EDGE_UP;
} else if (!strcasecmp(direction, "right")) {
return LAB_EDGE_RIGHT;
} else if (!strcasecmp(direction, "down")) {
return LAB_EDGE_DOWN;
}
if (any) {
if (!strcasecmp(direction, "any")) {
return LAB_EDGE_ANY;
}
}
if (tiled) {
if (!strcasecmp(direction, "center")) {
return LAB_EDGE_CENTER;
} else if (!strcasecmp(direction, "up-left")) {
return LAB_EDGE_UPLEFT;
} else if (!strcasecmp(direction, "up-right")) {
return LAB_EDGE_UPRIGHT;
} else if (!strcasecmp(direction, "down-left")) {
return LAB_EDGE_DOWNLEFT;
} else if (!strcasecmp(direction, "down-right")) {
return LAB_EDGE_DOWNRIGHT;
}
}
return LAB_EDGE_INVALID;
}
enum lab_edge
lab_edge_invert(enum lab_edge edge)
{
switch (edge) {
case LAB_EDGE_LEFT:
return LAB_EDGE_RIGHT;
case LAB_EDGE_RIGHT:
return LAB_EDGE_LEFT;
case LAB_EDGE_UP:
return LAB_EDGE_DOWN;
case LAB_EDGE_DOWN:
return LAB_EDGE_UP;
default:
return LAB_EDGE_INVALID;
}
}

View file

@ -3,6 +3,7 @@ labwc_sources += files(
'box.c',
'buf.c',
'dir.c',
'edge.c',
'fd-util.c',
'file-helpers.c',
'font.c',

View file

@ -444,7 +444,7 @@ fill_action_query(struct action *action, xmlNode *node, struct view_query *query
} else if (!strcasecmp(key, "omnipresent")) {
query->omnipresent = parse_tristate(content);
} else if (!strcasecmp(key, "tiled")) {
query->tiled = view_edge_parse(content,
query->tiled = lab_edge_parse(content,
/*tiled*/ true, /*any*/ true);
} else if (!strcasecmp(key, "tiled_region")) {
xstrdup_replace(query->tiled_region, content);
@ -1094,6 +1094,12 @@ entry(xmlNode *node, char *nodename, char *content)
} else {
rc.xdg_shell_server_side_deco = true;
}
} else if (!strcasecmp(nodename, "maximizedDecoration.core")) {
if (!strcasecmp(content, "titlebar")) {
rc.hide_maximized_window_titlebar = false;
} else if (!strcasecmp(content, "none")) {
rc.hide_maximized_window_titlebar = true;
}
} else if (!strcmp(nodename, "gap.core")) {
rc.gap = atoi(content);
} else if (!strcasecmp(nodename, "adaptiveSync.core")) {
@ -1370,6 +1376,7 @@ rcxml_init(void)
rc.placement_cascade_offset_y = 0;
rc.xdg_shell_server_side_deco = true;
rc.hide_maximized_window_titlebar = false;
rc.show_title = true;
rc.title_layout_loaded = false;
rc.ssd_keep_border = true;

View file

@ -90,9 +90,9 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
return;
}
/* Store natural geometry at start of move */
view_store_natural_geometry(view);
if (view_is_floating(view)) {
/* Store natural geometry at start of move */
view_store_natural_geometry(view);
view_invalidate_last_layout_geometry(view);
}

View file

@ -32,7 +32,7 @@ ssd_thickness(struct view *view)
* in border-only deco mode as view->ssd would only be set
* after ssd_create() returns.
*/
if (!view->ssd_enabled || view->fullscreen) {
if (!view->ssd_mode || view->fullscreen) {
return (struct border){ 0 };
}
@ -40,7 +40,7 @@ ssd_thickness(struct view *view)
if (view->maximized == VIEW_AXIS_BOTH) {
struct border thickness = { 0 };
if (!view->ssd_titlebar_hidden) {
if (view_titlebar_visible(view)) {
thickness.top += theme->titlebar_height;
}
return thickness;
@ -53,7 +53,7 @@ ssd_thickness(struct view *view)
.left = theme->border_width,
};
if (view->ssd_titlebar_hidden) {
if (!view_titlebar_visible(view)) {
thickness.top -= theme->titlebar_height;
}
return thickness;
@ -89,14 +89,14 @@ static enum ssd_part_type
get_resizing_type(const struct ssd *ssd, struct wlr_cursor *cursor)
{
struct view *view = ssd ? ssd->view : NULL;
if (!view || !cursor || !view->ssd_enabled || view->fullscreen) {
if (!view || !cursor || !view->ssd_mode || view->fullscreen) {
return LAB_SSD_NONE;
}
struct wlr_box view_box = view->current;
view_box.height = view_effective_height(view, /* use_pending */ false);
if (!view->ssd_titlebar_hidden) {
if (view_titlebar_visible(view)) {
/* If the titlebar is visible, consider it part of the view */
int titlebar_height = view->server->theme->titlebar_height;
view_box.y -= titlebar_height;
@ -250,7 +250,7 @@ ssd_create(struct view *view, bool active)
*/
ssd_titlebar_create(ssd);
ssd_border_create(ssd);
if (view->ssd_titlebar_hidden) {
if (!view_titlebar_visible(view)) {
/* Ensure we keep the old state on Reconfigure or when exiting fullscreen */
ssd_set_titlebar(ssd, false);
}
@ -312,6 +312,12 @@ ssd_update_geometry(struct ssd *ssd)
|| ssd->state.was_squared != squared
|| ssd->state.was_omnipresent != view->visible_on_all_workspaces;
/*
* (Un)maximization updates titlebar visibility with
* maximizedDecoration=none
*/
ssd_set_titlebar(ssd, view_titlebar_visible(view));
if (update_extents) {
ssd_extents_update(ssd);
}

View file

@ -78,8 +78,10 @@ struct view_query *
view_query_create(void)
{
struct view_query *query = znew(*query);
/* Must be synced with view_matches_criteria() in window-rules.c */
query->window_type = -1;
query->maximized = VIEW_AXIS_INVALID;
query->decoration = LAB_SSD_MODE_INVALID;
return query;
}
@ -206,8 +208,8 @@ view_matches_query(struct view *view, struct view_query *query)
}
}
enum lab_ssd_mode decor = view_get_ssd_mode(view);
if (query->decoration != LAB_SSD_MODE_INVALID && query->decoration != decor) {
if (query->decoration != LAB_SSD_MODE_INVALID
&& query->decoration != view->ssd_mode) {
return false;
}
@ -429,25 +431,6 @@ view_offer_focus(struct view *view)
* They may be called repeatably during output layout changes.
*/
enum lab_edge
view_edge_invert(enum lab_edge edge)
{
switch (edge) {
case LAB_EDGE_LEFT:
return LAB_EDGE_RIGHT;
case LAB_EDGE_RIGHT:
return LAB_EDGE_LEFT;
case LAB_EDGE_UP:
return LAB_EDGE_DOWN;
case LAB_EDGE_DOWN:
return LAB_EDGE_UP;
case LAB_EDGE_CENTER:
case LAB_EDGE_INVALID:
default:
return LAB_EDGE_INVALID;
}
}
struct wlr_box
view_get_edge_snap_box(struct view *view, struct output *output,
enum lab_edge edge)
@ -989,8 +972,11 @@ void
view_store_natural_geometry(struct view *view)
{
assert(view);
if (!view_is_floating(view)) {
/* Do not overwrite the stored geometry with special cases */
/*
* Do not overwrite the stored geometry if fullscreen or tiled.
* Maximized views are handled on a per-axis basis (see below).
*/
if (view->fullscreen || view_is_tiled(view)) {
return;
}
@ -1001,7 +987,14 @@ view_store_natural_geometry(struct view *view)
* xdg-toplevel configure event, which means the application should
* choose its own size.
*/
view->natural_geometry = view->pending;
if (!(view->maximized & VIEW_AXIS_HORIZONTAL)) {
view->natural_geometry.x = view->pending.x;
view->natural_geometry.width = view->pending.width;
}
if (!(view->maximized & VIEW_AXIS_VERTICAL)) {
view->natural_geometry.y = view->pending.y;
view->natural_geometry.height = view->pending.height;
}
}
int
@ -1335,7 +1328,7 @@ view_apply_maximized_geometry(struct view *view)
&natural.x, &natural.y);
}
if (view->ssd_enabled) {
if (view->ssd_mode) {
struct border border = ssd_thickness(view);
box.x += border.left;
box.y += border.top;
@ -1490,11 +1483,20 @@ view_maximize(struct view *view, enum view_axis axis,
*/
interactive_cancel(view);
if (store_natural_geometry && view_is_floating(view)) {
view_store_natural_geometry(view);
view_invalidate_last_layout_geometry(view);
}
}
/*
* Update natural geometry for any axis that wasn't already
* maximized. This is needed even when unmaximizing, because in
* single-axis cases the client may have resized the other axis
* while one axis was maximized.
*/
if (store_natural_geometry) {
view_store_natural_geometry(view);
}
/*
* When natural geometry is unknown (0x0) for an xdg-shell view,
* we normally send a configure event of 0x0 to get the client's
@ -1576,7 +1578,7 @@ view_set_decorations(struct view *view, enum lab_ssd_mode mode, bool force_ssd)
assert(view);
if (force_ssd || view_wants_decorations(view)
|| mode < view_get_ssd_mode(view)) {
|| mode < view->ssd_mode) {
view_set_ssd_mode(view, mode);
}
}
@ -1586,10 +1588,9 @@ view_toggle_decorations(struct view *view)
{
assert(view);
enum lab_ssd_mode mode = view_get_ssd_mode(view);
if (rc.ssd_keep_border && mode == LAB_SSD_MODE_FULL) {
if (rc.ssd_keep_border && view->ssd_mode == LAB_SSD_MODE_FULL) {
view_set_ssd_mode(view, LAB_SSD_MODE_BORDER);
} else if (mode != LAB_SSD_MODE_NONE) {
} else if (view->ssd_mode != LAB_SSD_MODE_NONE) {
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
} else {
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
@ -1676,18 +1677,14 @@ undecorate(struct view *view)
view->ssd = NULL;
}
enum lab_ssd_mode
view_get_ssd_mode(struct view *view)
bool
view_titlebar_visible(struct view *view)
{
assert(view);
if (!view->ssd_enabled) {
return LAB_SSD_MODE_NONE;
} else if (view->ssd_titlebar_hidden) {
return LAB_SSD_MODE_BORDER;
} else {
return LAB_SSD_MODE_FULL;
if (view->maximized == VIEW_AXIS_BOTH
&& rc.hide_maximized_window_titlebar) {
return false;
}
return view->ssd_mode == LAB_SSD_MODE_FULL;
}
void
@ -1696,7 +1693,7 @@ view_set_ssd_mode(struct view *view, enum lab_ssd_mode mode)
assert(view);
if (view->shaded || view->fullscreen
|| mode == view_get_ssd_mode(view)) {
|| mode == view->ssd_mode) {
return;
}
@ -1704,12 +1701,11 @@ view_set_ssd_mode(struct view *view, enum lab_ssd_mode mode)
* Set these first since they are referenced
* within the call tree of ssd_create() and ssd_thickness()
*/
view->ssd_enabled = mode != LAB_SSD_MODE_NONE;
view->ssd_titlebar_hidden = mode != LAB_SSD_MODE_FULL;
view->ssd_mode = mode;
if (view->ssd_enabled) {
if (mode) {
decorate(view);
ssd_set_titlebar(view->ssd, !view->ssd_titlebar_hidden);
ssd_set_titlebar(view->ssd, view_titlebar_visible(view));
} else {
undecorate(view);
}
@ -1737,7 +1733,7 @@ set_fullscreen(struct view *view, bool fullscreen)
}
/* Hide decorations when going fullscreen */
if (fullscreen && view->ssd_enabled) {
if (fullscreen && view->ssd_mode) {
undecorate(view);
}
@ -1749,7 +1745,7 @@ set_fullscreen(struct view *view, bool fullscreen)
wl_signal_emit_mutable(&view->events.fullscreened, NULL);
/* Re-show decorations when no longer fullscreen */
if (!fullscreen && view->ssd_enabled) {
if (!fullscreen && view->ssd_mode) {
decorate(view);
}
@ -2020,7 +2016,7 @@ view_move_to_edge(struct view *view, enum lab_edge direction, bool snap_to_windo
int destination_y = view->pending.y;
/* Compute the new position in the direction of motion */
direction = view_edge_invert(direction);
direction = lab_edge_invert(direction);
switch (direction) {
case LAB_EDGE_LEFT:
destination_x = left;
@ -2120,45 +2116,6 @@ view_axis_parse(const char *direction)
}
}
enum lab_edge
view_edge_parse(const char *direction, bool tiled, bool any)
{
if (!direction) {
return LAB_EDGE_INVALID;
}
if (!strcasecmp(direction, "left")) {
return LAB_EDGE_LEFT;
} else if (!strcasecmp(direction, "up")) {
return LAB_EDGE_UP;
} else if (!strcasecmp(direction, "right")) {
return LAB_EDGE_RIGHT;
} else if (!strcasecmp(direction, "down")) {
return LAB_EDGE_DOWN;
}
if (any) {
if (!strcasecmp(direction, "any")) {
return LAB_EDGE_ANY;
}
}
if (tiled) {
if (!strcasecmp(direction, "center")) {
return LAB_EDGE_CENTER;
} else if (!strcasecmp(direction, "up-left")) {
return LAB_EDGE_UPLEFT;
} else if (!strcasecmp(direction, "up-right")) {
return LAB_EDGE_UPRIGHT;
} else if (!strcasecmp(direction, "down-left")) {
return LAB_EDGE_DOWNLEFT;
} else if (!strcasecmp(direction, "down-right")) {
return LAB_EDGE_DOWNRIGHT;
}
}
return LAB_EDGE_INVALID;
}
enum lab_placement_policy
view_placement_parse(const char *policy)
{
@ -2215,7 +2172,7 @@ view_snap_to_edge(struct view *view, enum lab_edge edge,
}
/* When switching outputs, jump to the opposite edge */
edge = view_edge_invert(edge);
edge = lab_edge_invert(edge);
}
if (view->maximized != VIEW_AXIS_NONE) {
@ -2451,7 +2408,7 @@ void
view_reload_ssd(struct view *view)
{
assert(view);
if (view->ssd_enabled && !view->fullscreen) {
if (view->ssd_mode && !view->fullscreen) {
undecorate(view);
decorate(view);
}
@ -2474,7 +2431,7 @@ view_toggle_keybinds(struct view *view)
assert(view);
view->inhibits_keybinds = !view->inhibits_keybinds;
if (view->ssd_enabled) {
if (view->ssd_mode) {
ssd_enable_keybind_inhibit_indicator(view->ssd,
view->inhibits_keybinds);
}
@ -2556,7 +2513,7 @@ view_set_shade(struct view *view, bool shaded)
}
/* Views without a title-bar or SSD cannot be shaded */
if (shaded && (!view->ssd || view->ssd_titlebar_hidden)) {
if (shaded && (!view->ssd || !view_titlebar_visible(view))) {
return;
}

View file

@ -36,7 +36,9 @@ view_matches_criteria(struct window_rule *rule, struct view *view)
.window_type = rule->window_type,
.sandbox_engine = rule->sandbox_engine,
.sandbox_app_id = rule->sandbox_app_id,
/* Must be synced with view_query_create() */
.maximized = VIEW_AXIS_INVALID,
.decoration = LAB_SSD_MODE_INVALID,
};
if (rule->match_once && other_instances_exist(view, &query)) {