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
</device>
</libinput>
+ <!--
+ <windowRules>
+ <windowRule identifier="*">
+ <action name="Maximize" />
+ </windowRule>
+ <windowRule identifier="some-application" serverDecoration="yes" />
+ </windowRules>
+ -->
+
</labwc_config>
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;
--- /dev/null
+/* 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 */
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
-#include <glib.h>
#include <stdbool.h>
#include "common/match.h"
#include "config/mousebind.h"
#include "config/rcxml.h"
#include "regions.h"
+#include "window-rules.h"
#include "workspaces.h"
static bool in_regions;
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;
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,
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, ¤t_window_rule->link);
+ wl_list_init(¤t_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, ¤t_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(¤t_window_rule->actions,
+ ¤t_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)
{
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")) {
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);
}
}
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;
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;
current_mousebind_action = NULL;
current_region = NULL;
current_field = NULL;
+ current_window_rule = NULL;
+ current_window_rule_action = NULL;
}
'theme.c',
'view.c',
'view-impl-common.c',
+ 'window-rules.c',
'workspaces.c',
'xdg.c',
'xdg-popup.c',
#include "labwc.h"
#include "view.h"
#include "view-impl-common.h"
+#include "window-rules.h"
void
view_impl_move_to_front(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);
#include "regions.h"
#include "ssd.h"
#include "view.h"
+#include "window-rules.h"
#include "workspaces.h"
#include "xwayland.h"
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
--- /dev/null
+// 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;
+}
#include "node.h"
#include "view.h"
#include "view-impl-common.h"
+#include "window-rules.h"
#include "workspaces.h"
#define CONFIGURE_TIMEOUT_MS 100
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.
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
#include "ssd.h"
#include "view.h"
#include "view-impl-common.h"
+#include "window-rules.h"
#include "workspaces.h"
#include "xwayland.h"
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;
}
*/
view->current = view->pending;
view_moved(view);
- view->been_mapped = true;
}
if (view->ssd_enabled && view_is_floating(view)) {
view->commit.notify = handle_commit;
view_impl_map(view);
+ view->been_mapped = true;
}
static void