diff options
author | Richard Grenville <[email protected]> | 2012-09-22 11:42:39 +0800 |
---|---|---|
committer | Richard Grenville <[email protected]> | 2012-09-22 11:42:39 +0800 |
commit | cbdaa9c000fada417cabf2092751945cbafc6ec0 (patch) | |
tree | 8cc1324e3c5a99cb899d34836cc4c2753e1af284 /compton.c | |
parent | fd0900ef892c97ae51563ac32ebbe895b7fc80a8 (diff) | |
download | tdebase-cbdaa9c000fada417cabf2092751945cbafc6ec0.tar.gz tdebase-cbdaa9c000fada417cabf2092751945cbafc6ec0.zip |
Feature: Issue #29: Alternative shadow blacklist implementation
- Add shadow blacklist feature, but a different implementation from
nicklan's. 5 matching modes (exact, starts-with, contains, wildcard,
PCRE) and 3 matching targets (window name, window class instance,
window general class). Not extensively tested, bugs to be expected.
It's slower for exact matching than nicklan's as it uses linear search
instead of hash table. Also, PCRE's JIT optimization may cause issues
on PaX kernels.
- Add dependency to libpcre. Could be made optional if we have a
graceful way to handle that in Makefile.
- Some matching functions are GNU extensions of glibc. So this version
may have troubles running on platforms not using glibc.
- Fix a bug that access freed memory blocks in set_fade_callcack() and
check_fade_fin(). valgrind found it out.
- Use WM_CLASS to detect client windows instead of WM_STATE. Some client
windows (like notification windows) have WM_CLASS but not WM_STATE.
- Mark the extents as damaged if shadow state changed in
determine_shadow().
- Rewrite wid_get_name(). Code clean-up.
- Two debugging options: DEBUG_WINDATA and DEBUG_WINMATCH.
- As the matching system is ready, it should be rather easy to add other
kinds of blacklists, like fading blacklist.
Diffstat (limited to 'compton.c')
-rw-r--r-- | compton.c | 527 |
1 files changed, 465 insertions, 62 deletions
@@ -35,7 +35,7 @@ Bool has_name_pixmap; int root_height, root_width; /* errors */ -ignore *ignore_head, **ignore_tail = &ignore_head; +ignore *ignore_head = NULL, **ignore_tail = &ignore_head; int xfixes_event, xfixes_error; int damage_event, damage_error; int composite_event, composite_error; @@ -71,8 +71,14 @@ Atom extents_atom; Atom opacity_atom; Atom frame_extents_atom; Atom client_atom; +Atom name_atom; +Atom name_ewmh_atom; +Atom class_atom; + Atom win_type_atom; Atom win_type[NUM_WINTYPES]; + +// Window type settings double win_type_opacity[NUM_WINTYPES]; Bool win_type_shadow[NUM_WINTYPES]; Bool win_type_fade[NUM_WINTYPES]; @@ -123,6 +129,13 @@ double mark_wmwin_focused = False; /// Whether compton needs to track focus changes. Bool track_focus = False; +/// Whether compton needs to track window name and class. +Bool track_wdata = False; + +/// Shadow blacklist. A linked list of conditions. +wincond *shadow_blacklist = NULL; +/// Fading blacklist. A linked list of conditions. +wincond *fade_blacklist = NULL; Bool synchronize = False; @@ -206,9 +219,12 @@ run_fade(Display *dpy, win *w, unsigned steps) { static void set_fade_callback(Display *dpy, win *w, void (*callback) (Display *dpy, win *w), Bool exec_callback) { - if (exec_callback && w->fade_callback) - (w->fade_callback)(dpy, w); + void (*old_callback) (Display *dpy, win *w) = w->fade_callback; + w->fade_callback = callback; + // Must be the last line as the callback could destroy w! + if (exec_callback && old_callback) + old_callback(dpy, w); } /** @@ -615,6 +631,231 @@ should_ignore(Display *dpy, unsigned long sequence) { * Windows */ +/** + * Match a window against a single window condition. + * + * @return true if matched, false otherwise. + */ +static bool +win_match_once(win *w, const wincond *cond) { + const char *target; + bool matched = false; + +#ifdef DEBUG_WINMATCH + printf("win_match_once(%#010lx \"%s\"): cond = %p", w->id, w->name, + cond); +#endif + + // 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; + } + + 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 *condlst, wincond **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)) { + *cache = condlst; + return true; + } + } + + return false; +} + +/** + * Add a pattern to a condition linked list. + */ +static Bool +condlst_add(wincond **pcondlst, const char *pattern) { + unsigned plen = strlen(pattern); + wincond *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)); + + // 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; + 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 +#endif + } + else { + cond->pattern = mstrcpy(pos); + } + + // Insert it into the linked list + cond->next = *pcondlst; + *pcondlst = cond; + + return True; +} + static long determine_evmask(Display *dpy, Window wid, win_evmode_t mode) { long evmask = NoEventMask; @@ -625,7 +866,7 @@ determine_evmask(Display *dpy, Window wid, win_evmode_t mode) { } if (WIN_EVMODE_CLIENT == mode || find_toplevel(dpy, wid)) { - if (frame_opacity) + if (frame_opacity || track_wdata) evmask |= PropertyChangeMask; } @@ -1106,7 +1347,7 @@ paint_all(Display *dpy, XserverRegion region, win *t) { XFixesSetPictureClipRegion(dpy, root_buffer, 0, 0, region); // Painting shadow - if (win_type_shadow[w->window_type]) { + if (w->shadow) { XRenderComposite( dpy, PictOpOver, cshadow_picture, w->shadow_pict, root_buffer, 0, 0, 0, 0, @@ -1363,13 +1604,6 @@ map_win(Display *dpy, Window id, w->a.map_state = IsViewable; w->window_type = determine_wintype(dpy, w->id, w->id); - // Window type change could affect shadow and fade - determine_shadow(dpy, w); - determine_fade(dpy, w); - - // Determine mode here just in case the colormap changes - determine_mode(dpy, w); - #ifdef DEBUG_WINTYPE printf("map_win(): window %#010lx type %s\n", w->id, wintype_name(w->window_type)); @@ -1401,6 +1635,12 @@ map_win(Display *dpy, Window id, get_frame_extents(dpy, w, w->client_win); } + // Get window name and class if we are tracking them + if (track_wdata) { + win_get_name(dpy, w); + win_get_class(dpy, w); + } + /* * Occasionally compton does not seem able to get a FocusIn event from a * window just mapped. I suspect it's a timing issue again when the @@ -1415,6 +1655,13 @@ map_win(Display *dpy, Window id, w->focused = True; } + // Window type change could affect shadow and fade + determine_shadow(dpy, w); + determine_fade(dpy, w); + + // Determine mode here just in case the colormap changes + determine_mode(dpy, w); + // Fading in calc_opacity(dpy, w, True); set_fade_callback(dpy, w, NULL, True); @@ -1620,15 +1867,23 @@ static void determine_shadow(Display *dpy, win *w) { Bool shadow_old = w->shadow; - w->shadow = win_type_shadow[w->window_type]; + w->shadow = (win_type_shadow[w->window_type] + && !win_match(w, shadow_blacklist, &w->cache_sblst)); // Window extents need update on shadow state change if (w->shadow != shadow_old) { // Shadow geometry currently doesn't change on shadow state change // calc_shadow_geometry(dpy, w); if (w->extents) { - free_region(dpy, &w->extents); + // Mark the old extents as damaged if the shadow is removed + if (!w->shadow) + add_damage(dpy, w->extents); + else + free_region(dpy, &w->extents); w->extents = win_extents(dpy, w); + // Mark the new extents as damaged if the shadow is added + if (w->shadow) + add_damage_win(dpy, w); } } } @@ -1666,7 +1921,7 @@ calc_shadow_geometry(Display *dpy, win *w) { static void mark_client_win(Display *dpy, win *w, Window client) { w->client_win = client; - + // Get the frame width and monitor further frame width changes on client // window if necessary if (frame_opacity) { @@ -1721,6 +1976,12 @@ add_win(Display *dpy, Window id, Window prev, Bool override_redirect) { new->damage = XDamageCreate(dpy, id, XDamageReportNonEmpty); } + new->name = NULL; + new->class_instance = NULL; + new->class_general = NULL; + new->cache_sblst = NULL; + new->cache_fblst = NULL; + new->border_size = None; new->extents = None; new->shadow = False; @@ -1815,7 +2076,7 @@ restack_win(Display *dpy, win *w, Window new_above) { if (root == c->id) { window_name = "(Root window)"; } else { - to_free = window_get_name(c->id, &window_name); + to_free = wid_get_name(dpy, c->id, &window_name); } desc = ""; @@ -2131,39 +2392,127 @@ expose_root(Display *dpy, Window root, XRectangle *rects, int nrects) { add_damage(dpy, region); } -#if defined(DEBUG_EVENTS) || defined(DEBUG_RESTACK) -static int -window_get_name(Window w, char **name) { - Atom prop = XInternAtom(dpy, "_NET_WM_NAME", False); - Atom utf8_type = XInternAtom(dpy, "UTF8_STRING", False); - Atom actual_type; - int actual_format; - unsigned long nitems; - unsigned long leftover; - char *data = NULL; - Status ret; +static Bool +wid_get_text_prop(Display *dpy, Window wid, Atom prop, + char ***pstrlst, int *pnstr) { + XTextProperty text_prop; - set_ignore(dpy, NextRequest(dpy)); + if (!(XGetTextProperty(dpy, wid, &text_prop, prop) && text_prop.value)) + return False; - if (Success != (ret = XGetWindowProperty(dpy, w, prop, 0L, (long) BUFSIZ, - False, utf8_type, &actual_type, &actual_format, &nitems, - &leftover, (unsigned char **) &data))) { - if (BadWindow == ret) return 0; + if (Success != + XmbTextPropertyToTextList(dpy, &text_prop, pstrlst, pnstr) + || !*pnstr) { + *pnstr = 0; + if (*pstrlst) + XFreeStringList(*pstrlst); + return False; + } - set_ignore(dpy, NextRequest(dpy)); - printf("Window %#010lx: _NET_WM_NAME unset, falling back to WM_NAME.\n", w); + return True; +} - if (!XFetchName(dpy, w, &data)) { - return 0; +static Bool +wid_get_name(Display *dpy, Window wid, char **name) { + XTextProperty text_prop; + char **strlst = NULL; + int nstr = 0; + + // set_ignore(dpy, NextRequest(dpy)); + if (!(XGetTextProperty(dpy, wid, &text_prop, name_ewmh_atom) + && text_prop.value)) { + // set_ignore(dpy, NextRequest(dpy)); +#ifdef DEBUG_WINDATA + printf("wid_get_name(%#010lx): _NET_WM_NAME unset, falling back to WM_NAME.\n", wid); +#endif + + if (!(XGetWMName(dpy, wid, &text_prop) && text_prop.value)) { + return False; } } + if (Success != + XmbTextPropertyToTextList(dpy, &text_prop, &strlst, &nstr) + || !nstr || !strlst) { + if (strlst) + XFreeStringList(strlst); + return False; + } + *name = mstrcpy(strlst[0]); + + XFreeStringList(strlst); + + return True; +} + +static int +win_get_name(Display *dpy, win *w) { + Bool ret; + char *name_old = w->name; + + // Can't do anything if there's no client window + if (!w->client_win) + return False; + + // Get the name + ret = wid_get_name(dpy, w->client_win, &w->name); + + // Return -1 if wid_get_name() failed, 0 if name didn't change, 1 if + // it changes + if (!ret) + ret = -1; + else if (name_old && !strcmp(w->name, name_old)) + ret = 0; + else + ret = 1; + + // Keep the old name if there's no new one + if (w->name != name_old) + free(name_old); + +#ifdef DEBUG_WINDATA + printf("win_get_name(%#010lx): client = %#010lx, name = \"%s\", " + "ret = %d\n", w->id, w->client_win, w->name, ret); +#endif - // if (actual_type == utf8_type && actual_format == 8) - *name = (char *) data; - return 1; + return ret; } + +static Bool +win_get_class(Display *dpy, win *w) { + char **strlst = NULL; + int nstr = 0; + + // Can't do anything if there's no client window + if (!w->client_win) + return False; + + // Free and reset old strings + free(w->class_instance); + free(w->class_general); + w->class_instance = NULL; + w->class_general = NULL; + + // Retrieve the property string list + if (!wid_get_text_prop(dpy, w->client_win, class_atom, &strlst, &nstr)) + return False; + + // Copy the strings if successful + w->class_instance = mstrcpy(strlst[0]); + + if (nstr > 1) + w->class_general = mstrcpy(strlst[1]); + + XFreeStringList(strlst); + +#ifdef DEBUG_WINDATA + printf("win_get_class(%#010lx): client = %#010lx, " + "instance = \"%s\", general = \"%s\"\n", + w->id, w->client_win, w->class_instance, w->class_general); #endif + return True; +} + #ifdef DEBUG_EVENTS static int ev_serial(XEvent *ev) { @@ -2394,6 +2743,7 @@ ev_property_notify(XPropertyEvent *ev) { } } + // If frame extents property changes if (frame_opacity && ev->atom == extents_atom) { win *w = find_toplevel(dpy, ev->window); if (w) { @@ -2402,6 +2752,23 @@ ev_property_notify(XPropertyEvent *ev) { add_damage_win(dpy, w); } } + + // If name changes + if (track_wdata + && (name_atom == ev->atom || name_ewmh_atom == ev->atom)) { + win *w = find_toplevel(dpy, ev->window); + if (w && 1 == win_get_name(dpy, w)) + determine_shadow(dpy, w); + } + + // If class changes + if (track_wdata && class_atom == ev->atom) { + win *w = find_toplevel(dpy, ev->window); + if (w) { + win_get_class(dpy, w); + determine_shadow(dpy, w); + } + } } inline static void @@ -2433,38 +2800,37 @@ ev_shape_notify(XShapeEvent *ev) { inline static void ev_handle(XEvent *ev) { -#ifdef DEBUG_EVENTS - Window w; - char *window_name; - Bool to_free = False; -#endif - if ((ev->type & 0x7f) != KeymapNotify) { discard_ignore(dpy, ev->xany.serial); } #ifdef DEBUG_EVENTS - w = ev_window(ev); - window_name = "(Failed to get title)"; + if (ev->type != damage_event + XDamageNotify) { + Window w; + char *window_name; + Bool to_free = False; - if (w) { - if (root == w) { - window_name = "(Root window)"; - } else { - to_free = (Bool) window_get_name(w, &window_name); + w = ev_window(ev); + window_name = "(Failed to get title)"; + + if (w) { + if (root == w) { + window_name = "(Root window)"; + } else { + to_free = (Bool) wid_get_name(dpy, w, &window_name); + } } - } - if (ev->type != damage_event + XDamageNotify) { print_timestamp(); printf("event %10.10s serial %#010x window %#010lx \"%s\"\n", ev_name(ev), ev_serial(ev), w, window_name); - } - if (to_free) { - XFree(window_name); - window_name = NULL; + if (to_free) { + XFree(window_name); + window_name = NULL; + } } + #endif switch (ev->type) { @@ -2519,10 +2885,11 @@ ev_handle(XEvent *ev) { static void usage(void) { - fprintf(stderr, "compton v0.0.1\n"); + fprintf(stderr, "compton (development version)\n"); fprintf(stderr, "usage: compton [options]\n"); fprintf(stderr, - "Options\n" + "Options:\n" + "\n" "-d display\n" " Which display should be managed.\n" "-r radius\n" @@ -2570,7 +2937,28 @@ usage(void) { "--inactive-opacity-override\n" " Inactive opacity set by -i overrides value of _NET_WM_OPACITY.\n" "--inactive-dim value\n" - " Dim inactive windows. (0.0 - 1.0, defaults to 0)\n"); + " Dim inactive windows. (0.0 - 1.0, defaults to 0)\n" + "--mark-wmwin-focused\n" + " Try to detect WM windows and mark them as active.\n" + "--shadow-exclude condition\n" + " Exclude conditions for shadows.\n" + "\n" + "Format of a condition:\n" + "\n" + " condition = <target>:<type>[<flags>]:<pattern>\n" + "\n" + " <target> is one of \"n\" (window name), \"i\" (window class\n" + " instance), and \"g\" (window general class)\n" + "\n" + " <type> 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" + " <flags> could a serious of flags. Currently the only defined\n" + " flag is \"i\" (ignore case).\n" + "\n" + " <pattern> is the actual pattern string.\n" + ); exit(1); } @@ -2634,7 +3022,10 @@ get_atoms(void) { extents_atom = XInternAtom(dpy, "_NET_FRAME_EXTENTS", False); opacity_atom = XInternAtom(dpy, "_NET_WM_WINDOW_OPACITY", False); frame_extents_atom = XInternAtom(dpy, "_NET_FRAME_EXTENTS", False); - client_atom = XInternAtom(dpy, "WM_STATE", False); + client_atom = XInternAtom(dpy, "WM_CLASS", False); + name_atom = XInternAtom(dpy, "WM_NAME", False); + name_ewmh_atom = XInternAtom(dpy, "_NET_WM_NAME", False); + class_atom = XInternAtom(dpy, "WM_CLASS", False); win_type_atom = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); @@ -2678,6 +3069,7 @@ main(int argc, char **argv) { { "inactive-opacity-override", no_argument, NULL, 0 }, { "inactive-dim", required_argument, NULL, 0 }, { "mark-wmwin-focused", no_argument, NULL, 0 }, + { "shadow-exclude", required_argument, NULL, 0 }, // Must terminate with a NULL entry { NULL, 0, NULL, 0 }, }; @@ -2703,6 +3095,10 @@ main(int argc, char **argv) { gettimeofday(&time_start, NULL); + // Set locale so window names with special characters are interpreted + // correctly + setlocale (LC_ALL, ""); + for (i = 0; i < NUM_WINTYPES; ++i) { win_type_fade[i] = False; win_type_shadow[i] = False; @@ -2734,6 +3130,9 @@ main(int argc, char **argv) { case 5: mark_wmwin_focused = True; break; + case 6: + condlst_add(&shadow_blacklist, optarg); + break; } break; // Short options @@ -2831,6 +3230,10 @@ main(int argc, char **argv) { track_focus = True; } + // Determine whether we need to track window name and class + if (shadow_blacklist || fade_blacklist) + track_wdata = True; + fade_time = get_time_in_milliseconds(); dpy = XOpenDisplay(display); |