<windowRules>
<!-- Action -->
- <windowRule identifier="" title="">
+ <windowRule identifier="" title="" type="">
<action name=""/>
</windowRule>
*Criteria*
-*<windowRules><windowRule identifier="" title="" matchOnce="">*
+*<windowRules><windowRule identifier="" title="" type="" 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.
+ by the attributes *identifier*, *title*, or *type*. If more than one
+ is defined, AND logic is used, so all have to match.
Matching against patterns with '\*' (wildcard) and '?' (joker) is
supported. Pattern matching is case-insensitive.
*title* is the title of the window.
+ *type* [desktop|dock|toolbar|menu|utility|splash|dialog|dropdown_menu|
+ popup_menu|tooltip|notification|combo|dnd|normal] relates to
+ NET_WM_WINDOW_TYPE for XWayland clients. Native wayland clients have
+ type "dialog" when they have a parent or a fixed size, or "normal"
+ otherwise.
+
*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.
VIEW_WANTS_FOCUS_OFFER,
};
+enum window_type {
+ /* https://specifications.freedesktop.org/wm-spec/wm-spec-1.4.html#idm45649101374512 */
+ NET_WM_WINDOW_TYPE_DESKTOP = 0,
+ NET_WM_WINDOW_TYPE_DOCK,
+ NET_WM_WINDOW_TYPE_TOOLBAR,
+ NET_WM_WINDOW_TYPE_MENU,
+ NET_WM_WINDOW_TYPE_UTILITY,
+ NET_WM_WINDOW_TYPE_SPLASH,
+ NET_WM_WINDOW_TYPE_DIALOG,
+ NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
+ NET_WM_WINDOW_TYPE_POPUP_MENU,
+ NET_WM_WINDOW_TYPE_TOOLTIP,
+ NET_WM_WINDOW_TYPE_NOTIFICATION,
+ NET_WM_WINDOW_TYPE_COMBO,
+ NET_WM_WINDOW_TYPE_DND,
+ NET_WM_WINDOW_TYPE_NORMAL,
+
+ WINDOW_TYPE_LEN
+};
+
struct view;
struct wlr_surface;
enum view_wants_focus (*wants_focus)(struct view *self);
/* returns true if view reserves space at screen edge */
bool (*has_strut_partial)(struct view *self);
+ /* returns true if view declared itself a window type */
+ bool (*contains_window_type)(struct view *view, int32_t window_type);
};
struct view {
enum lab_view_criteria criteria);
enum view_wants_focus view_wants_focus(struct view *view);
+bool view_contains_window_type(struct view *view, enum window_type window_type);
/**
* view_edge_invert() - select the opposite of a provided edge
struct window_rule {
char *identifier;
char *title;
+ int window_type;
bool match_once;
enum window_rule_event event;
struct wlr_output;
struct wlr_output_layout;
-enum atom {
- /* https://specifications.freedesktop.org/wm-spec/wm-spec-1.4.html#idm45649101374512 */
- NET_WM_WINDOW_TYPE_DESKTOP = 0,
- NET_WM_WINDOW_TYPE_DOCK,
- NET_WM_WINDOW_TYPE_TOOLBAR,
- NET_WM_WINDOW_TYPE_MENU,
- NET_WM_WINDOW_TYPE_UTILITY,
- NET_WM_WINDOW_TYPE_SPLASH,
- NET_WM_WINDOW_TYPE_DIALOG,
- NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
- NET_WM_WINDOW_TYPE_POPUP_MENU,
- NET_WM_WINDOW_TYPE_TOOLTIP,
- NET_WM_WINDOW_TYPE_NOTIFICATION,
- NET_WM_WINDOW_TYPE_COMBO,
- NET_WM_WINDOW_TYPE_DND,
- NET_WM_WINDOW_TYPE_NORMAL,
-
- ATOM_LEN
-};
-
static const char * const atom_names[] = {
"_NET_WM_WINDOW_TYPE_DESKTOP",
"_NET_WM_WINDOW_TYPE_DOCK",
};
static_assert(
- ARRAY_SIZE(atom_names) == ATOM_LEN,
+ ARRAY_SIZE(atom_names) == WINDOW_TYPE_LEN,
"Xwayland atoms out of sync");
-extern xcb_atom_t atoms[ATOM_LEN];
+extern xcb_atom_t atoms[WINDOW_TYPE_LEN];
struct xwayland_unmanaged {
struct server *server;
struct wlr_xwayland_surface *xwayland_surface_from_view(struct view *view);
-bool xwayland_surface_contains_window_type(
- struct wlr_xwayland_surface *surface, enum atom window_type);
-
void xwayland_server_init(struct server *server,
struct wlr_compositor *compositor);
void xwayland_server_finish(struct server *server);
static void load_default_key_bindings(void);
static void load_default_mouse_bindings(void);
+static int
+parse_window_type(const char *type)
+{
+ if (!type) {
+ return -1;
+ }
+ if (!strcasecmp(type, "desktop")) {
+ return NET_WM_WINDOW_TYPE_DESKTOP;
+ } else if (!strcasecmp(type, "dock")) {
+ return NET_WM_WINDOW_TYPE_DOCK;
+ } else if (!strcasecmp(type, "toolbar")) {
+ return NET_WM_WINDOW_TYPE_TOOLBAR;
+ } else if (!strcasecmp(type, "menu")) {
+ return NET_WM_WINDOW_TYPE_MENU;
+ } else if (!strcasecmp(type, "utility")) {
+ return NET_WM_WINDOW_TYPE_UTILITY;
+ } else if (!strcasecmp(type, "splash")) {
+ return NET_WM_WINDOW_TYPE_SPLASH;
+ } else if (!strcasecmp(type, "dialog")) {
+ return NET_WM_WINDOW_TYPE_DIALOG;
+ } else if (!strcasecmp(type, "dropdown_menu")) {
+ return NET_WM_WINDOW_TYPE_DROPDOWN_MENU;
+ } else if (!strcasecmp(type, "popup_menu")) {
+ return NET_WM_WINDOW_TYPE_POPUP_MENU;
+ } else if (!strcasecmp(type, "tooltip")) {
+ return NET_WM_WINDOW_TYPE_TOOLTIP;
+ } else if (!strcasecmp(type, "notification")) {
+ return NET_WM_WINDOW_TYPE_NOTIFICATION;
+ } else if (!strcasecmp(type, "combo")) {
+ return NET_WM_WINDOW_TYPE_COMBO;
+ } else if (!strcasecmp(type, "dnd")) {
+ return NET_WM_WINDOW_TYPE_DND;
+ } else if (!strcasecmp(type, "normal")) {
+ return NET_WM_WINDOW_TYPE_NORMAL;
+ } else {
+ return -1;
+ }
+}
+
static void
fill_usable_area_override(char *nodename, char *content)
{
{
if (!strcasecmp(nodename, "windowRule.windowRules")) {
current_window_rule = znew(*current_window_rule);
+ current_window_rule->window_type = -1; // Window types are >= 0
wl_list_append(&rc.window_rules, ¤t_window_rule->link);
wl_list_init(¤t_window_rule->actions);
return;
} else if (!strcmp(nodename, "title")) {
free(current_window_rule->title);
current_window_rule->title = xstrdup(content);
+ } else if (!strcmp(nodename, "type")) {
+ current_window_rule->window_type = parse_window_type(content);
} else if (!strcasecmp(nodename, "matchOnce")) {
set_bool(content, ¤t_window_rule->match_once);
/* Window-rule criteria */
struct window_rule *rule, *rule_tmp;
wl_list_for_each_safe(rule, rule_tmp, &rc.window_rules, link) {
- if (!rule->identifier && !rule->title) {
+ if (!rule->identifier && !rule->title && rule->window_type < 0) {
wlr_log(WLR_ERROR, "Deleting rule %p as it has no criteria", rule);
rule_destroy(rule);
}
return VIEW_WANTS_FOCUS_ALWAYS;
}
+bool
+view_contains_window_type(struct view *view, enum window_type window_type)
+{
+ assert(view);
+ if (view->impl->contains_window_type) {
+ return view->impl->contains_window_type(view, window_type);
+ }
+ return false;
+}
+
bool
view_is_focusable(struct view *view)
{
#include "window-rules.h"
static bool
-other_instances_exist(struct view *self, const char *id, const char *title)
+matches_criteria(struct window_rule *rule, struct view *view)
{
- struct wl_list *views = &self->server->views;
- const char *prop = NULL;
- struct view *view;
+ const char *id = view_get_string_prop(view, "app_id");
+ const char *title = view_get_string_prop(view, "title");
- wl_list_for_each(view, views, link) {
- if (view == self) {
- continue;
+ if (rule->identifier) {
+ if (!id || !match_glob(rule->identifier, id)) {
+ return false;
}
- if (id) {
- prop = view_get_string_prop(view, "app_id");
- if (prop && !strcmp(prop, id)) {
- return true;
- }
+ }
+ if (rule->title) {
+ if (!title || !match_glob(rule->title, title)) {
+ return false;
}
- if (title) {
- prop = view_get_string_prop(view, "title");
- if (prop && !strcmp(prop, title)) {
- return true;
- }
+ }
+ if (rule->window_type >= 0) {
+ if (!view_contains_window_type(view, rule->window_type)) {
+ return false;
}
}
- return false;
+ return true;
}
-/* Try to match against identifier AND title (if set) */
static bool
-view_matches_criteria(struct window_rule *rule, struct view *view)
+other_instances_exist(struct window_rule *rule, struct view *self)
{
- const char *id = view_get_string_prop(view, "app_id");
- const char *title = view_get_string_prop(view, "title");
+ struct wl_list *views = &self->server->views;
+ struct view *view;
- if (rule->match_once && other_instances_exist(view, id, title)) {
- return false;
+ wl_list_for_each(view, views, link) {
+ if (view != self && matches_criteria(rule, view)) {
+ return true;
+ }
}
+ return false;
+}
- if (rule->identifier && rule->title) {
- if (!id || !title) {
- return false;
- }
- return match_glob(rule->identifier, id)
- && match_glob(rule->title, title);
- } else if (rule->identifier) {
- if (!id) {
- return false;
- }
- return match_glob(rule->identifier, id);
- } else if (rule->title) {
- if (!title) {
- return false;
- }
- return match_glob(rule->title, title);
- } else {
- wlr_log(WLR_ERROR, "rule has no identifier or title\n");
+static bool
+view_matches_criteria(struct window_rule *rule, struct view *view)
+{
+ if (rule->match_once && other_instances_exist(rule, view)) {
return false;
}
+ return matches_criteria(rule, view);
}
void
return xdg_surface->toplevel;
}
+static bool
+xdg_toplevel_view_contains_window_type(struct view *view, int32_t window_type)
+{
+ assert(view);
+
+ struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
+ struct wlr_xdg_toplevel_state *state = &toplevel->current;
+ bool is_dialog = (state->min_width != 0 && state->min_height != 0
+ && (state->min_width == state->max_width
+ || state->min_height == state->max_height))
+ || toplevel->parent;
+
+ switch (window_type) {
+ case NET_WM_WINDOW_TYPE_NORMAL:
+ return !is_dialog;
+ case NET_WM_WINDOW_TYPE_DIALOG:
+ return is_dialog;
+ default:
+ return false;
+ }
+}
+
static void
handle_new_popup(struct wl_listener *listener, void *data)
{
.move_to_back = view_impl_move_to_back,
.get_root = xdg_toplevel_view_get_root,
.append_children = xdg_toplevel_view_append_children,
+ .contains_window_type = xdg_toplevel_view_contains_window_type,
};
static void
#include "workspaces.h"
#include "xwayland.h"
-xcb_atom_t atoms[ATOM_LEN] = {0};
+xcb_atom_t atoms[WINDOW_TYPE_LEN] = {0};
static void xwayland_view_unmap(struct view *view, bool client_request);
-bool
+static bool
xwayland_surface_contains_window_type(
- struct wlr_xwayland_surface *surface, enum atom window_type)
+ struct wlr_xwayland_surface *surface, enum window_type window_type)
{
assert(surface);
for (size_t i = 0; i < surface->window_type_len; i++) {
return false;
}
+static bool
+xwayland_view_contains_window_type(struct view *view, int32_t window_type)
+{
+ assert(view);
+ struct wlr_xwayland_surface *surface = xwayland_surface_from_view(view);
+ return xwayland_surface_contains_window_type(surface, window_type);
+}
+
static struct view_size_hints
xwayland_view_get_size_hints(struct view *view)
{
.get_size_hints = xwayland_view_get_size_hints,
.wants_focus = xwayland_view_wants_focus,
.has_strut_partial = xwayland_view_has_strut_partial,
+ .contains_window_type = xwayland_view_contains_window_type,
};
void
assert(xcb_conn);
wlr_log(WLR_DEBUG, "Syncing X11 atoms");
- xcb_intern_atom_cookie_t cookies[ATOM_LEN];
+ xcb_intern_atom_cookie_t cookies[WINDOW_TYPE_LEN];
/* First request everything and then loop over the results to reduce latency */
- for (size_t i = 0; i < ATOM_LEN; i++) {
+ for (size_t i = 0; i < WINDOW_TYPE_LEN; i++) {
cookies[i] = xcb_intern_atom(xcb_conn, 0,
strlen(atom_names[i]), atom_names[i]);
}
- for (size_t i = 0; i < ATOM_LEN; i++) {
+ for (size_t i = 0; i < WINDOW_TYPE_LEN; i++) {
xcb_generic_error_t *err = NULL;
xcb_intern_atom_reply_t *reply =
xcb_intern_atom_reply(xcb_conn, cookies[i], &err);
wlr_log(WLR_ERROR, "Failed to create xcb connection");
/* Just clear all existing atoms */
- for (size_t i = 0; i < ATOM_LEN; i++) {
+ for (size_t i = 0; i < WINDOW_TYPE_LEN; i++) {
atoms[i] = XCB_ATOM_NONE;
}
return;