From d96abca075f19b1a8c01cddd92612ab9ee855595 Mon Sep 17 00:00:00 2001 From: Bernd Busse Date: Mon, 30 Nov 2020 15:51:26 +0100 Subject: [PATCH 1/2] x: split out function to get property info (type, format, size in bytes) --- src/x.c | 42 +++++++++++++++++++++++++++--------------- src/x.h | 9 +++++++++ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/x.c b/src/x.c index 3539ac1..3ee11ac 100644 --- a/src/x.c +++ b/src/x.c @@ -69,6 +69,25 @@ winprop_t x_get_prop_with_offset(const session_t *ps, xcb_window_t w, xcb_atom_t .ptr = NULL, .nitems = 0, .type = XCB_GET_PROPERTY_TYPE_ANY, .format = 0}; } +/// Get the type, format and size in bytes of a window's specific attribute. +winprop_info_t x_get_prop_info(const session_t *ps, xcb_window_t w, xcb_atom_t atom) { + xcb_generic_error_t *e = NULL; + auto r = xcb_get_property_reply( + ps->c, xcb_get_property(ps->c, 0, w, atom, XCB_ATOM_ANY, 0, 0), &e); + if (!r) { + log_debug_x_error(e, "Failed to get property info for window %#010x", w); + free(e); + return (winprop_info_t){ + .type = XCB_GET_PROPERTY_TYPE_ANY, .format = 0, .length = 0}; + } + + winprop_info_t winprop_info = { + .type = r->type, .format = r->format, .length = r->bytes_after}; + free(r); + + return winprop_info; +} + /** * Get the value of a type-xcb_window_t property of a window. * @@ -95,19 +114,10 @@ xcb_window_t wid_get_prop_window(session_t *ps, xcb_window_t wid, xcb_atom_t apr bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst, int *pnstr) { assert(ps->server_grabbed); - xcb_generic_error_t *e = NULL; - auto r = xcb_get_property_reply( - ps->c, xcb_get_property(ps->c, 0, wid, prop, XCB_ATOM_ANY, 0, 0), &e); - if (!r) { - log_debug_x_error(e, "Failed to get window property for %#010x", wid); - free(e); - return false; - } - - auto type = r->type; - auto format = r->format; - auto length = r->bytes_after; - free(r); + auto prop_info = x_get_prop_info(ps, wid, prop); + auto type = prop_info.type; + auto format = prop_info.format; + auto length = prop_info.length; if (type == XCB_ATOM_NONE) { return false; @@ -126,8 +136,10 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ** return false; } - r = xcb_get_property_reply( - ps->c, xcb_get_property(ps->c, 0, wid, prop, type, 0, length), &e); + xcb_generic_error_t *e = NULL; + auto word_count = (length + 4 - 1) / 4; + auto r = xcb_get_property_reply( + ps->c, xcb_get_property(ps->c, 0, wid, prop, type, 0, word_count), &e); if (!r) { log_debug_x_error(e, "Failed to get window property for %#010x", wid); free(e); diff --git a/src/x.h b/src/x.h index 426752e..daa3119 100644 --- a/src/x.h +++ b/src/x.h @@ -33,6 +33,12 @@ typedef struct winprop { xcb_get_property_reply_t *r; } winprop_t; +typedef struct winprop_info { + xcb_atom_t type; + uint8_t format; + uint32_t length; +} winprop_info_t; + struct xvisual_info { /// Bit depth of the red component int red_size; @@ -129,6 +135,9 @@ static inline winprop_t x_get_prop(const session_t *ps, xcb_window_t wid, xcb_at return x_get_prop_with_offset(ps, wid, atom, 0L, length, rtype, rformat); } +/// Get the type, format and size in bytes of a window's specific attribute. +winprop_info_t x_get_prop_info(const session_t *ps, xcb_window_t w, xcb_atom_t atom); + /// Discard all X events in queue or in flight. Should only be used when the server is /// grabbed static inline void x_discard_events(xcb_connection_t *c) { From 5989477b9f555e90c98a5b2bc329f2cc7772be8e Mon Sep 17 00:00:00 2001 From: Bernd Busse Date: Thu, 22 Oct 2020 21:21:54 +0200 Subject: [PATCH 2/2] c2: Support matching against "all" property values with special index `[*]` 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 using logical OR if the special index `[*]` is specified. If no index is specified, we fall-back to the first value. 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 *= 'HIDDEN'"` — without having to explicitly specify each index separately or when the index is not known in advance. Updated the manpage with examples for hidden and sticky windows. --- man/picom.1.asciidoc | 9 +- src/c2.c | 329 ++++++++++++++++++++++++++++--------------- 2 files changed, 220 insertions(+), 118 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 2612ece..f8d054a 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, the first value (index `[0]`) is used implicitly. Use the special value `[*]` to perform matching 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. @@ -307,6 +307,11 @@ Examples: # If the window is a menu window_type *= "menu" _NET_WM_WINDOW_TYPE@:a *= "MENU" + # If the window is marked hidden: _NET_WM_STATE contains _NET_WM_STATE_HIDDEN + _NET_WM_STATE@[*]:a = "_NET_WM_STATE_HIDDEN" + # If the window is marked sticky: _NET_WM_STATE contains an atom that contains + # "sticky", ignore case + _NET_WM_STATE@[*]:a *?= "sticky" # 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 53b85c1..e0a61a0 100644 --- a/src/c2.c +++ b/src/c2.c @@ -159,7 +159,7 @@ struct _c2_l { { \ .neg = false, .op = C2_L_OEXISTS, .match = C2_L_MEXACT, \ .match_ignorecase = false, .tgt = NULL, .tgtatom = 0, .tgt_onframe = false, \ - .predef = C2_L_PUNDEFINED, .index = -1, .type = C2_L_TUNDEFINED, \ + .predef = C2_L_PUNDEFINED, .index = 0, .type = C2_L_TUNDEFINED, \ .format = 0, .ptntype = C2_L_PTUNDEFINED, .ptnstr = NULL, .ptnint = 0, \ } @@ -214,17 +214,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; } @@ -610,24 +610,30 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { // Parse index if ('[' == pattern[offset]) { + if (pleaf->predef != C2_L_PUNDEFINED) { + c2_error("Predefined targets can't have index."); + } + offset++; C2H_SKIP_SPACES(); long index = -1; - char *endptr = NULL; + const char *endptr = NULL; - index = strtol(pattern + offset, &endptr, 0); + if ('*' == pattern[offset]) { + index = -1; + endptr = pattern + offset + 1; + } else { + index = strtol(pattern + offset, (char **)&endptr, 0); + if (index < 0) { + c2_error("Index number invalid."); + } + } if (!endptr || pattern + offset == endptr) { c2_error("No index number found after bracket."); } - if (index < 0) { - c2_error("Index number invalid."); - } - if (pleaf->predef != C2_L_PUNDEFINED) { - c2_error("Predefined targets can't have index."); - } pleaf->index = to_int_checked(index); offset = to_int_checked(endptr - pattern); @@ -677,9 +683,10 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { log_warn("Type specified for a default target " "will be ignored."); } else { - if (pleaf->type && type != pleaf->type) + if (pleaf->type && type != pleaf->type) { log_warn("Default type overridden on " "target."); + } pleaf->type = type; } } @@ -709,23 +716,26 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { "will be ignored.", format); } else { - if (pleaf->format && pleaf->format != format) + if (pleaf->format && pleaf->format != format) { log_warn("Default format %d overridden on " "target.", pleaf->format); + } pleaf->format = to_int_checked(format); } } } - if (!pleaf->type) + if (!pleaf->type) { c2_error("Target type cannot be determined."); + } // if (!pleaf->predef && !pleaf->format && C2_L_TSTRING != pleaf->type) // c2_error("Target format cannot be determined."); - if (pleaf->format && 8 != pleaf->format && 16 != pleaf->format && 32 != pleaf->format) + if (pleaf->format && 8 != pleaf->format && 16 != pleaf->format && 32 != pleaf->format) { c2_error("Invalid format."); + } return offset; @@ -1183,11 +1193,13 @@ static void c2_dump(c2_ptr_t p) { if (p.isbranch) { const c2_b_t *const pbranch = p.b; - if (!pbranch) + if (!pbranch) { return; + } - if (pbranch->neg) + if (pbranch->neg) { putchar('!'); + } printf("("); c2_dump(pbranch->opr1); @@ -1206,27 +1218,36 @@ static void c2_dump(c2_ptr_t p) { else { const c2_l_t *const pleaf = p.l; - if (!pleaf) + if (!pleaf) { return; + } - if (C2_L_OEXISTS == pleaf->op && pleaf->neg) + if (C2_L_OEXISTS == pleaf->op && pleaf->neg) { putchar('!'); + } // Print target name, type, and format { printf("%s", c2h_dump_str_tgt(pleaf)); - if (pleaf->tgt_onframe) + if (pleaf->tgt_onframe) { putchar('@'); - if (pleaf->index >= 0) - printf("[%d]", pleaf->index); + } + if (pleaf->predef == C2_L_PUNDEFINED) { + if (pleaf->index < 0) { + printf("[*]"); + } else { + printf("[%d]", pleaf->index); + } + } printf(":%d%s", pleaf->format, c2h_dump_str_type(pleaf)); } // Print operator putchar(' '); - if (C2_L_OEXISTS != pleaf->op && pleaf->neg) + if (C2_L_OEXISTS != pleaf->op && pleaf->neg) { putchar('!'); + } switch (pleaf->match) { case C2_L_MEXACT: break; @@ -1236,8 +1257,9 @@ static void c2_dump(c2_ptr_t p) { case C2_L_MWILDCARD: putchar('%'); break; } - if (pleaf->match_ignorecase) + if (pleaf->match_ignorecase) { putchar('?'); + } switch (pleaf->op) { case C2_L_OEXISTS: break; @@ -1248,8 +1270,9 @@ static void c2_dump(c2_ptr_t p) { case C2_L_OLTEQ: fputs("<=", stdout); break; } - if (C2_L_OEXISTS == pleaf->op) + if (C2_L_OEXISTS == pleaf->op) { return; + } // Print pattern putchar(' '); @@ -1300,11 +1323,14 @@ 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 = NULL; + long *targets_free = NULL; + 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 +1357,73 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w assert(0); break; } + ntargets = 1; + targets = &tgt; } // A raw window property else { + int word_count = 1; + if (pleaf->index < 0) { + // Get length of property in 32-bit multiples + auto prop_info = x_get_prop_info(ps, wid, pleaf->tgtatom); + word_count = to_int_checked((prop_info.length + 4 - 1) / 4); + } winprop_t prop = - x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, 1L, + x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, word_count, c2_get_atom_type(pleaf), pleaf->format); - if (prop.nitems) { + + ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); + if (ntargets > 0) { + targets = targets_free = ccalloc(ntargets, long); *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) - return; + if (*perr) { + goto fail_int; + } // 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; + + fail_int: + // Free property values after usage, if necessary + if (targets_free) { + free(targets_free); } } break; // String patterns case C2_L_PTSTRING: { - const char *tgt = NULL; - char *tgt_free = NULL; + const char **targets = NULL; + const char **targets_free = NULL; + const char **targets_free_inner = 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 +1432,138 @@ 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 = &tgt; + } + // An atom type property, convert it to string + else if (pleaf->type == C2_L_TATOM) { + int word_count = 1; + if (pleaf->index < 0) { + // Get length of property in 32-bit multiples + auto prop_info = x_get_prop_info(ps, wid, pleaf->tgtatom); + word_count = to_int_checked((prop_info.length + 4 - 1) / 4); + } winprop_t prop = - x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, 1L, + x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, word_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 = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); + targets = targets_free = (const char **)ccalloc(2 * ntargets, char *); + targets_free_inner = targets + ntargets; + + 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_inner[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 = to_u32_checked(nstr); + targets = (const char **)strlst; + } else if (nstr > idx) { + ntargets = 1; + targets = (const char **)strlst + idx; + } } if (strlst) { - free(strlst); + targets_free = (const char **)strlst; } } - if (tgt) { - *perr = false; - } else { - return; + if (ntargets == 0) { + goto fail_str; } + for (size_t i = 0; i < ntargets; ++i) { + if (!targets[i]) { + goto fail_str; + } + } + *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_str: // Free the string after usage, if necessary - if (tgt_free) { - free(tgt_free); + if (targets_free_inner) { + for (size_t i = 0; i < ntargets; ++i) { + if (targets_free_inner[i]) { + free((void *)targets_free_inner[i]); + } + } + } + // Free property values after usage, if necessary + if (targets_free) { + free(targets_free); } } break; default: assert(0); break;