window-rules: implement type filter

Co-Authored-By: Grigory Kirillov <txgk@bk.ru>
This commit is contained in:
Tobias Bengfort 2024-04-19 20:15:49 +02:00 committed by Consolatis
parent 9be18f3009
commit 858e1c65cf
9 changed files with 157 additions and 79 deletions

View file

@ -820,7 +820,7 @@ defined as shown below.
<windowRules> <windowRules>
<!-- Action --> <!-- Action -->
<windowRule identifier="" title=""> <windowRule identifier="" title="" type="">
<action name=""/> <action name=""/>
</windowRule> </windowRule>
@ -832,10 +832,10 @@ defined as shown below.
*Criteria* *Criteria*
*<windowRules><windowRule identifier="" title="" matchOnce="">* *<windowRules><windowRule identifier="" title="" type="" matchOnce="">*
Define a window rule for any window which matches the criteria defined Define a window rule for any window which matches the criteria defined
by the attributes *identifier* or *title*. If both are defined, AND by the attributes *identifier*, *title*, or *type*. If more than one
logic is used, so both have to match. is defined, AND logic is used, so all have to match.
Matching against patterns with '\*' (wildcard) and '?' (joker) is Matching against patterns with '\*' (wildcard) and '?' (joker) is
supported. Pattern matching is case-insensitive. supported. Pattern matching is case-insensitive.
@ -844,6 +844,12 @@ defined as shown below.
*title* is the title of the window. *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 *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. first instance of the window with the specified identifier or title.

View file

@ -67,6 +67,26 @@ enum view_wants_focus {
VIEW_WANTS_FOCUS_OFFER, 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 view;
struct wlr_surface; struct wlr_surface;
@ -113,6 +133,8 @@ struct view_impl {
enum view_wants_focus (*wants_focus)(struct view *self); enum view_wants_focus (*wants_focus)(struct view *self);
/* returns true if view reserves space at screen edge */ /* returns true if view reserves space at screen edge */
bool (*has_strut_partial)(struct view *self); 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 { struct view {
@ -358,6 +380,7 @@ void view_array_append(struct server *server, struct wl_array *views,
enum lab_view_criteria criteria); enum lab_view_criteria criteria);
enum view_wants_focus view_wants_focus(struct view *view); 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 * view_edge_invert() - select the opposite of a provided edge

View file

@ -21,6 +21,7 @@ enum property {
struct window_rule { struct window_rule {
char *identifier; char *identifier;
char *title; char *title;
int window_type;
bool match_once; bool match_once;
enum window_rule_event event; enum window_rule_event event;

View file

@ -14,26 +14,6 @@ struct wlr_compositor;
struct wlr_output; struct wlr_output;
struct wlr_output_layout; 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[] = { static const char * const atom_names[] = {
"_NET_WM_WINDOW_TYPE_DESKTOP", "_NET_WM_WINDOW_TYPE_DESKTOP",
"_NET_WM_WINDOW_TYPE_DOCK", "_NET_WM_WINDOW_TYPE_DOCK",
@ -52,10 +32,10 @@ static const char * const atom_names[] = {
}; };
static_assert( static_assert(
ARRAY_SIZE(atom_names) == ATOM_LEN, ARRAY_SIZE(atom_names) == WINDOW_TYPE_LEN,
"Xwayland atoms out of sync"); "Xwayland atoms out of sync");
extern xcb_atom_t atoms[ATOM_LEN]; extern xcb_atom_t atoms[WINDOW_TYPE_LEN];
struct xwayland_unmanaged { struct xwayland_unmanaged {
struct server *server; 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); 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, void xwayland_server_init(struct server *server,
struct wlr_compositor *compositor); struct wlr_compositor *compositor);
void xwayland_server_finish(struct server *server); void xwayland_server_finish(struct server *server);

View file

@ -77,6 +77,45 @@ enum font_place {
static void load_default_key_bindings(void); static void load_default_key_bindings(void);
static void load_default_mouse_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 static void
fill_usable_area_override(char *nodename, char *content) fill_usable_area_override(char *nodename, char *content)
{ {
@ -127,6 +166,7 @@ fill_window_rule(char *nodename, char *content)
{ {
if (!strcasecmp(nodename, "windowRule.windowRules")) { if (!strcasecmp(nodename, "windowRule.windowRules")) {
current_window_rule = znew(*current_window_rule); current_window_rule = znew(*current_window_rule);
current_window_rule->window_type = -1; // Window types are >= 0
wl_list_append(&rc.window_rules, &current_window_rule->link); wl_list_append(&rc.window_rules, &current_window_rule->link);
wl_list_init(&current_window_rule->actions); wl_list_init(&current_window_rule->actions);
return; return;
@ -145,6 +185,8 @@ fill_window_rule(char *nodename, char *content)
} else if (!strcmp(nodename, "title")) { } else if (!strcmp(nodename, "title")) {
free(current_window_rule->title); free(current_window_rule->title);
current_window_rule->title = xstrdup(content); 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")) { } else if (!strcasecmp(nodename, "matchOnce")) {
set_bool(content, &current_window_rule->match_once); set_bool(content, &current_window_rule->match_once);
@ -1493,7 +1535,7 @@ validate(void)
/* Window-rule criteria */ /* Window-rule criteria */
struct window_rule *rule, *rule_tmp; struct window_rule *rule, *rule_tmp;
wl_list_for_each_safe(rule, rule_tmp, &rc.window_rules, link) { 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); wlr_log(WLR_ERROR, "Deleting rule %p as it has no criteria", rule);
rule_destroy(rule); rule_destroy(rule);
} }

View file

@ -211,6 +211,16 @@ view_wants_focus(struct view *view)
return VIEW_WANTS_FOCUS_ALWAYS; 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 bool
view_is_focusable(struct view *view) view_is_focusable(struct view *view)
{ {

View file

@ -14,63 +14,50 @@
#include "window-rules.h" #include "window-rules.h"
static bool 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; struct wl_list *views = &self->server->views;
const char *prop = NULL;
struct view *view; struct view *view;
wl_list_for_each(view, views, link) { wl_list_for_each(view, views, link) {
if (view == self) { if (view != self && matches_criteria(rule, view)) {
continue;
}
if (id) {
prop = view_get_string_prop(view, "app_id");
if (prop && !strcmp(prop, id)) {
return true; return true;
} }
} }
if (title) {
prop = view_get_string_prop(view, "title");
if (prop && !strcmp(prop, title)) {
return true;
}
}
}
return false; return false;
} }
/* Try to match against identifier AND title (if set) */
static bool static bool
view_matches_criteria(struct window_rule *rule, struct view *view) view_matches_criteria(struct window_rule *rule, struct view *view)
{ {
const char *id = view_get_string_prop(view, "app_id"); if (rule->match_once && other_instances_exist(rule, view)) {
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");
return false; return false;
} }
return matches_criteria(rule, view);
} }
void void

View file

@ -43,6 +43,28 @@ xdg_toplevel_from_view(struct view *view)
return xdg_surface->toplevel; 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 static void
handle_new_popup(struct wl_listener *listener, void *data) 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, .move_to_back = view_impl_move_to_back,
.get_root = xdg_toplevel_view_get_root, .get_root = xdg_toplevel_view_get_root,
.append_children = xdg_toplevel_view_append_children, .append_children = xdg_toplevel_view_append_children,
.contains_window_type = xdg_toplevel_view_contains_window_type,
}; };
static void static void

View file

@ -15,13 +15,13 @@
#include "workspaces.h" #include "workspaces.h"
#include "xwayland.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); static void xwayland_view_unmap(struct view *view, bool client_request);
bool static bool
xwayland_surface_contains_window_type( 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); assert(surface);
for (size_t i = 0; i < surface->window_type_len; i++) { for (size_t i = 0; i < surface->window_type_len; i++) {
@ -32,6 +32,14 @@ xwayland_surface_contains_window_type(
return false; 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 static struct view_size_hints
xwayland_view_get_size_hints(struct view *view) 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, .get_size_hints = xwayland_view_get_size_hints,
.wants_focus = xwayland_view_wants_focus, .wants_focus = xwayland_view_wants_focus,
.has_strut_partial = xwayland_view_has_strut_partial, .has_strut_partial = xwayland_view_has_strut_partial,
.contains_window_type = xwayland_view_contains_window_type,
}; };
void void
@ -926,15 +935,15 @@ sync_atoms(xcb_connection_t *xcb_conn)
assert(xcb_conn); assert(xcb_conn);
wlr_log(WLR_DEBUG, "Syncing X11 atoms"); 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 */ /* 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, cookies[i] = xcb_intern_atom(xcb_conn, 0,
strlen(atom_names[i]), atom_names[i]); 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_generic_error_t *err = NULL;
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply_t *reply =
xcb_intern_atom_reply(xcb_conn, cookies[i], &err); 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"); wlr_log(WLR_ERROR, "Failed to create xcb connection");
/* Just clear all existing atoms */ /* 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; atoms[i] = XCB_ATOM_NONE;
} }
return; return;