Feature: Configuration file parsing

- Add support for parsing configuration files using libconfig.
  (Dependency on libconfig could be made optional once we get some
  better building system.) Few tests has been done, bugs to be expected.
  compton searches for a configuration file mostly according to the XDG
  standard. Firstly the configuration file requested by --config, then
  $XDG_CONFIG_HOME/compton.conf (~/.config/compton.conf, usually), then
  ~/.compton.conf, then compton.conf under $XDG_DATA_DIRS (often
  /etc/xdg/compton.conf). A sample configuration file is supplied as
  compton.sample.conf. Configuration file syntax may change in the
  future.  Commandline switches has higher priority than configuration
  file, except for --shadow-exclude. Use --config /dev/null to
  temporarily disable configuration file.

- Fix a bug that causes windows to disappear or be partially rendered on
  opacity changes.

- Fix a bug that causes some windows to ignore -i (inactive_opacity) and
  --inactive-dim, caused by the default window type change in
  a5d9955ca4.
This commit is contained in:
Richard Grenville 2012-09-25 10:19:20 +08:00
parent ce51d564d5
commit ee9e90efec
5 changed files with 522 additions and 165 deletions

View File

@ -4,7 +4,7 @@ PREFIX ?= /usr
BINDIR ?= $(PREFIX)/bin
MANDIR ?= $(PREFIX)/share/man/man1
PACKAGES = x11 xcomposite xfixes xdamage xrender xext
PACKAGES = x11 xcomposite xfixes xdamage xrender xext libconfig
LIBS = $(shell pkg-config --libs $(PACKAGES)) -lm
LIBS += $(shell pcre-config --libs)
INCS = $(shell pkg-config --cflags $(PACKAGES))

View File

@ -51,6 +51,7 @@ __R__ for runtime
* bash (R)
* xprop,xwininfo / x11-utils (R)
* libpcre (B,R) (Will probably be made optional soon)
* libconfig (B,R) (Will probably be made optional soon)
To build, make sure you have the above dependencies:

29
compton.sample.conf Normal file
View File

@ -0,0 +1,29 @@
# Shadow
shadow = true;
no-dnd-shadow = true;
no-dock-shadow = true;
clear-shadow = true;
shadow-radius = 7
shadow-offset-x = -7
shadow-offset-y = -7
# shadow-opacity = 0.7
# shadow-red = 0.0
# shadow-green = 0.0
# shadow-blue = 0.0
shadow-exclude = [ "n:e:Notification" ];
# shadow-exclude = "n:e:Notification";
# Opacity
menu-opacity = 0.8;
inactive-opacity = 0.8;
frame-opacity = 0.7;
inactive-opacity-override = true;
# Fading
fading = true;
# fade-delta = 30
fade-in-step = 0.03
fade-out-step = 0.03
# Other
mark-wmwin-focused = true;

View File

@ -87,10 +87,6 @@ Bool win_type_fade[NUM_WINTYPES];
* Macros
*/
#define IS_NORMAL_WIN(w) \
((w) && ((w)->window_type == WINTYPE_NORMAL \
|| (w)->window_type == WINTYPE_UTILITY))
#define HAS_FRAME_OPACITY(w) \
(frame_opacity && (w)->top_width)
@ -98,6 +94,7 @@ Bool win_type_fade[NUM_WINTYPES];
* Options
*/
char *display = NULL;
int shadow_radius = 12;
int shadow_offset_x = -15;
int shadow_offset_y = -15;
@ -137,8 +134,20 @@ wincond *shadow_blacklist = NULL;
/// Fading blacklist. A linked list of conditions.
wincond *fade_blacklist = NULL;
/// Whether to fork to background.
Bool fork_after_register = False;
/// Red, green and blue tone of the shadow.
double shadow_red = 0.0, shadow_green = 0.0, shadow_blue = 0.0;
Bool synchronize = False;
// Temporary options
Bool shadow_enable = False;
Bool fading_enable = False;
Bool no_dock_shadow = False;
Bool no_dnd_shadow = False;
double menu_opacity = 1.0;
/**
* Fades
*/
@ -740,6 +749,9 @@ win_match(win *w, wincond *condlst, wincond **cache) {
*/
static Bool
condlst_add(wincond **pcondlst, const char *pattern) {
if (!pattern)
return False;
unsigned plen = strlen(pattern);
wincond *cond;
const char *pos;
@ -1231,6 +1243,10 @@ paint_preprocess(Display *dpy, win *list) {
if (!w->extents) {
w->extents = win_extents(dpy, w);
// If w->extents does not exist, the previous add_damage_win()
// call when opacity changes has no effect, so redo it here.
if (w->opacity != opacity_old)
add_damage_win(dpy, w);
}
// Rebuild alpha_pict only if necessary
@ -1765,8 +1781,6 @@ determine_mode(Display *dpy, win *w) {
}
w->mode = mode;
add_damage_win(dpy, w);
}
/**
@ -1811,7 +1825,7 @@ calc_opacity(Display *dpy, win *w, Bool refetch_prop) {
}
// Respect inactive_opacity in some cases
if (inactive_opacity && IS_NORMAL_WIN(w) && False == w->focused
if (inactive_opacity && is_normal_win(w) && False == w->focused
&& (OPAQUE == opacity || inactive_opacity_override)) {
opacity = inactive_opacity;
}
@ -1823,7 +1837,7 @@ static void
calc_dim(Display *dpy, win *w) {
Bool dim;
if (inactive_dim && IS_NORMAL_WIN(w) && !(w->focused)) {
if (inactive_dim && is_normal_win(w) && !(w->focused)) {
dim = True;
} else {
dim = False;
@ -2916,6 +2930,8 @@ usage(void) {
" Daemonize process.\n"
"-S\n"
" Enable synchronous operation (for debugging).\n"
"--config path\n"
" Look for configuration file at the path.\n"
"--shadow-red value\n"
" Red color value of shadow (0.0 - 1.0, defaults to 0).\n"
"--shadow-green value\n"
@ -3005,6 +3021,402 @@ fork_after(void) {
freopen("/dev/null", "w", stderr);
}
#ifdef CONFIG_LIBCONFIG
/**
* Get a file stream of the configuration file to read.
*
* Follows the XDG specification to search for the configuration file.
*/
static FILE *
open_config_file(char *cpath, char **ppath) {
const static char *config_filename = "/compton.conf";
const static char *config_filename_legacy = "/.compton.conf";
const static char *config_home_suffix = "/.config";
const static char *config_system_dir = "/etc/xdg";
char *dir = NULL, *home = NULL;
char *path = cpath;
FILE *f = NULL;
if (path) {
f = fopen(path, "r");
if (f && ppath)
*ppath = path;
else
free(path);
return f;
}
// Check user configuration file in $XDG_CONFIG_HOME firstly
if (!((dir = getenv("XDG_CONFIG_HOME")) && strlen(dir))) {
if (!((home = getenv("HOME")) && strlen(home)))
return NULL;
path = mstrjoin3(home, config_home_suffix, config_filename);
}
else
path = mstrjoin(dir, config_filename);
f = fopen(path, "r");
if (f && ppath)
*ppath = path;
else
free(path);
if (f)
return f;
// Then check user configuration file in $HOME
if ((home = getenv("HOME")) && strlen(home)) {
path = mstrjoin(home, config_filename_legacy);
f = fopen(path, "r");
if (f && ppath)
*ppath = path;
else
free(path);
if (f)
return f;
}
// Check system configuration file in $XDG_CONFIG_DIRS at last
if ((dir = getenv("XDG_CONFIG_DIRS")) && strlen(dir)) {
char *part = strtok(dir, ":");
while (part) {
path = mstrjoin(part, config_filename);
f = fopen(path, "r");
if (f && ppath)
*ppath = path;
else
free(path);
if (f)
return f;
part = strtok(NULL, ":");
}
}
else {
path = mstrjoin(config_system_dir, config_filename);
f = fopen(path, "r");
if (f && ppath)
*ppath = path;
else
free(path);
if (f)
return f;
}
return NULL;
}
/**
* Parse a configuration file from default location.
*/
static void
parse_config(char *cpath) {
char *path = NULL, *parent = NULL;
FILE *f;
config_t cfg;
int ival = 0;
double dval = 0.0;
f = open_config_file(cpath, &path);
if (!f) {
if (cpath)
printf("Failed to read the specified configuration file.\n");
return;
}
config_init(&cfg);
parent = dirname(path);
if (parent)
config_set_include_dir(&cfg, parent);
if (CONFIG_FALSE == config_read(&cfg, f)) {
printf("Error when reading configuration file \"%s\", line %d: %s\n",
path, config_error_line(&cfg), config_error_text(&cfg));
config_destroy(&cfg);
free(path);
return;
}
config_set_auto_convert(&cfg, 1);
free(path);
// Get options from the configuration file. We don't do range checking
// right now. It will be done later
// -D (fade_delta)
if (config_lookup_int(&cfg, "fade-delta", &ival))
fade_delta = ival;
// -I (fade_in_step)
if (config_lookup_float(&cfg, "fade-in-step", &dval))
fade_in_step = normalize_d(dval) * OPAQUE;
// -O (fade_out_step)
if (config_lookup_float(&cfg, "fade-out-step", &dval))
fade_out_step = normalize_d(dval) * OPAQUE;
// -r (shadow_radius)
config_lookup_int(&cfg, "shadow-radius", &shadow_radius);
// -o (shadow_opacity)
config_lookup_float(&cfg, "shadow-opacity", &shadow_opacity);
// -l (shadow_offset_x)
config_lookup_int(&cfg, "shadow-offset-x", &shadow_offset_x);
// -t (shadow_offset_y)
config_lookup_int(&cfg, "shadow-offset-y", &shadow_offset_y);
// -i (inactive_opacity)
if (config_lookup_float(&cfg, "inactive-opacity", &dval))
inactive_opacity = normalize_d(dval) * OPAQUE;
// -e (frame_opacity)
config_lookup_float(&cfg, "frame-opacity", &frame_opacity);
// -z (clear_shadow)
if (config_lookup_bool(&cfg, "clear-shadow", &ival))
clear_shadow = ival;
// -c (shadow_enable)
if (config_lookup_bool(&cfg, "shadow", &ival))
shadow_enable = ival;
// -C (no_dock_shadow)
if (config_lookup_bool(&cfg, "no-dock-shadow", &ival))
no_dock_shadow = ival;
// -G (no_dnd_shadow)
if (config_lookup_bool(&cfg, "no-dnd-shadow", &ival))
no_dnd_shadow = ival;
// -m (menu_opacity)
config_lookup_float(&cfg, "menu-opacity", &menu_opacity);
// -f (fading_enable)
if (config_lookup_bool(&cfg, "fading", &ival))
fading_enable = ival;
// --shadow-red
config_lookup_float(&cfg, "shadow-red", &shadow_red);
// --shadow-green
config_lookup_float(&cfg, "shadow-green", &shadow_green);
// --shadow-blue
config_lookup_float(&cfg, "shadow-blue", &shadow_blue);
// --inactive-opacity-override
if (config_lookup_bool(&cfg, "inactive-opacity-override", &ival))
inactive_opacity_override = ival;
// --inactive-dim
config_lookup_float(&cfg, "inactive-dim", &inactive_dim);
// --mark-wmwin-focused
if (config_lookup_bool(&cfg, "mark-wmwin-focused", &ival))
mark_wmwin_focused = ival;
// --shadow-exclude
{
config_setting_t *shadow_blacklist_setting =
config_lookup(&cfg, "shadow-exclude");
if (shadow_blacklist_setting) {
// Parse an array of shadow-exclude
if (config_setting_is_array(shadow_blacklist_setting)) {
int i = config_setting_length(shadow_blacklist_setting);
while (i--) {
condlst_add(&shadow_blacklist,
config_setting_get_string_elem(shadow_blacklist_setting, i));
}
}
// Treat it as a single pattern if it's a string
else if (CONFIG_TYPE_STRING ==
config_setting_type(shadow_blacklist_setting)) {
condlst_add(&shadow_blacklist,
config_setting_get_string(shadow_blacklist_setting));
}
}
}
config_destroy(&cfg);
}
#endif
/**
* Process arguments and configuration files.
*/
static void
get_cfg(int argc, char *const *argv) {
const static char *shortopts = "D:I:O:d:r:o:m:l:t:i:e:scnfFCaSzGb";
const static struct option longopts[] = {
{ "config", required_argument, NULL, 256 },
{ "shadow-red", required_argument, NULL, 257 },
{ "shadow-green", required_argument, NULL, 258 },
{ "shadow-blue", required_argument, NULL, 259 },
{ "inactive-opacity-override", no_argument, NULL, 260 },
{ "inactive-dim", required_argument, NULL, 261 },
{ "mark-wmwin-focused", no_argument, NULL, 262 },
{ "shadow-exclude", required_argument, NULL, 263 },
// Must terminate with a NULL entry
{ NULL, 0, NULL, 0 },
};
int o, longopt_idx, i;
char *config_file = NULL;
for (i = 0; i < NUM_WINTYPES; ++i) {
win_type_fade[i] = False;
win_type_shadow[i] = False;
win_type_opacity[i] = 1.0;
}
// Pre-parse the commandline arguments to check for --config and invalid
// switches
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(config_file);
#endif
// Parse commandline arguments. Range checking will be done later.
optind = 1;
while (-1 !=
(o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) {
switch (o) {
// Short options
case 'd':
display = optarg;
break;
case 'D':
fade_delta = atoi(optarg);
break;
case 'I':
fade_in_step = normalize_d(atof(optarg)) * OPAQUE;
break;
case 'O':
fade_out_step = normalize_d(atof(optarg)) * OPAQUE;
break;
case 'c':
shadow_enable = True;
break;
case 'C':
no_dock_shadow = True;
break;
case 'm':
menu_opacity = atof(optarg);
break;
case 'f':
fading_enable = True;
break;
case 'F':
fade_trans = True;
break;
case 'S':
synchronize = True;
break;
case 'r':
shadow_radius = atoi(optarg);
break;
case 'o':
shadow_opacity = atof(optarg);
break;
case 'l':
shadow_offset_x = atoi(optarg);
break;
case 't':
shadow_offset_y = atoi(optarg);
break;
case 'i':
inactive_opacity = (normalize_d(atof(optarg)) * OPAQUE);
break;
case 'e':
frame_opacity = atof(optarg);
break;
case 'z':
clear_shadow = True;
break;
case 'n':
case 'a':
case 's':
fprintf(stderr, "Warning: "
"-n, -a, and -s have been removed.\n");
break;
case 'G':
no_dnd_shadow = True;
break;
case 'b':
fork_after_register = True;
break;
// Long options
case 256:
// --config
break;
case 257:
// --shadow-red
shadow_red = atof(optarg);
break;
case 258:
// --shadow-green
shadow_green = atof(optarg);
break;
case 259:
// --shadow-blue
shadow_blue = atof(optarg);
break;
case 260:
// --inactive-opacity-override
inactive_opacity_override = True;
break;
case 261:
// --inactive-dim
inactive_dim = atof(optarg);
break;
case 262:
// --mark-wmwin-focused
mark_wmwin_focused = True;
break;
case 263:
// --shadow-exclude
condlst_add(&shadow_blacklist, optarg);
break;
default:
usage();
break;
}
}
// Range checking and option assignments
fade_delta = max_i(fade_delta, 1);
shadow_radius = max_i(shadow_radius, 1);
shadow_red = normalize_d(shadow_red);
shadow_green = normalize_d(shadow_green);
shadow_blue = normalize_d(shadow_blue);
inactive_dim = normalize_d(inactive_dim);
frame_opacity = normalize_d(frame_opacity);
shadow_opacity = normalize_d(shadow_opacity);
menu_opacity = normalize_d(menu_opacity);
if (OPAQUE == inactive_opacity) {
inactive_opacity = 0;
}
if (shadow_enable) {
for (i = 0; i < NUM_WINTYPES; ++i) {
win_type_shadow[i] = True;
}
win_type_shadow[WINTYPE_DESKTOP] = False;
if (no_dock_shadow)
win_type_shadow[WINTYPE_DOCK] = False;
if (no_dnd_shadow)
win_type_shadow[WINTYPE_DND] = False;
}
if (fading_enable) {
for (i = 0; i < NUM_WINTYPES; ++i) {
win_type_fade[i] = True;
}
}
if (1.0 != menu_opacity) {
win_type_opacity[WINTYPE_DROPDOWN_MENU] = menu_opacity;
win_type_opacity[WINTYPE_POPUP_MENU] = menu_opacity;
}
// Other variables determined by options
// Determine whether we need to track focus changes
if (inactive_opacity || inactive_dim) {
track_focus = True;
}
// Determine whether we need to track window name and class
if (shadow_blacklist || fade_blacklist)
track_wdata = True;
}
static void
get_atoms(void) {
extents_atom = XInternAtom(dpy, "_NET_FRAME_EXTENTS", False);
@ -3050,18 +3462,6 @@ get_atoms(void) {
int
main(int argc, char **argv) {
const static struct option longopt[] = {
{ "shadow-red", required_argument, NULL, 0 },
{ "shadow-green", required_argument, NULL, 0 },
{ "shadow-blue", required_argument, NULL, 0 },
{ "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 },
};
XEvent ev;
Window root_return, parent_return;
Window *children;
@ -3070,15 +3470,6 @@ main(int argc, char **argv) {
XRenderPictureAttributes pa;
struct pollfd ufd;
int composite_major, composite_minor;
char *display = 0;
int o;
int longopt_idx;
Bool no_dock_shadow = False;
Bool no_dnd_shadow = False;
Bool fork_after_register = False;
double shadow_red = 0.0;
double shadow_green = 0.0;
double shadow_blue = 0.0;
win *t;
gettimeofday(&time_start, NULL);
@ -3087,140 +3478,7 @@ main(int argc, char **argv) {
// correctly
setlocale (LC_ALL, "");
for (i = 0; i < NUM_WINTYPES; ++i) {
win_type_fade[i] = False;
win_type_shadow[i] = False;
win_type_opacity[i] = 1.0;
}
while ((o = getopt_long(argc, argv,
"D:I:O:d:r:o:m:l:t:i:e:scnfFCaSzGb",
longopt, &longopt_idx)) != -1) {
switch (o) {
// Long options
case 0:
switch (longopt_idx) {
case 0:
shadow_red = normalize_d(atof(optarg));
break;
case 1:
shadow_green = normalize_d(atof(optarg));
break;
case 2:
shadow_blue = normalize_d(atof(optarg));
break;
case 3:
inactive_opacity_override = True;
break;
case 4:
inactive_dim = normalize_d(atof(optarg));
break;
case 5:
mark_wmwin_focused = True;
break;
case 6:
condlst_add(&shadow_blacklist, optarg);
break;
}
break;
// Short options
case 'd':
display = optarg;
break;
case 'D':
fade_delta = atoi(optarg);
if (fade_delta < 1) {
fade_delta = 10;
}
break;
case 'I':
fade_in_step = normalize_d(atof(optarg)) * OPAQUE;
break;
case 'O':
fade_out_step = normalize_d(atof(optarg)) * OPAQUE;
break;
case 'c':
for (i = 0; i < NUM_WINTYPES; ++i) {
win_type_shadow[i] = True;
}
win_type_shadow[WINTYPE_DESKTOP] = False;
break;
case 'C':
no_dock_shadow = True;
break;
case 'm':
win_type_opacity[WINTYPE_DROPDOWN_MENU] = atof(optarg);
win_type_opacity[WINTYPE_POPUP_MENU] = atof(optarg);
break;
case 'f':
for (i = 0; i < NUM_WINTYPES; ++i) {
win_type_fade[i] = True;
}
break;
case 'F':
fade_trans = True;
break;
case 'S':
synchronize = True;
break;
case 'r':
shadow_radius = atoi(optarg);
break;
case 'o':
shadow_opacity = normalize_d(atof(optarg));
break;
case 'l':
shadow_offset_x = atoi(optarg);
break;
case 't':
shadow_offset_y = atoi(optarg);
break;
case 'i':
inactive_opacity = (normalize_d(atof(optarg)) * OPAQUE);
if (OPAQUE == inactive_opacity) {
inactive_opacity = 0;
}
break;
case 'e':
frame_opacity = normalize_d(atof(optarg));
break;
case 'z':
clear_shadow = True;
break;
case 'n':
case 'a':
case 's':
fprintf(stderr, "Warning: "
"-n, -a, and -s have been removed.\n");
break;
case 'G':
no_dnd_shadow = True;
break;
case 'b':
fork_after_register = True;
break;
default:
usage();
break;
}
}
if (no_dock_shadow) {
win_type_shadow[WINTYPE_DOCK] = False;
}
if (no_dnd_shadow) {
win_type_shadow[WINTYPE_DND] = False;
}
// Determine whether we need to track focus changes
if (inactive_opacity || inactive_dim) {
track_focus = True;
}
// Determine whether we need to track window name and class
if (shadow_blacklist || fade_blacklist)
track_wdata = True;
get_cfg(argc, argv);
fade_time = get_time_in_milliseconds();

View File

@ -24,6 +24,8 @@
// Whether to enable JIT support of libpcre. This may cause problems on PaX
// kernels.
#define CONFIG_REGEX_PCRE_JIT 1
// Whether to enable parsing of configuration files using libconfig
#define CONFIG_LIBCONFIG 1
// === Includes ===
@ -44,10 +46,16 @@
#include <locale.h>
#include <fnmatch.h>
#ifdef CONFIG_REGEX_PCRE
#include <pcre.h>
#endif
#ifdef CONFIG_LIBCONFIG
#include <libgen.h>
#include <libconfig.h>
#endif
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
@ -277,6 +285,34 @@ mstrcpy(const char *src) {
return str;
}
/**
* Allocate the space and join two strings.
*/
static inline char *
mstrjoin(const char *src1, const char *src2) {
char *str = malloc(sizeof(char) * (strlen(src1) + strlen(src2) + 1));
strcpy(str, src1);
strcat(str, src2);
return str;
}
/**
* Allocate the space and join two strings;
*/
static inline char *
mstrjoin3(const char *src1, const char *src2, const char *src3) {
char *str = malloc(sizeof(char) * (strlen(src1) + strlen(src2)
+ strlen(src3) + 1));
strcpy(str, src1);
strcat(str, src2);
strcat(str, src3);
return str;
}
/**
* Normalize an int value to a specific range.
*
@ -285,13 +321,29 @@ mstrcpy(const char *src) {
* @param max maximum value
* @return normalized value
*/
static inline double
static inline int
normalize_i_range(int i, int min, int max) {
if (i > max) return max;
if (i < min) return min;
return i;
}
/**
* Select the larger integer of two.
*/
static inline int
max_i(int a, int b) {
return (a > b ? a : b);
}
/**
* Select the smaller integer of two.
*/
static inline int
min_i(int a, int b) {
return (a > b ? b : a);
}
/**
* Normalize a double value to a specific range.
*
@ -530,6 +582,12 @@ static Picture
solid_picture(Display *dpy, Bool argb, double a,
double r, double g, double b);
static inline bool is_normal_win(const win *w) {
return (WINTYPE_NORMAL == w->window_type
|| WINTYPE_UTILITY == w->window_type
|| WINTYPE_UNKNOWN == w->window_type);
}
static bool
win_match_once(win *w, const wincond *cond);
@ -789,5 +847,16 @@ ev_handle(XEvent *ev);
static void
fork_after(void);
#ifdef CONFIG_LIBCONFIG
static FILE *
open_config_file(char *cpath, char **path);
static void
parse_config(char *cpath);
#endif
static void
get_cfg(int argc, char *const *argv);
static void
get_atoms(void);