]> git.mdlowis.com Git - proto/labwc.git/commitdiff
window-rules: implement type filter
authorTobias Bengfort <tobias.bengfort@posteo.de>
Fri, 19 Apr 2024 18:15:49 +0000 (20:15 +0200)
committerConsolatis <35009135+Consolatis@users.noreply.github.com>
Sat, 20 Apr 2024 13:29:41 +0000 (15:29 +0200)
Co-Authored-By: Grigory Kirillov <txgk@bk.ru>
docs/labwc-config.5.scd
include/view.h
include/window-rules.h
include/xwayland.h
src/config/rcxml.c
src/view.c
src/window-rules.c
src/xdg.c
src/xwayland.c

index e3c6fcf7b1868deff5eb31f3ccb2ace2ba219f90..b9d9e4d125e4d89d560d72cdc6900766b84056a9 100644 (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.
 
index 18efe9f36f1c8f13bec09f7a4e068e0542d18156..0cc3cfbdadd404f874a5236711334874bd55593c 100644 (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
index 626fb540b85af47faf563f74b39fdf0ba2e8fde9..55741f4c0f4d19afb2e2da8ee2045e3cbb21c51e 100644 (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;
index 6009ad9d5abe0fdafab17692fe41c721f7944492..0072106e8c1060671d8d4085252572ca9cdcd70c 100644 (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);
index 1b14c0a2a80245b9e02c65f7515d6dcb4782466a..1262f035bdf507e9e550dc28e09ff0521d749b83 100644 (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);
                }
index 9e8ab7032784e4c9bc0a3101592a6028a123023c..19b5a33a19298526cf943b7a523be680db557378 100644 (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)
 {
index 3ee83a5b733276e7c6b857fc912c01adb55a3356..5c16c2bac4392af55b36beec526fb19d5b539b02 100644 (file)
 #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
index 4e6d6dad4923631276ec41b8bed31adf3cde8410..154eb34f66518b43fee277199d14b8b5496d8764 100644 (file)
--- 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
index 5b4065228c05cd3d8466903a46d0eb15a89bf7e6..5f90ead1d84bf61cf90c74617cf3c074f6827ad0 100644 (file)
 #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;