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>
<!-- Action -->
<windowRule identifier="" title="">
<windowRule identifier="" title="" type="">
<action name=""/>
</windowRule>
@ -832,10 +832,10 @@ defined as shown below.
*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.
@ -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.

View file

@ -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

View file

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

View file

@ -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);

View file

@ -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, &current_window_rule->link);
wl_list_init(&current_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, &current_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);
}

View file

@ -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)
{

View file

@ -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

View file

@ -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

View file

@ -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;