diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index e3c6fcf7..b9d9e4d1 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -820,7 +820,7 @@ defined as shown below. - + @@ -832,10 +832,10 @@ defined as shown below. *Criteria* -** +** 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. @@ -844,6 +844,12 @@ defined as shown below. *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. diff --git a/include/view.h b/include/view.h index 18efe9f3..0cc3cfbd 100644 --- a/include/view.h +++ b/include/view.h @@ -67,6 +67,26 @@ enum view_wants_focus { 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; @@ -113,6 +133,8 @@ struct view_impl { 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 { @@ -358,6 +380,7 @@ void view_array_append(struct server *server, struct wl_array *views, 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 diff --git a/include/window-rules.h b/include/window-rules.h index 626fb540..55741f4c 100644 --- a/include/window-rules.h +++ b/include/window-rules.h @@ -21,6 +21,7 @@ enum property { struct window_rule { char *identifier; char *title; + int window_type; bool match_once; enum window_rule_event event; diff --git a/include/xwayland.h b/include/xwayland.h index 6009ad9d..0072106e 100644 --- a/include/xwayland.h +++ b/include/xwayland.h @@ -14,26 +14,6 @@ struct wlr_compositor; 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", @@ -52,10 +32,10 @@ static const char * const atom_names[] = { }; 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; @@ -105,9 +85,6 @@ void xwayland_adjust_stacking_order(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); diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 1b14c0a2..1262f035 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -77,6 +77,45 @@ enum font_place { 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) { @@ -127,6 +166,7 @@ fill_window_rule(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; @@ -145,6 +185,8 @@ fill_window_rule(char *nodename, char *content) } 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); @@ -1493,7 +1535,7 @@ validate(void) /* 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); } diff --git a/src/view.c b/src/view.c index 9e8ab703..19b5a33a 100644 --- a/src/view.c +++ b/src/view.c @@ -211,6 +211,16 @@ view_wants_focus(struct view *view) 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) { diff --git a/src/window-rules.c b/src/window-rules.c index 3ee83a5b..5c16c2ba 100644 --- a/src/window-rules.c +++ b/src/window-rules.c @@ -14,63 +14,50 @@ #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) +{ + const char *id = view_get_string_prop(view, "app_id"); + const char *title = view_get_string_prop(view, "title"); + + if (rule->identifier) { + if (!id || !match_glob(rule->identifier, id)) { + return false; + } + } + if (rule->title) { + if (!title || !match_glob(rule->title, title)) { + return false; + } + } + if (rule->window_type >= 0) { + if (!view_contains_window_type(view, rule->window_type)) { + return false; + } + } + return true; +} + +static bool +other_instances_exist(struct window_rule *rule, struct view *self) { 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; - } + if (view != self && matches_criteria(rule, view)) { + 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; - } - 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"); + if (rule->match_once && other_instances_exist(rule, view)) { return false; } + return matches_criteria(rule, view); } void diff --git a/src/xdg.c b/src/xdg.c index 4e6d6dad..154eb34f 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -43,6 +43,28 @@ xdg_toplevel_from_view(struct view *view) 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) { @@ -652,6 +674,7 @@ static const struct view_impl xdg_toplevel_view_impl = { .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 diff --git a/src/xwayland.c b/src/xwayland.c index 5b406522..5f90ead1 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -15,13 +15,13 @@ #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++) { @@ -32,6 +32,14 @@ xwayland_surface_contains_window_type( 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) { @@ -845,6 +853,7 @@ static const struct view_impl xwayland_view_impl = { .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 @@ -926,15 +935,15 @@ sync_atoms(xcb_connection_t *xcb_conn) 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); @@ -960,7 +969,7 @@ handle_server_ready(struct wl_listener *listener, void *data) 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;