</windowRules>
```
-*Actions*
+*Criteria*
-*<windowRules><windowRule identifier="" title="">*
+*<windowRules><windowRule identifier="" title="" matchOnce="">*
Define a window rule for any window which matches the criteria defined
by the attributes *identifier* or *title*. If both are defined, AND
logic is used, so both have to match.
*title* is the title of the window.
+ *matchOnce* can be true|false. If true, the rule will only apply to the
+ first instance of the window with the specified identifier or title.
+
*Properties*
Property values can be *yes*, *no* or *default*.
</libinput>
<!--
- Window Rules
- - Criteria can consist of 'identifier' or 'title' or both (in which case
- AND logic is used).
- - 'identifier' relates to app_id for native Wayland windows and WM_CLASS
- for XWayland clients.
- - Matching against patterns with '*' (wildcard) and '?' (joker) is
- supported. Pattern matching is case-insensitive.
-
- <windowRules>
- <windowRule identifier="*"><action name="Maximize"/></windowRule>
- <windowRule identifier="foo" serverDecoration="yes"/>
- <windowRule title="bar" serverDecoration="yes"/>
- <windowRule identifier="baz" title="quax" serverDecoration="yes"/>
- </windowRules>
+ # Window Rules
+ # - Criteria can consist of 'identifier' or 'title' or both (in which case
+ # AND logic is used).
+ # - 'identifier' relates to app_id for native Wayland windows and WM_CLASS
+ # for XWayland clients.
+ # - Criteria can also contain `matchOnce="true"` meaning that the rule
+ # must only apply to the first instance of the window with that
+ # particular 'identifier' or 'title'.
+ # - Matching against patterns with '*' (wildcard) and '?' (joker) is
+ # supported. Pattern matching is case-insensitive.
+
+ <windowRules>
+ <windowRule identifier="*"><action name="Maximize"/></windowRule>
+ <windowRule identifier="foo" serverDecoration="yes"/>
+ <windowRule title="bar" serverDecoration="yes"/>
+ <windowRule identifier="baz" title="quax" serverDecoration="yes"/>
+ </windowRules>
+
+ # Example below for `lxqt-panel` and `pcmanfm-qt \-\-desktop`
+ # where 'matchOnce' is used to avoid applying rule to the panel configuration
+ # window with the same 'app_id'
+
+ <windowRules>
+ <windowRule identifier="lxqt-panel" matchOnce="true">
+ <skipTaskbar>yes</skipTaskbar>
+ <action name="MoveTo" x="0" y="0" />
+ <action name="ToggleAlwaysOnTop"/>
+ </windowRule>
+ <windowRule title="pcmanfm-desktop*">
+ <skipTaskbar>yes</skipTaskbar>
+ <skipWindowSwitcher>yes</skipWindowSwitcher>
+ <action name="MoveTo" x="0" y="0" />
+ <action name="ToggleAlwaysOnBottom"/>
+ </windowRule>
+ </windowRules>
-->
</labwc_config>
struct window_rule {
char *identifier;
char *title;
+ bool match_once;
enum window_rule_event event;
struct wl_list actions;
} else if (!strcmp(nodename, "title")) {
free(current_window_rule->title);
current_window_rule->title = xstrdup(content);
+ } else if (!strcasecmp(nodename, "matchOnce")) {
+ set_bool(content, ¤t_window_rule->match_once);
/* Event */
} else if (!strcmp(nodename, "event")) {
#include "view.h"
#include "window-rules.h"
+static bool
+other_instances_exist(struct view *self, const char *id, const char *title)
+{
+ struct wl_list *views = &self->server->views;
+ const char *prop = NULL;
+ struct view *view;
+
+ wl_list_for_each(view, views, link) {
+ if (view == self) {
+ continue;
+ }
+ if (id) {
+ prop = view_get_string_prop(view, "app_id");
+ if (prop && !strcmp(prop, id)) {
+ return true;
+ }
+ }
+ if (title) {
+ prop = view_get_string_prop(view, "title");
+ if (prop && !strcmp(prop, title)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
/* Try to match against identifier AND title (if set) */
static bool
view_matches_criteria(struct window_rule *rule, struct view *view)
const char *id = view_get_string_prop(view, "app_id");
const char *title = view_get_string_prop(view, "title");
+ if (rule->match_once && other_instances_exist(view, id, title)) {
+ return false;
+ }
+
if (rule->identifier && rule->title) {
if (!id || !title) {
return false;