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.
This commit is contained in:
Richard Grenville 2013-01-28 21:39:38 +08:00
parent 00424b1082
commit 6d36ef2d0f
11 changed files with 1839 additions and 392 deletions

2
.gitignore vendored
View File

@ -38,4 +38,6 @@ oprofile_data/
compton.plist
callgrind.out.*
man/*.html
man/*.1
doxygen/
.clang_complete

View File

@ -55,10 +55,10 @@ ifeq "$(NO_DBUS)" ""
endif
# ==== C2 ====
# ifeq "$(NO_C2)" ""
# CFG += -DCONFIG_C2
# OBJS += c2.o
# endif
ifeq "$(NO_C2)" ""
CFG += -DCONFIG_C2
OBJS += c2.o
endif
# === Version string ===
COMPTON_VERSION ?= git-$(shell git describe --always --dirty)-$(shell git log -1 --date=short --pretty=format:%cd)
@ -78,6 +78,9 @@ MANPAGES_HTML = $(addsuffix .html,$(MANPAGES))
# === Recipes ===
.DEFAULT_GOAL := compton
.clang_complete: Makefile
@(for i in $(filter-out -O% -DNDEBUG, $(CFLAGS) $(INCS)); do echo "$$i"; done) > $@
%.o: src/%.c src/%.h src/common.h
$(CC) $(CFLAGS) $(INCS) -c src/$*.c
@ -105,7 +108,7 @@ uninstall:
@rm -f "$(DESTDIR)$(MANDIR)/compton-trans.1"
clean:
@rm -f $(OBJS) compton $(MANPAGES) $(MANPAGES_HTML)
@rm -f $(OBJS) compton $(MANPAGES) $(MANPAGES_HTML) .clang_complete
version:
@echo "$(COMPTON_VERSION)"

View File

@ -10,7 +10,7 @@ shadow-offset-y = -7;
# shadow-red = 0.0;
# shadow-green = 0.0;
# shadow-blue = 0.0;
shadow-exclude = [ "n:e:Notification", "g:e:Conky" ];
shadow-exclude = [ "name = 'Notification'", "class_g = 'Conky'" ];
# shadow-exclude = "n:e:Notification";
shadow-ignore-shaped = false;

View File

@ -26,7 +26,7 @@ type_enum='uint16'
dbus-send --print-reply --dest="$service" "$object" "${interface}.list_win"
# Get window ID of currently focused window
focused=$(dbus-send --print-reply --dest="$service" "$object" "${interface}.find_win" string:focused | $SED -n 's/^[[:space:]]*'${type_win}'\s*\([[:digit:]]*\).*/\1/p')
focused=$(dbus-send --print-reply --dest="$service" "$object" "${interface}.find_win" string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p')
if [ -n "$focused" ]; then
# Get invert_color_force property of the window

View File

@ -180,8 +180,77 @@ OPTIONS
FORMAT OF CONDITIONS
--------------------
Some options accept a condition string to match certain windows. A condition string is formed by one or more conditions, joined by logical operators.
*--shadow-exclude* and *--focus-exclude* uses a condition string to blacklist certain windows. The format of the condition string is:
A condition with "exists" operator looks like this:
<NEGATION> <TARGET> <CLIENT/FRAME> [<INDEX>] : <FORMAT> <TYPE>
With equals operator it looks like:
<NEGATION> <TARGET> <CLIENT/FRAME> [<INDEX>] : <FORMAT> <TYPE> <NEGATION> <OP QUALIFIER> <MATCH TYPE> = <PATTERN>
With greater-than/less-than operators it looks like:
<NEGATION> <TARGET> <CLIENT/FRAME> [<INDEX>] : <FORMAT> <TYPE> <NEGATION> <OPERATOR> <PATTERN>
'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`, `override_redirect`, `focused`, `wmwin`, `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.
'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.
'TYPE' is a single character representing the type of the property to match for: `c` for 'CARDINAL', `a` for 'ATOM', `w` for 'WINDOW', `d` for 'DRAWABLE', `s` for 'STRING' (and any other string types, such as 'UTF8_STRING'). Do not specify it for predefined targets.
'OP QUALIFIER' (optional), applicable only for equals operator, could be `?` (ignore-case).
'MATCH TYPE' (optional), applicable only for equals operator, could be nothing (exact match), `*` (match anywhere), `^` (match from start), `%` (wildcard), or `~` (PCRE regular expression).
'OPERATOR' is one of `=` (equals), `<`, `>`, `<=`, `=>`, or nothing (exists). Exists operator checks whether a property exists on a window (but for predefined targets, exists means != 0 then).
'PATTERN' is either an integer or a string enclosed by single or double quotes. Python-3-style escape sequences and raw string are supported in the string format.
Supported logical operators are `&&` (and) and `||` (or). `&&` has higher precedence than `||`, left-to-right associativity. Use parentheses to change precedence.
Examples:
# If the window is focused
focused
focused = 1
# If the window is not override-redirected
!override_redirect
override_redirect = false
override_redirect != true
override_redirect != 1
# If the window is a menu
window_type *= "menu"
_NET_WM_WINDOW_TYPE@:a *= "MENU"
# If the window name contains "Firefox", ignore case
name *?= "Firefox"
_NET_WM_NAME@:s *?= "Firefox"
# If the window name ends with "Firefox"
name %= "*Firefox"
name ~= "Firefox$"
# If the window has a property _COMPTON_SHADOW with value 0, type CARDINAL,
# format 32, value 0, on its frame window
_COMPTON_SHADOW:32c = 0
# If the third value of _NET_FRAME_EXTENTS is less than 20, or there's no
# _NET_FRAME_EXTENTS property on client window
_NET_FRAME_EXTENTS@[2]:32c < 20 || !_NET_FRAME_EXTENTS@:32c
# The pattern here will be parsed as "dd4"
name = "\x64\x64\o64"
# The pattern here will be parsed as "\x64\x64\x64"
name = r"\x64\x64\o64"
LEGACY FORMAT OF CONDITIONS
---------------------------
This is the old condition format we once used. Support of this format might be removed in the future.
condition = TARGET:TYPE[FLAGS]:PATTERN
@ -239,7 +308,7 @@ $ compton -c --shadow-red 1 --shadow-green 1 --shadow-blue 1
* Avoid drawing shadows on wbar window:
+
------------
$ compton -c --shadow-exclude 'g:e:wbar'
$ compton -c --shadow-exclude 'class_g = "wbar"'
------------
* Enable OpenGL vsync:

1296
src/c2.c Normal file

File diff suppressed because it is too large Load Diff

326
src/c2.h Normal file
View File

@ -0,0 +1,326 @@
/*
* Compton - a compositor for X11
*
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2011-2013, Christopher Jeffrey
* See LICENSE for more information.
*
*/
#include "common.h"
#include <fnmatch.h>
#include <ctype.h>
// libpcre
#ifdef CONFIG_REGEX_PCRE
#include <pcre.h>
// For compatiblity with <libpcre-8.20
#ifndef PCRE_STUDY_JIT_COMPILE
#define PCRE_STUDY_JIT_COMPILE 0
#define LPCRE_FREE_STUDY(extra) pcre_free(extra)
#else
#define LPCRE_FREE_STUDY(extra) pcre_free_study(extra)
#endif
#endif
#define C2_MAX_LEVELS 10
typedef struct _c2_b c2_b_t;
typedef struct _c2_l c2_l_t;
/// Pointer to a condition tree.
typedef struct {
bool isbranch : 1;
union {
c2_b_t *b;
c2_l_t *l;
};
} c2_ptr_t;
/// Initializer for c2_ptr_t.
#define C2_PTR_INIT { \
.isbranch = false, \
.l = NULL, \
}
const static c2_ptr_t C2_PTR_NULL = C2_PTR_INIT;
/// Operator of a branch element.
typedef enum {
C2_B_OUNDEFINED,
C2_B_OAND,
C2_B_OOR,
C2_B_OXOR,
} c2_b_op_t;
/// Structure for branch element in a window condition
struct _c2_b {
bool neg : 1;
c2_b_op_t op;
c2_ptr_t opr1;
c2_ptr_t opr2;
};
/// Initializer for c2_b_t.
#define C2_B_INIT { \
.neg = false, \
.op = C2_B_OUNDEFINED, \
.opr1 = C2_PTR_INIT, \
.opr2 = C2_PTR_INIT, \
}
/// Structure for leaf element in a window condition
struct _c2_l {
bool neg : 1;
enum {
C2_L_OEXISTS,
C2_L_OEQ,
C2_L_OGT,
C2_L_OGTEQ,
C2_L_OLT,
C2_L_OLTEQ,
} op : 3;
enum {
C2_L_MEXACT,
C2_L_MSTART,
C2_L_MCONTAINS,
C2_L_MWILDCARD,
C2_L_MPCRE,
} match : 3;
bool match_ignorecase : 1;
char *tgt;
Atom tgtatom;
bool tgt_onframe;
int index;
enum {
C2_L_PUNDEFINED,
C2_L_PID,
C2_L_POVREDIR,
C2_L_PFOCUSED,
C2_L_PWMWIN,
C2_L_PCLIENT,
C2_L_PWINDOWTYPE,
C2_L_PLEADER,
C2_L_PNAME,
C2_L_PCLASSG,
C2_L_PCLASSI,
C2_L_PROLE,
} predef;
enum c2_l_type {
C2_L_TUNDEFINED,
C2_L_TSTRING,
C2_L_TCARDINAL,
C2_L_TWINDOW,
C2_L_TATOM,
C2_L_TDRAWABLE,
} type;
int format;
enum {
C2_L_PTUNDEFINED,
C2_L_PTSTRING,
C2_L_PTINT,
} ptntype;
char *ptnstr;
long ptnint;
#ifdef CONFIG_REGEX_PCRE
pcre *regex_pcre;
pcre_extra *regex_pcre_extra;
#endif
};
/// Initializer for c2_l_t.
#define C2_L_INIT { \
.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, \
.format = 0, \
.ptntype = C2_L_PTUNDEFINED, \
.ptnstr = NULL, \
.ptnint = 0, \
}
const static c2_l_t leaf_def = C2_L_INIT;
/// Linked list type of conditions.
struct _c2_lptr {
c2_ptr_t ptr;
struct _c2_lptr *next;
};
/// Initializer for c2_lptr_t.
#define C2_LPTR_INIT { \
.ptr = C2_PTR_INIT, \
.next = NULL, \
}
/// Structure representing a predefined target.
typedef struct {
const char *name;
enum c2_l_type type;
int format;
} c2_predef_t;
// Predefined targets.
const static c2_predef_t C2_PREDEFS[] = {
[C2_L_PID ] = { "id" , C2_L_TCARDINAL , 0 },
[C2_L_POVREDIR ] = { "override_redirect" , C2_L_TCARDINAL , 0 },
[C2_L_PFOCUSED ] = { "focused" , C2_L_TCARDINAL , 0 },
[C2_L_PWMWIN ] = { "wmwin" , C2_L_TCARDINAL , 0 },
[C2_L_PCLIENT ] = { "client" , C2_L_TWINDOW , 0 },
[C2_L_PWINDOWTYPE ] = { "window_type" , C2_L_TSTRING , 0 },
[C2_L_PLEADER ] = { "leader" , C2_L_TWINDOW , 0 },
[C2_L_PNAME ] = { "name" , C2_L_TSTRING , 0 },
[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 },
};
#define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1))
/**
* Compare next word in a string with another string.
*/
static inline int
strcmp_wd(const char *needle, const char *src) {
int ret = mstrncmp(needle, src);
if (ret)
return ret;
char c = src[strlen(needle)];
if (isalnum(c) || '_' == c)
return 1;
else
return 0;
}
/**
* Return whether a c2_ptr_t is empty.
*/
static inline bool
c2_ptr_isempty(const c2_ptr_t p) {
return !(p.isbranch ? (bool) p.b: (bool) p.l);
}
/**
* Reset a c2_ptr_t.
*/
static inline void
c2_ptr_reset(c2_ptr_t *pp) {
if (pp)
memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t));
}
/**
* Combine two condition trees.
*/
static inline c2_ptr_t
c2h_comb_tree(c2_b_op_t op, c2_ptr_t p1, c2_ptr_t p2) {
c2_ptr_t p = {
.isbranch = true,
.b = malloc(sizeof(c2_b_t))
};
p.b->opr1 = p1;
p.b->opr2 = p2;
p.b->op = op;
return p;
}
/**
* Get the precedence value of a condition branch operator.
*/
static inline int
c2h_b_opp(c2_b_op_t op) {
switch (op) {
case C2_B_OAND: return 2;
case C2_B_OOR: return 1;
case C2_B_OXOR: return 1;
default: break;
}
assert(0);
return 0;
}
/**
* Compare precedence of two condition branch operators.
*
* Associativity is left-to-right, forever.
*
* @return positive number if op1 > op2, 0 if op1 == op2 in precedence,
* negative number otherwise
*/
static inline int
c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) {
return c2h_b_opp(op1) - c2h_b_opp(op2);
}
static int
c2_parse_grp(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult, int level);
static int
c2_parse_target(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult);
static int
c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult);
static int
c2_parse_pattern(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult);
static int
c2_parse_legacy(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult);
static bool
c2_l_postprocess(session_t *ps, c2_l_t *pleaf);
static void
c2_free(c2_ptr_t p);
/**
* Wrapper of c2_free().
*/
static inline void
c2_freep(c2_ptr_t *pp) {
if (pp) {
c2_free(*pp);
c2_ptr_reset(pp);
}
}
static const char *
c2h_dump_str_tgt(const c2_l_t *pleaf);
static const char *
c2h_dump_str_type(const c2_l_t *pleaf);
static void
c2_dump_raw(c2_ptr_t p);
/**
* Wrapper of c2_dump_raw().
*/
static inline void
c2_dump(c2_ptr_t p) {
c2_dump_raw(p);
printf("\n");
fflush(stdout);
}
static Atom
c2_get_atom_type(const c2_l_t *pleaf);
static bool
c2_match_once(session_t *ps, win *w, const c2_ptr_t cond);

View File

@ -76,20 +76,6 @@
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/Xdbe.h>
// libpcre
#ifdef CONFIG_REGEX_PCRE
#include <pcre.h>
// For compatiblity with <libpcre-8.20
#ifndef PCRE_STUDY_JIT_COMPILE
#define PCRE_STUDY_JIT_COMPILE 0
#define LPCRE_FREE_STUDY(extra) pcre_free(extra)
#else
#define LPCRE_FREE_STUDY(extra) pcre_free_study(extra)
#endif
#endif
// libconfig
#ifdef CONFIG_LIBCONFIG
#include <libgen.h>
@ -257,18 +243,6 @@ enum wincond_type {
#define CONDF_IGNORECASE 0x0001
typedef struct _wincond {
enum wincond_target target;
enum wincond_type type;
char *pattern;
#ifdef CONFIG_REGEX_PCRE
pcre *regex_pcre;
pcre_extra *regex_pcre_extra;
#endif
int16_t flags;
struct _wincond *next;
} wincond_t;
/// VSync modes.
typedef enum {
VSYNC_NONE,
@ -297,13 +271,13 @@ struct _timeout_t;
struct _win;
#ifdef CONFIG_C2
typedef struct _c2_lptr c2_lptr_t;
#endif
/// Structure representing all options.
typedef struct {
// === General ===
/// The configuration file we used.
char *config_file;
/// The display name we used. NULL means we are using the value of the
/// <code>DISPLAY</code> environment variable.
char *display;
@ -350,7 +324,7 @@ typedef struct {
double shadow_opacity;
bool clear_shadow;
/// Shadow blacklist. A linked list of conditions.
wincond_t *shadow_blacklist;
c2_lptr_t *shadow_blacklist;
/// Whether bounding-shaped window should be ignored.
bool shadow_ignore_shaped;
/// Whether to respect _COMPTON_SHADOW.
@ -368,7 +342,7 @@ typedef struct {
/// Whether to disable fading on window open/close.
bool no_fading_openclose;
/// Fading blacklist. A linked list of conditions.
wincond_t *fade_blacklist;
c2_lptr_t *fade_blacklist;
// === Opacity ===
/// Default opacity for specific window types
@ -404,7 +378,7 @@ typedef struct {
/// based on window opacity.
bool inactive_dim_fixed;
/// Conditions of windows to have inverted colors.
wincond_t *invert_color_list;
c2_lptr_t *invert_color_list;
// === Focus related ===
/// Consider windows of specific types to be always focused.
@ -412,7 +386,7 @@ typedef struct {
/// Whether to use EWMH _NET_ACTIVE_WINDOW to find active window.
bool use_ewmh_active_win;
/// A list of windows always to be considered focused.
wincond_t *focus_blacklist;
c2_lptr_t *focus_blacklist;
/// Whether to do window grouping with <code>WM_TRANSIENT_FOR</code>.
bool detect_transient;
/// Whether to do window grouping with <code>WM_CLIENT_LEADER</code>.
@ -736,10 +710,10 @@ typedef struct _win {
char *class_general;
/// <code>WM_WINDOW_ROLE</code> value of the window.
char *role;
wincond_t *cache_sblst;
wincond_t *cache_fblst;
wincond_t *cache_fcblst;
wincond_t *cache_ivclst;
const c2_lptr_t *cache_sblst;
const c2_lptr_t *cache_fblst;
const c2_lptr_t *cache_fcblst;
const c2_lptr_t *cache_ivclst;
// Opacity-related members
/// Current window opacity.
@ -1056,10 +1030,13 @@ print_timestamp(session_t *ps) {
/**
* Allocate the space and copy a string.
*/
static inline char * __attribute__((const))
static inline char *
mstrcpy(const char *src) {
char *str = malloc(sizeof(char) * (strlen(src) + 1));
if (!str)
printf_errfq(1, "(): Failed to allocate memory.");
strcpy(str, src);
return str;
@ -1068,10 +1045,13 @@ mstrcpy(const char *src) {
/**
* Allocate the space and copy a string.
*/
static inline char * __attribute__((const))
static inline char *
mstrncpy(const char *src, unsigned len) {
char *str = malloc(sizeof(char) * (len + 1));
if (!str)
printf_errfq(1, "(): Failed to allocate memory.");
strncpy(str, src, len);
str[len] = '\0';
@ -1479,7 +1459,7 @@ win_set_invert_color_force(session_t *ps, win *w, switch_t val);
///@{
c2_lptr_t *
c2_parse(session_t *ps, c2_lptr_t **pcondlst, char *pattern);
c2_parse(session_t *ps, c2_lptr_t **pcondlst, const char *pattern);
c2_lptr_t *
c2_free_lptr(c2_lptr_t *lp);

View File

@ -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;
#ifdef CONFIG_C2
if (!c2_parse(ps, pcondlst, pattern))
exit(1);
#else
printf_errfq(1, "(): Condition support not compiled in.");
#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;
}
@ -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 = <target>:<type>[<flags>]:<pattern>\n"
"\n"
" <target> is one of \"n\" (window name), \"i\" (window class\n"
" instance), \"g\" (window general class), and \"r\"\n"
" (window role).\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 be a series of flags. Currently the only defined\n"
" flag is \"i\" (ignore case).\n"
"\n"
" <pattern> 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);

View File

@ -15,7 +15,6 @@
#include <unistd.h>
#include <getopt.h>
#include <locale.h>
#include <fnmatch.h>
#include <signal.h>
#ifdef CONFIG_VSYNC_DRM
@ -165,37 +164,16 @@ free_damage(session_t *ps, Damage *p) {
}
}
#ifdef CONFIG_C2
/**
* Destroy a <code>wincond_t</code>.
* Destroy a condition list.
*/
inline static void
free_wincond(wincond_t *cond) {
if (cond->pattern)
free(cond->pattern);
#ifdef CONFIG_REGEX_PCRE
if (cond->regex_pcre_extra)
LPCRE_FREE_STUDY(cond->regex_pcre_extra);
if (cond->regex_pcre)
pcre_free(cond->regex_pcre);
static inline void
free_wincondlst(c2_lptr_t **pcondlst) {
while ((*pcondlst = c2_free_lptr(*pcondlst)))
continue;
}
#endif
free(cond);
}
/**
* Destroy a linked list of <code>wincond_t</code>.
*/
inline static void
free_wincondlst(wincond_t **cond_lst) {
wincond_t *next = NULL;
for (wincond_t *cond = *cond_lst; cond; cond = next) {
next = cond->next;
free_wincond(cond);
}
*cond_lst = NULL;
}
/**
* Destroy all resources in a <code>struct _win</code>.
@ -396,14 +374,20 @@ win_is_fullscreen(session_t *ps, const win *w) {
static void
win_rounded_corners(session_t *ps, win *w);
static bool
win_match_once(win *w, const wincond_t *cond);
/**
* Wrapper of c2_match().
*/
static inline bool
win_match(session_t *ps, win *w, c2_lptr_t *condlst, const c2_lptr_t **cache) {
#ifdef CONFIG_C2
return c2_match(ps, w, condlst, cache);
#else
return false;
#endif
}
static bool
win_match(win *w, wincond_t *condlst, wincond_t * *cache);
static bool
condlst_add(wincond_t **pcondlst, const char *pattern);
condlst_add(session_t *ps, c2_lptr_t **pcondlst, const char *pattern);
static long
determine_evmask(session_t *ps, Window wid, win_evmode_t mode);
@ -594,7 +578,7 @@ static void
win_on_wtype_change(session_t *ps, win *w);
static void
win_on_wdata_change(session_t *ps, win *w);
win_on_factor_change(session_t *ps, win *w);
static void
win_upd_run(session_t *ps, win *w, win_upd_t *pupd);
@ -858,15 +842,15 @@ static FILE *
open_config_file(char *cpath, char **path);
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);
static void
parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp);
parse_config(session_t *ps, struct options_tmp *pcfgtmp);
#endif
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);
static void
init_atoms(session_t *ps);

View File

@ -859,6 +859,7 @@ cdbus_process_opts_get(session_t *ps, DBusMessage *msg) {
return true;
}
cdbus_m_opts_get_do(config_file, cdbus_reply_string);
cdbus_m_opts_get_do(mark_wmwin_focused, cdbus_reply_bool);
cdbus_m_opts_get_do(mark_ovredir_focused, cdbus_reply_bool);
cdbus_m_opts_get_do(fork_after_register, cdbus_reply_bool);