From 679bfe3cab740f06b38f606d599304f515b9de4d Mon Sep 17 00:00:00 2001 From: Richard Grenville Date: Mon, 28 Jan 2013 21:39:38 +0800 Subject: Feature #16: Advanced window matching - Add advanced window matching system, capable of matching against arbitrary window properties as well as a series of internal properties, with 4 additional operators (>, <, >=, <=) useful for integer targets, and support of logical operators. The old matching system is removed, but compatibility with the format is retained. - As the new matching system is pretty complicated, and I have no past experience in writing a parser, it's pretty possible that bugs are present. It also has inferior performance, but I hope it doesn't matter on modern CPUs. - It's possible to disable matching system at compile time with NO_C2=1 now. - Add ps->o.config_file to track which config file we have actually read. Queryable via D-Bus. - Parse -d in first pass in get_cfg() as c2 needs to query X to get atoms during condition parsing. - Fix a bug in wid_get_prop_adv() that 0 == rformat is not handled correctly. - Fix incompatibility with FreeBSD sed in dbus-examples/cdbus-driver.sh . - Add recipe to generate .clang_complete in Makefile, used by Vim clang_complete plugin. - Add DEBUG_C2 for debugging condition string parsing. DEBUG_WINMATCH is still used for match debugging. - Rename win_on_wdata_change() to win_on_factor_change(). - Extra malloc() failure checks. Add const to matching cache members in session_t. Code clean-up. Documentation update. --- compton.c | 396 +++++++++++++++----------------------------------------------- 1 file changed, 91 insertions(+), 305 deletions(-) (limited to 'compton.c') diff --git a/compton.c b/compton.c index 814bf4a3b..cbb5e7b61 100644 --- a/compton.c +++ b/compton.c @@ -567,7 +567,7 @@ wid_get_prop_adv(const session_t *ps, Window w, Atom atom, long offset, if (Success == XGetWindowProperty(ps->dpy, w, atom, offset, length, False, rtype, &type, &format, &nitems, &after, &data) && nitems && (AnyPropertyType == type || type == rtype) - && (!format || format == rformat) + && (!rformat || format == rformat) && (8 == format || 16 == format || 32 == format)) { return (winprop_t) { .data.p8 = data, @@ -629,244 +629,20 @@ win_rounded_corners(session_t *ps, win *w) { XFree(rects); } -/** - * Match a window against a single window condition. - * - * @return true if matched, false otherwise. - */ -static bool -win_match_once(win *w, const wincond_t *cond) { - const char *target; - bool matched = false; - -#ifdef DEBUG_WINMATCH - printf("win_match_once(%#010lx \"%s\"): cond = %p", w->id, w->name, - cond); -#endif - - if (InputOnly == w->a.class) { -#ifdef DEBUG_WINMATCH - printf(": InputOnly\n"); -#endif - return false; - } - - // Determine the target - target = NULL; - switch (cond->target) { - case CONDTGT_NAME: - target = w->name; - break; - case CONDTGT_CLASSI: - target = w->class_instance; - break; - case CONDTGT_CLASSG: - target = w->class_general; - break; - case CONDTGT_ROLE: - target = w->role; - break; - } - - if (!target) { -#ifdef DEBUG_WINMATCH - printf(": Target not found\n"); -#endif - return false; - } - - // Determine pattern type and match - switch (cond->type) { - case CONDTP_EXACT: - if (cond->flags & CONDF_IGNORECASE) - matched = !strcasecmp(target, cond->pattern); - else - matched = !strcmp(target, cond->pattern); - break; - case CONDTP_ANYWHERE: - if (cond->flags & CONDF_IGNORECASE) - matched = strcasestr(target, cond->pattern); - else - matched = strstr(target, cond->pattern); - break; - case CONDTP_FROMSTART: - if (cond->flags & CONDF_IGNORECASE) - matched = !strncasecmp(target, cond->pattern, - strlen(cond->pattern)); - else - matched = !strncmp(target, cond->pattern, - strlen(cond->pattern)); - break; - case CONDTP_WILDCARD: - { - int flags = 0; - if (cond->flags & CONDF_IGNORECASE) - flags = FNM_CASEFOLD; - matched = !fnmatch(cond->pattern, target, flags); - } - break; - case CONDTP_REGEX_PCRE: -#ifdef CONFIG_REGEX_PCRE - matched = (pcre_exec(cond->regex_pcre, cond->regex_pcre_extra, - target, strlen(target), 0, 0, NULL, 0) >= 0); -#endif - break; - } - -#ifdef DEBUG_WINMATCH - printf(", matched = %d\n", matched); -#endif - - return matched; -} - -/** - * Match a window against a condition linked list. - * - * @param cache a place to cache the last matched condition - * @return true if matched, false otherwise. - */ -static bool -win_match(win *w, wincond_t *condlst, wincond_t **cache) { - // Check if the cached entry matches firstly - if (cache && *cache && win_match_once(w, *cache)) - return true; - - // Then go through the whole linked list - for (; condlst; condlst = condlst->next) { - if (win_match_once(w, condlst)) { - if (cache) - *cache = condlst; - return true; - } - } - - return false; -} - /** * Add a pattern to a condition linked list. */ static bool -condlst_add(wincond_t **pcondlst, const char *pattern) { +condlst_add(session_t *ps, c2_lptr_t **pcondlst, const char *pattern) { if (!pattern) return false; - unsigned plen = strlen(pattern); - wincond_t *cond; - const char *pos; - - if (plen < 4 || ':' != pattern[1] || !strchr(pattern + 2, ':')) { - printf("Pattern \"%s\": Format invalid.\n", pattern); - return false; - } - - // Allocate memory for the new condition - cond = malloc(sizeof(wincond_t)); - - // Determine the pattern target - switch (pattern[0]) { - case 'n': - cond->target = CONDTGT_NAME; - break; - case 'i': - cond->target = CONDTGT_CLASSI; - break; - case 'g': - cond->target = CONDTGT_CLASSG; - break; - case 'r': - cond->target = CONDTGT_ROLE; - break; - default: - printf("Pattern \"%s\": Target \"%c\" invalid.\n", - pattern, pattern[0]); - free(cond); - return false; - } - - // Determine the pattern type - switch (pattern[2]) { - case 'e': - cond->type = CONDTP_EXACT; - break; - case 'a': - cond->type = CONDTP_ANYWHERE; - break; - case 's': - cond->type = CONDTP_FROMSTART; - break; - case 'w': - cond->type = CONDTP_WILDCARD; - break; -#ifdef CONFIG_REGEX_PCRE - case 'p': - cond->type = CONDTP_REGEX_PCRE; - break; -#endif - default: - printf("Pattern \"%s\": Type \"%c\" invalid.\n", - pattern, pattern[2]); - free(cond); - return false; - } - - // Determine the pattern flags - pos = &pattern[3]; - cond->flags = 0; - while (':' != *pos) { - switch (*pos) { - case 'i': - cond->flags |= CONDF_IGNORECASE; - break; - default: - printf("Pattern \"%s\": Flag \"%c\" invalid.\n", - pattern, *pos); - break; - } - ++pos; - } - - // Copy the pattern - ++pos; - cond->pattern = NULL; -#ifdef CONFIG_REGEX_PCRE - cond->regex_pcre = NULL; - cond->regex_pcre_extra = NULL; -#endif - if (CONDTP_REGEX_PCRE == cond->type) { -#ifdef CONFIG_REGEX_PCRE - const char *error = NULL; - int erroffset = 0; - int options = 0; - - if (cond->flags & CONDF_IGNORECASE) - options |= PCRE_CASELESS; - - cond->regex_pcre = pcre_compile(pos, options, &error, &erroffset, - NULL); - if (!cond->regex_pcre) { - printf("Pattern \"%s\": PCRE regular expression parsing failed on " - "offset %d: %s\n", pattern, erroffset, error); - free(cond); - return false; - } -#ifdef CONFIG_REGEX_PCRE_JIT - cond->regex_pcre_extra = pcre_study(cond->regex_pcre, PCRE_STUDY_JIT_COMPILE, &error); - if (!cond->regex_pcre_extra) { - printf("Pattern \"%s\": PCRE regular expression study failed: %s", - pattern, error); - } -#endif +#ifdef CONFIG_C2 + if (!c2_parse(ps, pcondlst, pattern)) + exit(1); +#else + printf_errfq(1, "(): Condition support not compiled in."); #endif - } - else { - cond->pattern = mstrcpy(pos); - } - - // Insert it into the linked list - cond->next = *pcondlst; - *pcondlst = cond; return true; } @@ -2313,7 +2089,7 @@ win_determine_shadow(session_t *ps, win *w) { w->shadow = (UNSET == w->shadow_force ? (ps->o.wintype_shadow[w->window_type] - && !win_match(w, ps->o.shadow_blacklist, &w->cache_sblst) + && !win_match(ps, w, ps->o.shadow_blacklist, &w->cache_sblst) && !(ps->o.shadow_ignore_shaped && w->bounding_shaped && !w->rounded_corners) && !(ps->o.respect_prop_shadow && 0 == w->prop_shadow)) @@ -2347,7 +2123,7 @@ win_determine_invert_color(session_t *ps, win *w) { if (UNSET != w->invert_color_force) w->invert_color = w->invert_color_force; else - w->invert_color = win_match(w, ps->o.invert_color_list, &w->cache_ivclst); + w->invert_color = win_match(ps, w, ps->o.invert_color_list, &w->cache_ivclst); if (w->invert_color != invert_color_old) add_damage_win(ps, w); @@ -2361,13 +2137,15 @@ win_on_wtype_change(session_t *ps, win *w) { win_determine_shadow(ps, w); win_determine_fade(ps, w); win_update_focused(ps, w); + if (ps->o.invert_color_list) + win_determine_invert_color(ps, w); } /** * Function to be called on window data changes. */ static void -win_on_wdata_change(session_t *ps, win *w) { +win_on_factor_change(session_t *ps, win *w) { if (ps->o.shadow_blacklist) win_determine_shadow(ps, w); if (ps->o.fade_blacklist) @@ -2481,9 +2259,11 @@ win_mark_client(session_t *ps, win *w, Window client) { win_get_name(ps, w); win_get_class(ps, w); win_get_role(ps, w); - win_on_wdata_change(ps, w); } + // Update everything related to conditions + win_on_factor_change(ps, w); + // Update window focus state win_update_focused(ps, w); } @@ -3048,7 +2828,7 @@ win_update_focused(session_t *ps, win *w) { || (ps->o.mark_wmwin_focused && w->wmwin) || (ps->o.mark_ovredir_focused && w->id == w->client_win && !w->wmwin) - || win_match(w, ps->o.focus_blacklist, &w->cache_fcblst)) + || win_match(ps, w, ps->o.focus_blacklist, &w->cache_fcblst)) w->focused = true; // If window grouping detection is enabled, mark the window active if @@ -3102,6 +2882,9 @@ win_set_focused(session_t *ps, win *w, bool focused) { else { win_update_focused(ps, w); } + + // Update everything related to conditions + win_on_factor_change(ps, w); } } /** @@ -3153,6 +2936,9 @@ win_set_leader(session_t *ps, win *w, Window nleader) { else { win_update_focused(ps, w); } + + // Update everything related to conditions + win_on_factor_change(ps, w); } } @@ -3746,7 +3532,7 @@ ev_property_notify(session_t *ps, XPropertyEvent *ev) { && (ps->atom_name == ev->atom || ps->atom_name_ewmh == ev->atom)) { win *w = find_toplevel(ps, ev->window); if (w && 1 == win_get_name(ps, w)) { - win_on_wdata_change(ps, w); + win_on_factor_change(ps, w); } } @@ -3755,7 +3541,7 @@ ev_property_notify(session_t *ps, XPropertyEvent *ev) { win *w = find_toplevel(ps, ev->window); if (w) { win_get_class(ps, w); - win_on_wdata_change(ps, w); + win_on_factor_change(ps, w); } } @@ -3763,7 +3549,7 @@ ev_property_notify(session_t *ps, XPropertyEvent *ev) { if (ps->o.track_wdata && ps->atom_role == ev->atom) { win *w = find_toplevel(ps, ev->window); if (w && 1 == win_get_role(ps, w)) { - win_on_wdata_change(ps, w); + win_on_factor_change(ps, w); } } @@ -3790,7 +3576,7 @@ ev_property_notify(session_t *ps, XPropertyEvent *ev) { if (!w) w = find_toplevel(ps, ev->window); if (w) - win_on_wdata_change(ps, w); + win_on_factor_change(ps, w); break; } } @@ -4095,24 +3881,7 @@ usage(void) { " inverted color. Resource-hogging, and is not well tested.\n" "--dbus\n" " Enable remote control via D-Bus. See the D-BUS API section in the\n" - " man page for more details.\n" - "\n" - "Format of a condition:\n" - "\n" - " condition = :[]:\n" - "\n" - " is one of \"n\" (window name), \"i\" (window class\n" - " instance), \"g\" (window general class), and \"r\"\n" - " (window role).\n" - "\n" - " is one of \"e\" (exact match), \"a\" (match anywhere),\n" - " \"s\" (match from start), \"w\" (wildcard), and \"p\" (PCRE\n" - " regular expressions, if compiled with the support).\n" - "\n" - " could be a series of flags. Currently the only defined\n" - " flag is \"i\" (ignore case).\n" - "\n" - " is the actual pattern string.\n"; + " man page for more details.\n"; fputs(usage_text , stderr); exit(1); @@ -4270,8 +4039,6 @@ open_config_file(char *cpath, char **ppath) { f = fopen(path, "r"); if (f && ppath) *ppath = path; - else - free(path); return f; } @@ -4356,7 +4123,7 @@ parse_vsync(session_t *ps, const char *optarg) { * Parse a condition list in configuration file. */ static void -parse_cfg_condlst(const config_t *pcfg, wincond_t **pcondlst, +parse_cfg_condlst(session_t *ps, const config_t *pcfg, c2_lptr_t **pcondlst, const char *name) { config_setting_t *setting = config_lookup(pcfg, name); if (setting) { @@ -4364,12 +4131,12 @@ parse_cfg_condlst(const config_t *pcfg, wincond_t **pcondlst, if (config_setting_is_array(setting)) { int i = config_setting_length(setting); while (i--) { - condlst_add(pcondlst, config_setting_get_string_elem(setting, i)); + condlst_add(ps, pcondlst, config_setting_get_string_elem(setting, i)); } } // Treat it as a single pattern if it's a string else if (CONFIG_TYPE_STRING == config_setting_type(setting)) { - condlst_add(pcondlst, config_setting_get_string(setting)); + condlst_add(ps, pcondlst, config_setting_get_string(setting)); } } } @@ -4378,7 +4145,7 @@ parse_cfg_condlst(const config_t *pcfg, wincond_t **pcondlst, * Parse a configuration file from default location. */ static void -parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { +parse_config(session_t *ps, struct options_tmp *pcfgtmp) { char *path = NULL; FILE *f; config_t cfg; @@ -4386,10 +4153,14 @@ parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { double dval = 0.0; const char *sval = NULL; - f = open_config_file(cpath, &path); + f = open_config_file(ps->o.config_file, &path); if (!f) { - if (cpath) - printf_errfq(1, "(): Failed to read the specified configuration file."); + if (ps->o.config_file) { + printf_errfq(1, "(): Failed to read configuration file \"%s\".", + ps->o.config_file); + free(ps->o.config_file); + ps->o.config_file = NULL; + } return; } @@ -4417,7 +4188,10 @@ parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { } config_set_auto_convert(&cfg, 1); - free(path); + if (path != ps->o.config_file) { + free(ps->o.config_file); + ps->o.config_file = path; + } // Get options from the configuration file. We don't do range checking // right now. It will be done later @@ -4512,11 +4286,11 @@ parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { lcfg_lookup_bool(&cfg, "detect-client-leader", &ps->o.detect_client_leader); // --shadow-exclude - parse_cfg_condlst(&cfg, &ps->o.shadow_blacklist, "shadow-exclude"); + parse_cfg_condlst(ps, &cfg, &ps->o.shadow_blacklist, "shadow-exclude"); // --focus-exclude - parse_cfg_condlst(&cfg, &ps->o.focus_blacklist, "focus-exclude"); + parse_cfg_condlst(ps, &cfg, &ps->o.focus_blacklist, "focus-exclude"); // --invert-color-include - parse_cfg_condlst(&cfg, &ps->o.invert_color_list, "invert-color-include"); + parse_cfg_condlst(ps, &cfg, &ps->o.invert_color_list, "invert-color-include"); // --blur-background lcfg_lookup_bool(&cfg, "blur-background", &ps->o.blur_background); // --blur-background-frame @@ -4554,7 +4328,7 @@ parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { * Process arguments and configuration files. */ static void -get_cfg(session_t *ps, int argc, char *const *argv) { +get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) { const static char *shortopts = "D:I:O:d:r:o:m:l:t:i:e:hscnfFCaSzGb"; const static struct option longopts[] = { { "help", no_argument, NULL, 'h' }, @@ -4595,14 +4369,33 @@ get_cfg(session_t *ps, int argc, char *const *argv) { { NULL, 0, NULL, 0 }, }; + int o = 0, longopt_idx = -1, i = 0; + + if (first_pass) { + // Pre-parse the commandline arguments to check for --config and invalid + // switches + // Must reset optind to 0 here in case we reread the commandline + // arguments + optind = 1; + while (-1 != + (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { + if (256 == o) + ps->o.config_file = mstrcpy(optarg); + else if ('d' == o) + ps->o.display = mstrcpy(optarg); + else if ('?' == o || ':' == o) + usage(); + } + + return; + } + struct options_tmp cfgtmp = { .no_dock_shadow = false, .no_dnd_shadow = false, .menu_opacity = 1.0, }; bool shadow_enable = false, fading_enable = false; - int o, longopt_idx, i; - char *config_file = NULL; char *lc_numeric_old = mstrcpy(setlocale(LC_NUMERIC, NULL)); for (i = 0; i < NUM_WINTYPES; ++i) { @@ -4611,21 +4404,8 @@ get_cfg(session_t *ps, int argc, char *const *argv) { ps->o.wintype_opacity[i] = 1.0; } - // Pre-parse the commandline arguments to check for --config and invalid - // switches - // Must reset optind to 0 here in case we reread the commandline - // arguments - optind = 1; - while (-1 != - (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { - if (256 == o) - config_file = mstrcpy(optarg); - else if ('?' == o || ':' == o) - usage(); - } - #ifdef CONFIG_LIBCONFIG - parse_config(ps, config_file, &cfgtmp); + parse_config(ps, &cfgtmp); #endif // Parse commandline arguments. Range checking will be done later. @@ -4643,7 +4423,6 @@ get_cfg(session_t *ps, int argc, char *const *argv) { usage(); break; case 'd': - ps->o.display = mstrcpy(optarg); break; case 'D': ps->o.fade_delta = atoi(optarg); @@ -4732,7 +4511,7 @@ get_cfg(session_t *ps, int argc, char *const *argv) { break; case 263: // --shadow-exclude - condlst_add(&ps->o.shadow_blacklist, optarg); + condlst_add(ps, &ps->o.shadow_blacklist, optarg); break; case 264: // --mark-ovredir-focused @@ -4796,7 +4575,7 @@ get_cfg(session_t *ps, int argc, char *const *argv) { break; case 279: // --focus-exclude - condlst_add(&ps->o.focus_blacklist, optarg); + condlst_add(ps, &ps->o.focus_blacklist, optarg); break; case 280: // --inactive-dim-fixed @@ -4832,7 +4611,7 @@ get_cfg(session_t *ps, int argc, char *const *argv) { break; case 288: // --invert-color-include - condlst_add(&ps->o.invert_color_list, optarg); + condlst_add(ps, &ps->o.invert_color_list, optarg); break; default: usage(); @@ -4884,11 +4663,6 @@ get_cfg(session_t *ps, int argc, char *const *argv) { ps->o.track_focus = true; } - // Determine whether we need to track window name and class - if (ps->o.shadow_blacklist || ps->o.fade_blacklist - || ps->o.focus_blacklist || ps->o.invert_color_list) - ps->o.track_wdata = true; - // Determine whether we track window grouping if (ps->o.detect_transient || ps->o.detect_client_leader) { ps->o.track_leader = true; @@ -5702,7 +5476,8 @@ session_init(session_t *ps_old, int argc, char **argv) { ps->o.wintype_focus[WINTYPE_NORMAL] = false; ps->o.wintype_focus[WINTYPE_UTILITY] = false; - get_cfg(ps, argc, argv); + // First pass + get_cfg(ps, argc, argv, true); // Inherit old Display if possible, primarily for resource leak checking if (ps_old && ps_old->dpy) @@ -5712,11 +5487,13 @@ session_init(session_t *ps_old, int argc, char **argv) { if (!ps->dpy) { ps->dpy = XOpenDisplay(ps->o.display); if (!ps->dpy) { - fprintf(stderr, "Can't open display\n"); - exit(1); + printf_errfq(1, "(): Can't open display."); } } + // Second pass + get_cfg(ps, argc, argv, false); + XSetErrorHandler(error); if (ps->o.synchronize) { XSynchronize(ps->dpy, 1); @@ -5973,11 +5750,24 @@ session_destroy(session_t *ps) { ps->alpha_picts = NULL; } +#ifdef CONFIG_C2 // Free blacklists free_wincondlst(&ps->o.shadow_blacklist); free_wincondlst(&ps->o.fade_blacklist); free_wincondlst(&ps->o.focus_blacklist); free_wincondlst(&ps->o.invert_color_list); +#endif + + // Free tracked atom list + { + latom_t *next = NULL; + for (latom_t *this = ps->track_atom_lst; this; this = next) { + next = this->next; + free(this); + } + + ps->track_atom_lst = NULL; + } // Free ignore linked list { @@ -6025,6 +5815,7 @@ session_destroy(session_t *ps) { free(ps->gaussian_map); free(ps->o.display); free(ps->o.logpath); + free(ps->o.config_file); free(ps->pfds_read); free(ps->pfds_write); free(ps->pfds_except); @@ -6164,11 +5955,6 @@ main(int argc, char **argv) { printf_errf("Failed to create new session."); return 1; } -#ifdef DEBUG_C2 - // c2_parse(ps_g, NULL, "name ~= \"master\""); - // c2_parse(ps_g, NULL, "n:e:Notification"); - c2_parse(ps_g, NULL, "(WM_NAME:16s = 'Notification' || class_g = 'fox') && class_g = 'fox'"); -#endif session_run(ps_g); ps_old = ps_g; session_destroy(ps_g); -- cgit v1.2.1