]> git.mdlowis.com Git - proto/labwc.git/commitdiff
Support window-rules
authorJohan Malm <jgm323@gmail.com>
Fri, 28 Apr 2023 20:41:41 +0000 (21:41 +0100)
committerJohan Malm <johanmalm@users.noreply.github.com>
Thu, 4 May 2023 21:09:55 +0000 (22:09 +0100)
Two types of window rules are supported, actions and properties. They are
defined as shown below.

    <windowRules>

      <!-- Action -->
      <windowRule identifier="some-application">
        <action name="Maximize"/>
      </windowRule>

      <!-- Property -->
      <windowRule identifier="foo*" serverDecoration="yes|no"/>

    </windowRules>

Rules are applied if windows match the criteria defined by the
'identifier' attribute which relates to app_id for native Wayland windows
and WM_CLASS for XWayland clients.

Matching against patterns with '*' (wildcard) and '?' (joker) is
supported.

Add 'serverDecoration' property.

12 files changed:
docs/labwc-config.5.scd
docs/rc.xml.all
include/config/rcxml.h
include/window-rules.h [new file with mode: 0644]
src/common/match.c
src/config/rcxml.c
src/meson.build
src/view-impl-common.c
src/view.c
src/window-rules.c [new file with mode: 0644]
src/xdg.c
src/xwayland.c

index 14f680387c19cef43e5bd382a3196c8fd6f85468..be70b5967c4f7f9baf3fc82fa17b0f9e9000c08a 100644 (file)
@@ -330,6 +330,48 @@ The rest of this man page describes configuration options.
        any motion events while a keyboard is typing, and for a short while
        after as well.
 
+## WINDOW RULES
+
+Two types of window rules are supported, actions and properties. They are
+defined as shown below.
+
+```
+<windowRules>
+
+  <!-- Action -->
+  <windowRule identifier="">
+    <action name=""/>
+  </windowRule>
+
+  <!-- Property -->
+  <windowRule identifier="" serverDecoration="" />
+
+</windowRules>
+```
+
+*Actions*
+
+*<windowRules><windowRule identifier="">*
+       Define a window rule for any window which matches the criteria defined
+       by the attribute *identifier*. Matching against patterns with '\*'
+       (wildcard) and '?' (joker) is supported. Pattern matching is
+       case-insensitive.
+
+       *identifier* relates to app_id for native Wayland windows and WM_CLASS
+       for XWayland clients.
+
+*Properties*
+
+Property values can be *yes*, *no* or *default*.
+
+If a window matches criteria for multiple rules which set the same property,
+later config entries have higher priority. *default* can be useful in this
+situation.
+
+*<windowRules><windowRule serverDecoration="">* [yes|no|default]
+       *serverDecoration* over-rules any other setting for server-side window
+       decoration on first map.
+
 ## ENVIRONMENT VARIABLES
 
 *XCURSOR_THEME* and *XCURSOR_SIZE* are supported to set cursor theme
index 648bb5f34a1c508c0c726db95a803e32bb7e3bfb..3de44aae9109b5682694c34bf7cecb5656dd9f40 100644 (file)
     </device>
   </libinput>
 
+  <!--
+  <windowRules>
+    <windowRule identifier="*">
+      <action name="Maximize" />
+    </windowRule>
+    <windowRule identifier="some-application" serverDecoration="yes" />
+  </windowRules>
+  -->
+
 </labwc_config>
index 4679239782eacfa957bf94103378dcf37d3f5d27..ad50ec82b0385178b26979f53c49c71a0a0e6c4e 100644 (file)
@@ -80,6 +80,8 @@ struct rcxml {
                bool outlines;
                struct wl_list fields;  /* struct window_switcher_field.link */
        } window_switcher;
+
+       struct wl_list window_rules; /* struct window_rule.link */
 };
 
 extern struct rcxml rc;
diff --git a/include/window-rules.h b/include/window-rules.h
new file mode 100644 (file)
index 0000000..5d7f0c5
--- /dev/null
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __WINDOW_RULES_H
+#define __WINDOW_RULES_H
+
+enum window_rule_event {
+       LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP = 0,
+};
+
+enum property {
+       LAB_PROP_UNSPECIFIED = 0,
+       LAB_PROP_UNSET,
+       LAB_PROP_FALSE,
+       LAB_PROP_TRUE,
+};
+
+/*
+ * 'identifier' represents:
+ *   - 'app_id' for native Wayland windows
+ *   - 'WM_CLASS' for XWayland clients
+ */
+struct window_rule {
+       char *identifier;
+
+       enum window_rule_event event;
+       struct wl_list actions;
+
+       enum property server_decoration;
+
+       struct wl_list link; /* struct rcxml.window_rules */
+};
+
+struct view;
+
+void window_rules_apply(struct view *view, enum window_rule_event event);
+enum property window_rules_get_property(struct view *view, const char *property);
+
+#endif /* __WINDOW_RULES_H */
index 8b6ca19c1ea657e6e5ebca4be4fb50d1881c06f4..eb975f637b3bcb921e8dacc0eb006ec49b7c6f15 100644 (file)
@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0-only
 #define _POSIX_C_SOURCE 200809L
-#include <glib.h>
 #include <stdbool.h>
 #include "common/match.h"
 
index 283affc04005f9c99be4055a0ab006cc23a86471..37b5067c3a3ffae3dae4434964086cf76e566dab 100644 (file)
@@ -25,6 +25,7 @@
 #include "config/mousebind.h"
 #include "config/rcxml.h"
 #include "regions.h"
+#include "window-rules.h"
 #include "workspaces.h"
 
 static bool in_regions;
@@ -32,6 +33,7 @@ static bool in_keybind;
 static bool in_mousebind;
 static bool in_libinput_category;
 static bool in_window_switcher_field;
+static bool in_window_rules;
 
 static struct keybind *current_keybind;
 static struct mousebind *current_mousebind;
@@ -41,6 +43,8 @@ static struct action *current_keybind_action;
 static struct action *current_mousebind_action;
 static struct region *current_region;
 static struct window_switcher_field *current_field;
+static struct window_rule *current_window_rule;
+static struct action *current_window_rule_action;
 
 enum font_place {
        FONT_PLACE_NONE = 0,
@@ -54,6 +58,63 @@ enum font_place {
 static void load_default_key_bindings(void);
 static void load_default_mouse_bindings(void);
 
+/* Does a boolean-parse but also allows 'default' */
+static void
+set_property(const char *str, enum property *variable)
+{
+       if (!str || !strcasecmp(str, "default")) {
+               *variable = LAB_PROP_UNSET;
+               return;
+       }
+       int ret = parse_bool(str, -1);
+       if (ret < 0) {
+               return;
+       }
+       *variable = ret ? LAB_PROP_TRUE : LAB_PROP_FALSE;
+}
+
+static void
+fill_window_rule(char *nodename, char *content)
+{
+       if (!strcasecmp(nodename, "windowRule.windowRules")) {
+               current_window_rule = znew(*current_window_rule);
+               wl_list_append(&rc.window_rules, &current_window_rule->link);
+               wl_list_init(&current_window_rule->actions);
+               return;
+       }
+
+       string_truncate_at_pattern(nodename, ".windowrule.windowrules");
+       if (!content) {
+               /* nop */
+       } else if (!current_window_rule) {
+               wlr_log(WLR_ERROR, "no window-rule");
+       } else if (!strcmp(nodename, "identifier")) {
+               free(current_window_rule->identifier);
+               current_window_rule->identifier = xstrdup(content);
+       } else if (!strcmp(nodename, "event")) {
+               /*
+                * This is just in readiness for adding any other types of
+                * events in the future. We default to onFirstMap anyway.
+                */
+               if (!strcasecmp(content, "onFirstMap")) {
+                       current_window_rule->event = LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP;
+               }
+       } else if (!strcasecmp(nodename, "serverDecoration")) {
+               set_property(content, &current_window_rule->server_decoration);
+       } else if (!strcmp(nodename, "name.action")) {
+               current_window_rule_action = action_create(content);
+               if (current_window_rule_action) {
+                       wl_list_append(&current_window_rule->actions,
+                               &current_window_rule_action->link);
+               }
+       } else if (!current_window_rule_action) {
+               wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. "
+                       "nodename: '%s' content: '%s'", nodename, content);
+       } else {
+               action_arg_from_xml_node(current_window_rule_action, nodename, content);
+       }
+}
+
 static void
 fill_window_switcher_field(char *nodename, char *content)
 {
@@ -417,6 +478,10 @@ entry(xmlNode *node, char *nodename, char *content)
                fill_window_switcher_field(nodename, content);
                return;
        }
+       if (in_window_rules) {
+               fill_window_rule(nodename, content);
+               return;
+       }
 
        /* handle nodes without content, e.g. <keyboard><default /> */
        if (!strcmp(nodename, "default.keyboard")) {
@@ -600,6 +665,12 @@ xml_tree_walk(xmlNode *node)
                        in_window_switcher_field = false;
                        continue;
                }
+               if (!strcasecmp((char *)n->name, "windowRules")) {
+                       in_window_rules = true;
+                       traverse(n);
+                       in_window_rules = false;
+                       continue;
+               }
                traverse(n);
        }
 }
@@ -638,6 +709,7 @@ rcxml_init(void)
                wl_list_init(&rc.workspace_config.workspaces);
                wl_list_init(&rc.regions);
                wl_list_init(&rc.window_switcher.fields);
+               wl_list_init(&rc.window_rules);
        }
        has_run = true;
 
@@ -1059,6 +1131,14 @@ rcxml_finish(void)
                zfree(field);
        }
 
+       struct window_rule *rule, *rule_tmp;
+       wl_list_for_each_safe(rule, rule_tmp, &rc.window_rules, link) {
+               wl_list_remove(&rule->link);
+               zfree(rule->identifier);
+               action_list_free(&rule->actions);
+               zfree(rule);
+       }
+
        /* Reset state vars for starting fresh when Reload is triggered */
        current_keybind = NULL;
        current_mousebind = NULL;
@@ -1068,4 +1148,6 @@ rcxml_finish(void)
        current_mousebind_action = NULL;
        current_region = NULL;
        current_field = NULL;
+       current_window_rule = NULL;
+       current_window_rule_action = NULL;
 }
index 5087d331847c06ca1673a6888aa689afd2e2aab8..5aab9a621ef11a08430842b0c4d990f99e7d4c4c 100644 (file)
@@ -23,6 +23,7 @@ labwc_sources = files(
   'theme.c',
   'view.c',
   'view-impl-common.c',
+  'window-rules.c',
   'workspaces.c',
   'xdg.c',
   'xdg-popup.c',
index 98d92529e12801f21bbda33fad046c49b50c2ead..dacfc9b070c9f77b95120570b6c8facaffcb0a33 100644 (file)
@@ -6,6 +6,7 @@
 #include "labwc.h"
 #include "view.h"
 #include "view-impl-common.h"
+#include "window-rules.h"
 
 void
 view_impl_move_to_front(struct view *view)
@@ -26,6 +27,9 @@ view_impl_move_to_back(struct view *view)
 void
 view_impl_map(struct view *view)
 {
+       if (!view->been_mapped) {
+               window_rules_apply(view, LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP);
+       }
        desktop_focus_and_activate_view(&view->server->seat, view);
        view_move_to_front(view);
        view_update_title(view);
index 0f72887273c2509ffdc7453498a93a0b8df54414..ea7ce086d98cdcfba8a4c9c6d53dd6823f602579 100644 (file)
@@ -9,6 +9,7 @@
 #include "regions.h"
 #include "ssd.h"
 #include "view.h"
+#include "window-rules.h"
 #include "workspaces.h"
 #include "xwayland.h"
 
@@ -658,6 +659,7 @@ void
 view_set_decorations(struct view *view, bool decorations)
 {
        assert(view);
+
        if (view->ssd_enabled != decorations && !view->fullscreen) {
                /*
                 * Set view->ssd_enabled first since it is referenced
diff --git a/src/window-rules.c b/src/window-rules.c
new file mode 100644 (file)
index 0000000..5fb7631
--- /dev/null
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#define _POSIX_C_SOURCE 200809L
+#include "config.h"
+#include <assert.h>
+#include <cairo.h>
+#include <glib.h>
+#include <strings.h>
+#include <wlr/util/log.h>
+#include "action.h"
+#include "common/match.h"
+#include "config/rcxml.h"
+#include "labwc.h"
+#include "view.h"
+#include "window-rules.h"
+
+void
+window_rules_apply(struct view *view, enum window_rule_event event)
+{
+       const char *identifier = view_get_string_prop(view, "app_id");
+       if (!identifier) {
+               return;
+       }
+
+       struct window_rule *rule;
+       wl_list_for_each(rule, &rc.window_rules, link) {
+               if (!rule->identifier || rule->event != event) {
+                       continue;
+               }
+               if (match_glob(rule->identifier, identifier)) {
+                       actions_run(view, view->server, &rule->actions, 0);
+               }
+       }
+}
+
+enum property
+window_rules_get_property(struct view *view, const char *property)
+{
+       assert(property);
+
+       const char *identifier = view_get_string_prop(view, "app_id");
+       if (!identifier) {
+               goto out;
+       }
+
+       /*
+        * We iterate in reverse here because later items in list have higher
+        * priority. For example, in the config below we want the return value
+        * for foot's "serverDecoration" property to be "default".
+        *
+        *     <windowRules>
+        *       <windowRule identifier="*" serverDecoration="no"/>
+        *       <windowRule identifier="foot" serverDecoration="default"/>
+        *     </windowRules>
+        */
+       struct window_rule *rule;
+       wl_list_for_each_reverse(rule, &rc.window_rules, link) {
+               if (!rule->identifier) {
+                       continue;
+               }
+               /*
+                * Only return if property != LAB_PROP_UNSPECIFIED otherwise a
+                * <windowRule> which does not set a particular property
+                * attribute would still return here if that property was asked
+                * for.
+                */
+               if (match_glob(rule->identifier, identifier)) {
+                       if (!strcasecmp(property, "serverDecoration")) {
+                               if (rule->server_decoration) {
+                                       return rule->server_decoration;
+                               }
+                       }
+               }
+       }
+out:
+       return LAB_PROP_UNSPECIFIED;
+}
index 1afc1f227376d6dac4293e38ae9d4d9ef851fe10..6d5a9928260169fe4c313df68be181ec455aa0f7 100644 (file)
--- a/src/xdg.c
+++ b/src/xdg.c
@@ -6,6 +6,7 @@
 #include "node.h"
 #include "view.h"
 #include "view-impl-common.h"
+#include "window-rules.h"
 #include "workspaces.h"
 
 #define CONFIGURE_TIMEOUT_MS 100
@@ -49,6 +50,16 @@ handle_new_xdg_popup(struct wl_listener *listener, void *data)
 static bool
 has_ssd(struct view *view)
 {
+       /* Window-rules take priority if they exist for this view */
+       switch (window_rules_get_property(view, "serverDecoration")) {
+       case LAB_PROP_TRUE:
+               return true;
+       case LAB_PROP_FALSE:
+               return false;
+       default:
+               break;
+       }
+
        /*
         * view->ssd_preference may be set by the decoration implementation
         * e.g. src/decorations/xdg-deco.c or src/decorations/kde-deco.c.
@@ -425,13 +436,13 @@ xdg_toplevel_view_map(struct view *view)
                view->current.y = view->pending.y;
 
                view_moved(view);
-               view->been_mapped = true;
        }
 
        view->commit.notify = handle_commit;
        wl_signal_add(&xdg_surface->surface->events.commit, &view->commit);
 
        view_impl_map(view);
+       view->been_mapped = true;
 }
 
 static void
index 7a950310440916f4cd0987ee4a2b7e7a918b27e5..2684263e6aa51f69b0f7c4e127cd30cef97140c9 100644 (file)
@@ -9,6 +9,7 @@
 #include "ssd.h"
 #include "view.h"
 #include "view-impl-common.h"
+#include "window-rules.h"
 #include "workspaces.h"
 #include "xwayland.h"
 
@@ -103,6 +104,18 @@ ensure_initial_geometry_and_output(struct view *view)
 static bool
 want_deco(struct wlr_xwayland_surface *xwayland_surface)
 {
+       struct view *view = (struct view *)xwayland_surface->data;
+
+       /* Window-rules take priority if they exist for this view */
+       switch (window_rules_get_property(view, "serverDecoration")) {
+       case LAB_PROP_TRUE:
+               return true;
+       case LAB_PROP_FALSE:
+               return false;
+       default:
+               break;
+       }
+
        return xwayland_surface->decorations ==
                WLR_XWAYLAND_SURFACE_DECORATIONS_ALL;
 }
@@ -464,7 +477,6 @@ xwayland_view_map(struct view *view)
                 */
                view->current = view->pending;
                view_moved(view);
-               view->been_mapped = true;
        }
 
        if (view->ssd_enabled && view_is_floating(view)) {
@@ -476,6 +488,7 @@ xwayland_view_map(struct view *view)
        view->commit.notify = handle_commit;
 
        view_impl_map(view);
+       view->been_mapped = true;
 }
 
 static void