From 0e57e5e4b494642e821b03c7eb1f77ab8b4bea6b Mon Sep 17 00:00:00 2001 From: Bernd Busse <bernd@busse-net.de> Date: Thu, 22 Oct 2020 21:21:54 +0200 Subject: [PATCH 1/3] c2: Perform matching against "all" property values if no index is specified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When matching against custom window properties or atoms perform the matching against all available values (currently hardcoded to a max. of 10) using logical OR if no index is specified. This should help when an atom has multiple values and you only want to check against any of these — e.g. hiding windows with state `hidden`: `--opacity-rule "0:_NET_WM_STATE@32a *= '_NET_WM_STATE_HIDDEN'"` — without having to explicitly specify each index separately. --- man/picom.1.asciidoc | 4 +- src/c2.c | 232 ++++++++++++++++++++++++++----------------- 2 files changed, 144 insertions(+), 92 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 2612ecebe0..466ab55b9a 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -274,11 +274,11 @@ With greater-than/less-than operators it looks like: 'NEGATION' (optional) is one or more exclamation marks; -'TARGET' is either a predefined target name, or the name of a window property to match. Supported predefined targets are `id`, `x`, `y`, `x2` (x + widthb), `y2`, `width`, `height`, `widthb` (width + 2 * `border_width`), `heightb`, `override_redirect`, `argb` (whether the window has an ARGB visual), `focused`, `wmwin` (whether the window looks like a WM window, i.e. has no child window with `WM_STATE` and is not override-redirected), `bounding_shaped`, `rounded_corners` (requires *--detect-rounded-corners*), `client` (ID of client window), `window_type` (window type in string), `leader` (ID of window leader), `name`, `class_g` (= `WM_CLASS[1]`), `class_i` (= `WM_CLASS[0]`), and `role`. +'TARGET' is either a predefined target name, or the name of a window property to match. Supported predefined targets are `id`, `x`, `y`, `x2` (`x` + `widthb`), `y2` (like `x2`), `width`, `height`, `widthb` (`width` + 2 * `border_width`), `heightb` (like `widthb`), `border_width`, `fullscreen`, `override_redirect`, `argb` (whether the window has an ARGB visual), `focused`, `wmwin` (whether the window looks like a WM window, i.e. has no child window with `WM_STATE` and is not override-redirected), `bounding_shaped`, `rounded_corners` (requires *--detect-rounded-corners*), `client` (ID of client window), `window_type` (window type in string), `leader` (ID of window leader), `name`, `class_g` (= `WM_CLASS[1]`), `class_i` (= `WM_CLASS[0]`), and `role`. 'CLIENT/FRAME' is a single `@` if the window attribute should be be looked up on client window, nothing if on frame window; -'INDEX' (optional) is the index number of the property to look up. For example, `[2]` means look at the third value in the property. Do not specify it for predefined targets. +'INDEX' (optional) is the index number of the property to look up. For example, `[2]` means look at the third value in the property. If not specified, matching is performed against all available property values using logical OR. Do not specify it for predefined targets. 'FORMAT' (optional) specifies the format of the property, 8, 16, or 32. On absence we use format X reports. Do not specify it for predefined or string targets. diff --git a/src/c2.c b/src/c2.c index 53b85c1142..860ef50d27 100644 --- a/src/c2.c +++ b/src/c2.c @@ -47,6 +47,7 @@ #pragma GCC diagnostic error "-Wunused-parameter" #define C2_MAX_LEVELS 10 +#define C2_MAX_PROPERTIES 10 typedef struct _c2_b c2_b_t; typedef struct _c2_l c2_l_t; @@ -214,17 +215,17 @@ static const c2_predef_t C2_PREDEFS[] = { /** * Get the numeric property value from a win_prop_t. */ -static inline long winprop_get_int(winprop_t prop) { +static inline long winprop_get_int(winprop_t prop, size_t index) { long tgt = 0; - if (!prop.nitems) { + if (!prop.nitems || index >= prop.nitems) { return 0; } switch (prop.format) { - case 8: tgt = *(prop.p8); break; - case 16: tgt = *(prop.p16); break; - case 32: tgt = *(prop.p32); break; + case 8: tgt = *(prop.p8 + index); break; + case 16: tgt = *(prop.p16 + index); break; + case 32: tgt = *(prop.p32 + index); break; default: assert(0); break; } @@ -1300,11 +1301,13 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w switch (pleaf->ptntype) { // Deal with integer patterns case C2_L_PTINT: { - long tgt = 0; + long targets[C2_MAX_PROPERTIES] = {0}; + size_t ntargets = 0; // Get the value // A predefined target if (pleaf->predef != C2_L_PUNDEFINED) { + long tgt = 0; *perr = false; switch (pleaf->predef) { case C2_L_PID: tgt = wid; break; @@ -1331,45 +1334,61 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w assert(0); break; } + ntargets = 1; + targets[0] = tgt; } // A raw window property else { + const int count = (pleaf->index < 0 ? C2_MAX_PROPERTIES : 1); + assert(count > 0); winprop_t prop = - x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, 1L, + x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, count, c2_get_atom_type(pleaf), pleaf->format); - if (prop.nitems) { + + ntargets = min2(prop.nitems, (size_t)count); + if (ntargets > 0) { *perr = false; - tgt = winprop_get_int(prop); + for (size_t i = 0; i < ntargets; ++i) { + targets[i] = winprop_get_int(prop, i); + } } free_winprop(&prop); } - if (*perr) + if (*perr) { return; + } // Do comparison - switch (pleaf->op) { - case C2_L_OEXISTS: - *pres = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true); - break; - case C2_L_OEQ: *pres = (tgt == pleaf->ptnint); break; - case C2_L_OGT: *pres = (tgt > pleaf->ptnint); break; - case C2_L_OGTEQ: *pres = (tgt >= pleaf->ptnint); break; - case C2_L_OLT: *pres = (tgt < pleaf->ptnint); break; - case C2_L_OLTEQ: *pres = (tgt <= pleaf->ptnint); break; - default: - *perr = true; - assert(0); - break; + bool res = false; + for (size_t i = 0; i < ntargets; ++i) { + long tgt = targets[i]; + switch (pleaf->op) { + case C2_L_OEXISTS: + res = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true); + break; + case C2_L_OEQ: res = (tgt == pleaf->ptnint); break; + case C2_L_OGT: res = (tgt > pleaf->ptnint); break; + case C2_L_OGTEQ: res = (tgt >= pleaf->ptnint); break; + case C2_L_OLT: res = (tgt < pleaf->ptnint); break; + case C2_L_OLTEQ: res = (tgt <= pleaf->ptnint); break; + default: *perr = true; assert(0); + } + if (res) { + break; + } } + *pres = res; } break; // String patterns case C2_L_PTSTRING: { - const char *tgt = NULL; - char *tgt_free = NULL; + const char *targets[C2_MAX_PROPERTIES] = {NULL}; + char *targets_free[C2_MAX_PROPERTIES] = {NULL}; + size_t ntargets = 0; // A predefined target if (pleaf->predef != C2_L_PUNDEFINED) { + const char *tgt = NULL; switch (pleaf->predef) { case C2_L_PWINDOWTYPE: tgt = WINTYPES[w->window_type]; break; case C2_L_PNAME: tgt = w->name; break; @@ -1378,95 +1397,128 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w case C2_L_PROLE: tgt = w->role; break; default: assert(0); break; } - } else if (pleaf->type == C2_L_TATOM) { - // An atom type property, convert it to string + ntargets = 1; + targets[0] = tgt; + } + // An atom type property, convert it to string + else if (pleaf->type == C2_L_TATOM) { + const int count = (pleaf->index < 0 ? C2_MAX_PROPERTIES : 1); + assert(count > 0); winprop_t prop = - x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, 1L, + x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, count, c2_get_atom_type(pleaf), pleaf->format); - xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop); - if (atom) { - xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply( - ps->c, xcb_get_atom_name(ps->c, atom), NULL); - if (reply) { - tgt_free = strndup( - xcb_get_atom_name_name(reply), - (size_t)xcb_get_atom_name_name_length(reply)); - free(reply); + + ntargets = min2(prop.nitems, (size_t)count); + for (size_t i = 0; i < ntargets; ++i) { + xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop, i); + if (atom) { + xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply( + ps->c, xcb_get_atom_name(ps->c, atom), NULL); + if (reply) { + targets[i] = targets_free[i] = strndup( + xcb_get_atom_name_name(reply), + (size_t)xcb_get_atom_name_name_length(reply)); + free(reply); + } } } - if (tgt_free) { - tgt = tgt_free; - } free_winprop(&prop); - } else { - // Not an atom type, just fetch the string list + } + // Not an atom type, just fetch the string list + else { char **strlst = NULL; - int nstr; - if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr) && - nstr > idx) { - tgt_free = strdup(strlst[idx]); - tgt = tgt_free; + int nstr = 0; + if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr)) { + if (pleaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) { + ntargets = min2((size_t)nstr, C2_MAX_PROPERTIES); + for (size_t i = 0; i < ntargets; ++i) { + targets[i] = targets_free[i] = + strdup(strlst[i]); + } + } else if (nstr > idx) { + ntargets = 1; + targets[0] = targets_free[0] = strdup(strlst[idx]); + } } if (strlst) { free(strlst); } } - if (tgt) { - *perr = false; - } else { + if (ntargets == 0) { return; } + for (size_t i = 0; i < ntargets; ++i) { + if (!targets[i]) { + goto fail; + } + } + *perr = false; // Actual matching - switch (pleaf->op) { - case C2_L_OEXISTS: *pres = true; break; - case C2_L_OEQ: - switch (pleaf->match) { - case C2_L_MEXACT: - if (pleaf->match_ignorecase) - *pres = !strcasecmp(tgt, pleaf->ptnstr); - else - *pres = !strcmp(tgt, pleaf->ptnstr); - break; - case C2_L_MCONTAINS: - if (pleaf->match_ignorecase) - *pres = strcasestr(tgt, pleaf->ptnstr); - else - *pres = strstr(tgt, pleaf->ptnstr); - break; - case C2_L_MSTART: - if (pleaf->match_ignorecase) - *pres = !strncasecmp(tgt, pleaf->ptnstr, - strlen(pleaf->ptnstr)); - else - *pres = !strncmp(tgt, pleaf->ptnstr, - strlen(pleaf->ptnstr)); - break; - case C2_L_MWILDCARD: { - int flags = 0; - if (pleaf->match_ignorecase) - flags |= FNM_CASEFOLD; - *pres = !fnmatch(pleaf->ptnstr, tgt, flags); - } break; - case C2_L_MPCRE: + bool res = false; + for (size_t i = 0; i < ntargets; ++i) { + const char *tgt = targets[i]; + switch (pleaf->op) { + case C2_L_OEXISTS: res = true; break; + case C2_L_OEQ: + switch (pleaf->match) { + case C2_L_MEXACT: + if (pleaf->match_ignorecase) { + res = !strcasecmp(tgt, pleaf->ptnstr); + } else { + res = !strcmp(tgt, pleaf->ptnstr); + } + break; + case C2_L_MCONTAINS: + if (pleaf->match_ignorecase) { + res = strcasestr(tgt, pleaf->ptnstr); + } else { + res = strstr(tgt, pleaf->ptnstr); + } + break; + case C2_L_MSTART: + if (pleaf->match_ignorecase) { + res = !strncasecmp(tgt, pleaf->ptnstr, + strlen(pleaf->ptnstr)); + } else { + res = !strncmp(tgt, pleaf->ptnstr, + strlen(pleaf->ptnstr)); + } + break; + case C2_L_MWILDCARD: { + int flags = 0; + if (pleaf->match_ignorecase) { + flags |= FNM_CASEFOLD; + } + res = !fnmatch(pleaf->ptnstr, tgt, flags); + } break; + case C2_L_MPCRE: #ifdef CONFIG_REGEX_PCRE - assert(strlen(tgt) <= INT_MAX); - *pres = - (pcre_exec(pleaf->regex_pcre, pleaf->regex_pcre_extra, - tgt, (int)strlen(tgt), 0, 0, NULL, 0) >= 0); + assert(strlen(tgt) <= INT_MAX); + res = (pcre_exec(pleaf->regex_pcre, + pleaf->regex_pcre_extra, tgt, + (int)strlen(tgt), 0, 0, NULL, 0) >= 0); #else - assert(0); + assert(0); #endif + break; + } + break; + default: *perr = true; assert(0); + } + if (res) { break; } - break; - default: *perr = true; assert(0); } + *pres = res; + fail: // Free the string after usage, if necessary - if (tgt_free) { - free(tgt_free); + for (size_t i = 0; i < ntargets; ++i) { + if (targets_free[i]) { + free(targets_free[i]); + } } } break; default: assert(0); break; From 9125693593dc6ca42a1700b9b9e554863ac8c492 Mon Sep 17 00:00:00 2001 From: Bernd Busse <bernd@busse-net.de> Date: Sun, 25 Oct 2020 20:57:52 +0100 Subject: [PATCH 2/3] win: cache `_NET_WM_STATE` property for each window Keep track of the `_NET_WM_STATE` property for each managed window. Use the cached state to determine if window is fullscreen instead of querying the Xserver every time. --- src/atom.h | 22 +++++++++++---- src/common.h | 12 ++++---- src/event.c | 8 ++++++ src/picom.c | 18 ++++++++++++ src/win.c | 76 ++++++++++++++++++++++++++++++-------------------- src/win.h | 2 ++ src/win_defs.h | 24 ++++++++++++++-- 7 files changed, 119 insertions(+), 43 deletions(-) diff --git a/src/atom.h b/src/atom.h index 6f4eae69f7..2e2668a6c6 100644 --- a/src/atom.h +++ b/src/atom.h @@ -3,8 +3,8 @@ #include <xcb/xcb.h> -#include "meta.h" #include "cache.h" +#include "meta.h" // clang-format off // Splitted into 2 lists because of the limitation of our macros @@ -23,9 +23,7 @@ WM_CLIENT_MACHINE, \ _NET_ACTIVE_WINDOW, \ _COMPTON_SHADOW, \ - _NET_WM_WINDOW_TYPE - -#define ATOM_LIST2 \ + _NET_WM_WINDOW_TYPE, \ _NET_WM_WINDOW_TYPE_DESKTOP, \ _NET_WM_WINDOW_TYPE_DOCK, \ _NET_WM_WINDOW_TYPE_TOOLBAR, \ @@ -39,9 +37,23 @@ _NET_WM_WINDOW_TYPE_TOOLTIP, \ _NET_WM_WINDOW_TYPE_NOTIFICATION, \ _NET_WM_WINDOW_TYPE_COMBO, \ - _NET_WM_WINDOW_TYPE_DND, \ + _NET_WM_WINDOW_TYPE_DND + +#define ATOM_LIST2 \ _NET_WM_STATE, \ + _NET_WM_STATE_MODAL, \ + _NET_WM_STATE_STICKY, \ + _NET_WM_STATE_MAXIMIZED_VERT, \ + _NET_WM_STATE_MAXIMIZED_HORZ, \ + _NET_WM_STATE_SHADED, \ + _NET_WM_STATE_SKIP_TASKBAR, \ + _NET_WM_STATE_SKIP_PAGER, \ + _NET_WM_STATE_HIDDEN, \ _NET_WM_STATE_FULLSCREEN, \ + _NET_WM_STATE_ABOVE, \ + _NET_WM_STATE_BELOW, \ + _NET_WM_STATE_DEMANDS_ATTENTION, \ + _NET_WM_STATE_FOCUSED, \ _NET_WM_BYPASS_COMPOSITOR, \ UTF8_STRING, \ C_STRING diff --git a/src/common.h b/src/common.h index 193bc2a36a..f1a4db66fb 100644 --- a/src/common.h +++ b/src/common.h @@ -36,9 +36,9 @@ #include <X11/Xlib.h> #include <ev.h> #include <pixman.h> -#include <xcb/xproto.h> #include <xcb/render.h> #include <xcb/sync.h> +#include <xcb/xproto.h> #include "uthash_extra.h" #ifdef CONFIG_OPENGL @@ -55,11 +55,11 @@ #include "backend/driver.h" #include "compiler.h" #include "config.h" +#include "list.h" #include "region.h" +#include "render.h" #include "types.h" #include "utils.h" -#include "list.h" -#include "render.h" #include "win_defs.h" #include "x.h" @@ -254,11 +254,11 @@ typedef struct session { // Cached blur convolution kernels. struct x_convolution_kernel **blur_kerns_cache; /// If we should quit - bool quit:1; + bool quit : 1; // TODO(yshui) use separate flags for dfferent kinds of updates so we don't // waste our time. /// Whether there are pending updates, like window creation, etc. - bool pending_updates:1; + bool pending_updates : 1; // === Expose event related === /// Pointer to an array of <code>XRectangle</code>-s of exposed region. @@ -369,6 +369,8 @@ typedef struct session { struct atom *atoms; /// Array of atoms of all possible window types. xcb_atom_t atoms_wintypes[NUM_WINTYPES]; + /// Array of atoms of all possible window wm_states. + xcb_atom_t atoms_wmstates[NUM_WMSTATES]; /// Linked list of additional atoms to track. latom_t *track_atom_lst; diff --git a/src/event.c b/src/event.c index 88bcf31a4b..52a57959ef 100644 --- a/src/event.c +++ b/src/event.c @@ -505,6 +505,14 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t } } + // If _NET_WM_STATE changes + if (ev->atom == ps->atoms->a_NET_WM_STATE) { + struct managed_win *w = NULL; + if ((w = find_toplevel(ps, ev->window))) { + win_set_property_stale(w, ev->atom); + } + } + if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) { // Unnecessay until we remove the queue_redraw in ev_handle queue_redraw(ps); diff --git a/src/picom.c b/src/picom.c index 17005c384a..49930de6ce 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1687,6 +1687,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .xrfilter_convolution_exists = false, .atoms_wintypes = {0}, + .atoms_wmstates = {0}, .track_atom_lst = NULL, #ifdef CONFIG_DBUS @@ -1877,6 +1878,23 @@ static session_t *session_init(int argc, char **argv, Display *dpy, SET_WM_TYPE_ATOM(COMBO); SET_WM_TYPE_ATOM(DND); #undef SET_WM_TYPE_ATOM + ps->atoms_wmstates[WMSTATE_UNKNOWN] = 0; +#define SET_WM_STATE_ATOM(x) \ + ps->atoms_wmstates[WMSTATE_##x] = ps->atoms->a_NET_WM_STATE_##x + SET_WM_STATE_ATOM(MODAL); + SET_WM_STATE_ATOM(STICKY); + SET_WM_STATE_ATOM(MAXIMIZED_VERT); + SET_WM_STATE_ATOM(MAXIMIZED_HORZ); + SET_WM_STATE_ATOM(SHADED); + SET_WM_STATE_ATOM(SKIP_TASKBAR); + SET_WM_STATE_ATOM(SKIP_PAGER); + SET_WM_STATE_ATOM(HIDDEN); + SET_WM_STATE_ATOM(FULLSCREEN); + SET_WM_STATE_ATOM(ABOVE); + SET_WM_STATE_ATOM(BELOW); + SET_WM_STATE_ATOM(DEMANDS_ATTENTION); + SET_WM_STATE_ATOM(FOCUSED); +#undef SET_WM_STATE_ATOM // Get needed atoms for c2 condition lists if (!(c2_list_postprocess(ps, ps->o.unredir_if_possible_blacklist) && diff --git a/src/win.c b/src/win.c index 5bdaacc83e..fbd12a7f79 100644 --- a/src/win.c +++ b/src/win.c @@ -61,6 +61,7 @@ static const double ROUNDED_PERCENT = 0.05; static bool win_update_class(session_t *ps, struct managed_win *w); static int win_update_role(session_t *ps, struct managed_win *w); static void win_update_wintype(session_t *ps, struct managed_win *w); +static void win_update_wm_state(session_t *ps, struct managed_win *w); static int win_update_name(session_t *ps, struct managed_win *w); /** * Reread opacity property of a window. @@ -362,6 +363,10 @@ static void win_update_properties(session_t *ps, struct managed_win *w) { win_update_wintype(ps, w); } + if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_STATE)) { + win_update_wm_state(ps, w); + } + if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_OPACITY)) { win_update_opacity_prop(ps, w); // we cannot receive OPACITY change when window has been destroyed @@ -643,6 +648,23 @@ static wintype_t wid_get_prop_wintype(session_t *ps, xcb_window_t wid) { return WINTYPE_UNKNOWN; } +static win_wmstate_t wid_get_prop_wm_state(session_t *ps, xcb_window_t wid) { + winprop_t prop = + x_get_prop(ps, wid, ps->atoms->a_NET_WM_STATE, 32L, XCB_ATOM_ATOM, 32); + + win_wmstate_t wm_state = WMSTATE_UNKNOWN; + for (unsigned i = 0; i < prop.nitems; ++i) { + for (enum wm_state j = 1; j < NUM_WMSTATES; ++j) { + if (ps->atoms_wmstates[j] == (xcb_atom_t)prop.p32[i]) { + wm_state |= (1 << (j - 1)); + } + } + } + + free_winprop(&prop); + return wm_state; +} + static bool wid_get_opacity_prop(session_t *ps, xcb_window_t wid, opacity_t def, opacity_t *out) { bool ret = false; @@ -1117,6 +1139,27 @@ void win_update_wintype(session_t *ps, struct managed_win *w) { } } +/** + * Update window wm_state. + */ +void win_update_wm_state(session_t *ps, struct managed_win *w) { + const win_wmstate_t wmstate_old = w->wm_state; + + // Detect window wm_state here + w->wm_state = wid_get_prop_wm_state(ps, w->client_win); + + if (w->wm_state != wmstate_old) { + win_on_factor_change(ps, w); + } +} + +bool win_check_wm_state(const struct managed_win *w, enum wm_state state) { + if (state == WMSTATE_UNKNOWN) { + return false; + } + return (w->wm_state & (1 << (state - 1))) != 0; +} + /** * Mark a window as the client window of another. * @@ -1143,6 +1186,7 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) } win_update_wintype(ps, w); + win_update_wm_state(ps, w); // Get frame widths. The window is in damaged area already. win_update_frame_extents(ps, w, client); @@ -1388,6 +1432,7 @@ struct win *fill_win(session_t *ps, struct win *w) { .leader = XCB_NONE, .cache_leader = XCB_NONE, .window_type = WINTYPE_UNKNOWN, + .wm_state = WMSTATE_UNKNOWN, .wmwin = false, .focused = false, .opacity = 0, @@ -2478,34 +2523,6 @@ static inline bool rect_is_fullscreen(const session_t *ps, int x, int y, int wid return (x <= 0 && y <= 0 && (x + wid) >= ps->root_width && (y + hei) >= ps->root_height); } -/** - * Check if a window is fulscreen using EWMH - * - * TODO(yshui) cache this property - */ -static inline bool -win_is_fullscreen_xcb(xcb_connection_t *c, const struct atom *a, const xcb_window_t w) { - xcb_get_property_cookie_t prop = - xcb_get_property(c, 0, w, a->a_NET_WM_STATE, XCB_ATOM_ATOM, 0, 12); - xcb_get_property_reply_t *reply = xcb_get_property_reply(c, prop, NULL); - if (!reply) { - return false; - } - - if (reply->length) { - xcb_atom_t *val = xcb_get_property_value(reply); - for (uint32_t i = 0; i < reply->length; i++) { - if (val[i] != a->a_NET_WM_STATE_FULLSCREEN) { - continue; - } - free(reply); - return true; - } - } - free(reply); - return false; -} - /// Set flags on a window. Some sanity checks are performed void win_set_flags(struct managed_win *w, uint64_t flags) { log_debug("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name); @@ -2591,8 +2608,7 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags) { * It's not using w->border_size for performance measures. */ bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) { - if (!ps->o.no_ewmh_fullscreen && - win_is_fullscreen_xcb(ps->c, ps->atoms, w->client_win)) { + if (!ps->o.no_ewmh_fullscreen && win_check_wm_state(w, WMSTATE_FULLSCREEN)) { return true; } return rect_is_fullscreen(ps, w->g.x, w->g.y, w->widthb, w->heightb) && diff --git a/src/win.h b/src/win.h index 83cebec56c..aa37bcaa6c 100644 --- a/src/win.h +++ b/src/win.h @@ -166,6 +166,8 @@ struct managed_win { xcb_window_t client_win; /// Type of the window. wintype_t window_type; + /// State of the window as described by the window manager in _NET_WM_STATE. + win_wmstate_t wm_state; /// Whether it looks like a WM window. We consider a window WM window if /// it does not have a decedent with WM_STATE and it is not override- /// redirected itself. diff --git a/src/win_defs.h b/src/win_defs.h index e032bc747c..b641a78967 100644 --- a/src/win_defs.h +++ b/src/win_defs.h @@ -2,7 +2,7 @@ #include <stdint.h> typedef enum { - WINTYPE_UNKNOWN, + WINTYPE_UNKNOWN = 0, WINTYPE_DESKTOP, WINTYPE_DOCK, WINTYPE_TOOLBAR, @@ -20,6 +20,25 @@ typedef enum { NUM_WINTYPES } wintype_t; +enum wm_state { + WMSTATE_UNKNOWN = 0, + WMSTATE_MODAL, + WMSTATE_STICKY, + WMSTATE_MAXIMIZED_VERT, + WMSTATE_MAXIMIZED_HORZ, + WMSTATE_SHADED, + WMSTATE_SKIP_TASKBAR, + WMSTATE_SKIP_PAGER, + WMSTATE_HIDDEN, + WMSTATE_FULLSCREEN, + WMSTATE_ABOVE, + WMSTATE_BELOW, + WMSTATE_DEMANDS_ATTENTION, + WMSTATE_FOCUSED, + NUM_WMSTATES, +}; +typedef uint64_t win_wmstate_t; + /// Enumeration type of window painting mode. typedef enum { WMODE_TRANS, // The window body is (potentially) transparent @@ -96,7 +115,6 @@ enum win_flags { WIN_FLAGS_FACTOR_CHANGED = 1024, }; -static const uint64_t WIN_FLAGS_IMAGES_STALE = - WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE; +static const uint64_t WIN_FLAGS_IMAGES_STALE = WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE; #define WIN_FLAGS_IMAGES_NONE (WIN_FLAGS_PIXMAP_NONE | WIN_FLAGS_SHADOW_NONE) From dac6785666e1579d14094dcab551f04d45d357f2 Mon Sep 17 00:00:00 2001 From: Bernd Busse <bernd@busse-net.de> Date: Sun, 25 Oct 2020 21:21:44 +0100 Subject: [PATCH 3/3] c2: add new predefined target `wm_state` to match against `_NET_WM_STATE` in string New predefined target `wm_state` for rule-based matching. Matches against the cached `_NET_WM_STATE` property of a window in string representation. NOTE: `wm_state = 'fullscreen'` is not neccessarily identical to `fullscreen`, and `wm_state = 'focused'` is not identical to `focused`. --- man/picom.1.asciidoc | 5 ++++- src/c2.c | 17 +++++++++++++++-- src/common.h | 1 + src/picom.c | 9 +++++++++ src/win.c | 4 ++-- src/win.h | 3 +++ 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 466ab55b9a..68df50620c 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -274,7 +274,7 @@ With greater-than/less-than operators it looks like: 'NEGATION' (optional) is one or more exclamation marks; -'TARGET' is either a predefined target name, or the name of a window property to match. Supported predefined targets are `id`, `x`, `y`, `x2` (`x` + `widthb`), `y2` (like `x2`), `width`, `height`, `widthb` (`width` + 2 * `border_width`), `heightb` (like `widthb`), `border_width`, `fullscreen`, `override_redirect`, `argb` (whether the window has an ARGB visual), `focused`, `wmwin` (whether the window looks like a WM window, i.e. has no child window with `WM_STATE` and is not override-redirected), `bounding_shaped`, `rounded_corners` (requires *--detect-rounded-corners*), `client` (ID of client window), `window_type` (window type in string), `leader` (ID of window leader), `name`, `class_g` (= `WM_CLASS[1]`), `class_i` (= `WM_CLASS[0]`), and `role`. +'TARGET' is either a predefined target name, or the name of a window property to match. Supported predefined targets are `id`, `x`, `y`, `x2` (`x` + `widthb`), `y2` (like `x2`), `width`, `height`, `widthb` (`width` + 2 * `border_width`), `heightb` (like `widthb`), `border_width`, `fullscreen`, `override_redirect`, `argb` (whether the window has an ARGB visual), `focused`, `wmwin` (whether the window looks like a WM window, i.e. has no child window with `WM_STATE` and is not override-redirected), `bounding_shaped`, `rounded_corners` (requires *--detect-rounded-corners*), `client` (ID of client window), `window_type` (window type in string), `leader` (ID of window leader), `name`, `class_g` (= `WM_CLASS[1]`), `class_i` (= `WM_CLASS[0]`), `role`, and `wm_state` (window manager state hint in string). 'CLIENT/FRAME' is a single `@` if the window attribute should be be looked up on client window, nothing if on frame window; @@ -307,6 +307,9 @@ Examples: # If the window is a menu window_type *= "menu" _NET_WM_WINDOW_TYPE@:a *= "MENU" + # If the window is marked hidden + wm_state *= "hidden" + _NET_WM_STATE@:a *= "HIDDEN" # If the window name contains "Firefox", ignore case name *?= "Firefox" _NET_WM_NAME@:s *?= "Firefox" diff --git a/src/c2.c b/src/c2.c index 860ef50d27..7da9b25d34 100644 --- a/src/c2.c +++ b/src/c2.c @@ -133,6 +133,7 @@ struct _c2_l { C2_L_PCLASSG, C2_L_PCLASSI, C2_L_PROLE, + C2_L_PWMSTATE, } predef; enum c2_l_type { C2_L_TUNDEFINED, @@ -210,6 +211,7 @@ static const c2_predef_t C2_PREDEFS[] = { [C2_L_PCLASSG] = {"class_g", C2_L_TSTRING, 0}, [C2_L_PCLASSI] = {"class_i", C2_L_TSTRING, 0}, [C2_L_PROLE] = {"role", C2_L_TSTRING, 0}, + [C2_L_PWMSTATE] = {"wm_state", C2_L_TSTRING, 0}, }; /** @@ -1395,10 +1397,21 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w case C2_L_PCLASSG: tgt = w->class_general; break; case C2_L_PCLASSI: tgt = w->class_instance; break; case C2_L_PROLE: tgt = w->role; break; + case C2_L_PWMSTATE: { + ntargets = 0; + for (enum wm_state i = 1; i < NUM_WMSTATES; ++i) { + if (win_check_wm_state(w, i)) { + targets[ntargets++] = WMSTATES[i]; + } + } + break; + } default: assert(0); break; } - ntargets = 1; - targets[0] = tgt; + if (tgt) { + ntargets = 1; + targets[0] = tgt; + } } // An atom type property, convert it to string else if (pleaf->type == C2_L_TATOM) { diff --git a/src/common.h b/src/common.h index f1a4db66fb..debda88bf9 100644 --- a/src/common.h +++ b/src/common.h @@ -386,6 +386,7 @@ typedef struct session { typedef enum { WIN_EVMODE_UNKNOWN, WIN_EVMODE_FRAME, WIN_EVMODE_CLIENT } win_evmode_t; extern const char *const WINTYPES[NUM_WINTYPES]; +extern const char *const WMSTATES[NUM_WMSTATES]; extern session_t *ps_g; void ev_xcb_error(session_t *ps, xcb_generic_error_t *err); diff --git a/src/picom.c b/src/picom.c index 49930de6ce..497fe4fad4 100644 --- a/src/picom.c +++ b/src/picom.c @@ -84,6 +84,15 @@ const char *const WINTYPES[NUM_WINTYPES] = { "popup_menu", "tooltip", "notification", "combo", "dnd", }; +/// Name string for window wm_states. +const char *const WMSTATES[NUM_WMSTATES] = { + "unknown", "modal", "sticky", + "maximized_vert", "maximized_horz", "shaded", + "skip_taskbar", "skip_pager", "hidden", + "fullscreen", "above", "below", + "demands_attention", "focused", +}; + // clang-format off /// Names of backends. const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender", diff --git a/src/win.c b/src/win.c index fbd12a7f79..ef64c1e1c3 100644 --- a/src/win.c +++ b/src/win.c @@ -656,7 +656,7 @@ static win_wmstate_t wid_get_prop_wm_state(session_t *ps, xcb_window_t wid) { for (unsigned i = 0; i < prop.nitems; ++i) { for (enum wm_state j = 1; j < NUM_WMSTATES; ++j) { if (ps->atoms_wmstates[j] == (xcb_atom_t)prop.p32[i]) { - wm_state |= (1 << (j - 1)); + wm_state |= (win_wmstate_t)(1 << (j - 1)); } } } @@ -1157,7 +1157,7 @@ bool win_check_wm_state(const struct managed_win *w, enum wm_state state) { if (state == WMSTATE_UNKNOWN) { return false; } - return (w->wm_state & (1 << (state - 1))) != 0; + return (w->wm_state & (win_wmstate_t)(1 << (state - 1))) != 0; } /** diff --git a/src/win.h b/src/win.h index aa37bcaa6c..6cddeb1622 100644 --- a/src/win.h +++ b/src/win.h @@ -410,6 +410,9 @@ bool attr_pure win_is_fullscreen(const session_t *ps, const struct managed_win * */ bool attr_pure win_is_focused_raw(const session_t *ps, const struct managed_win *w); +/// check if window has specific wm_state. +bool attr_pure win_check_wm_state(const struct managed_win *w, enum wm_state state); + /// check if window has ARGB visual bool attr_pure win_has_alpha(const struct managed_win *w);