You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8483 lines
230 KiB
8483 lines
230 KiB
/*
|
|
* Compton - a compositor for X11
|
|
*
|
|
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
|
|
*
|
|
* Copyright (c) 2011-2013, Christopher Jeffrey
|
|
* Copyright (c) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net>
|
|
* See LICENSE for more information.
|
|
*
|
|
*/
|
|
|
|
#include "compton.h"
|
|
#include <ctype.h>
|
|
|
|
// === Global constants ===
|
|
|
|
/// Name strings for window types.
|
|
const char * const WINTYPES[NUM_WINTYPES] = {
|
|
"unknown",
|
|
"desktop",
|
|
"dock",
|
|
"toolbar",
|
|
"menu",
|
|
"utility",
|
|
"splash",
|
|
"dialog",
|
|
"normal",
|
|
"dropdown_menu",
|
|
"popup_menu",
|
|
"tooltip",
|
|
"notify",
|
|
"combo",
|
|
"dnd",
|
|
};
|
|
|
|
/// Names of VSync modes.
|
|
const char * const VSYNC_STRS[NUM_VSYNC + 1] = {
|
|
"none", // VSYNC_NONE
|
|
"drm", // VSYNC_DRM
|
|
"opengl", // VSYNC_OPENGL
|
|
"opengl-oml", // VSYNC_OPENGL_OML
|
|
"opengl-swc", // VSYNC_OPENGL_SWC
|
|
"opengl-mswc", // VSYNC_OPENGL_MSWC
|
|
NULL
|
|
};
|
|
|
|
/// Names of backends.
|
|
const char * const BACKEND_STRS[NUM_BKEND + 1] = {
|
|
"xrender", // BKEND_XRENDER
|
|
"glx", // BKEND_GLX
|
|
"xr_glx_hybrid",// BKEND_XR_GLX_HYBRID
|
|
NULL
|
|
};
|
|
|
|
/// Function pointers to init VSync modes.
|
|
static bool (* const (VSYNC_FUNCS_INIT[NUM_VSYNC]))(session_t *ps) = {
|
|
[VSYNC_DRM ] = vsync_drm_init,
|
|
[VSYNC_OPENGL ] = vsync_opengl_init,
|
|
[VSYNC_OPENGL_OML ] = vsync_opengl_oml_init,
|
|
[VSYNC_OPENGL_SWC ] = vsync_opengl_swc_init,
|
|
[VSYNC_OPENGL_MSWC ] = vsync_opengl_mswc_init,
|
|
};
|
|
|
|
/// Function pointers to wait for VSync.
|
|
static int (* const (VSYNC_FUNCS_WAIT[NUM_VSYNC]))(session_t *ps) = {
|
|
#ifdef CONFIG_VSYNC_DRM
|
|
[VSYNC_DRM ] = vsync_drm_wait,
|
|
#endif
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
[VSYNC_OPENGL ] = vsync_opengl_wait,
|
|
[VSYNC_OPENGL_OML ] = vsync_opengl_oml_wait,
|
|
#endif
|
|
};
|
|
|
|
/// Function pointers to deinitialize VSync.
|
|
static void (* const (VSYNC_FUNCS_DEINIT[NUM_VSYNC]))(session_t *ps) = {
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
[VSYNC_OPENGL_SWC ] = vsync_opengl_swc_deinit,
|
|
[VSYNC_OPENGL_MSWC ] = vsync_opengl_mswc_deinit,
|
|
#endif
|
|
};
|
|
|
|
/// Names of root window properties that could point to a pixmap of
|
|
/// background.
|
|
const static char *background_props_str[] = {
|
|
"_XROOTPMAP_ID",
|
|
"_XSETROOT_ID",
|
|
0,
|
|
};
|
|
|
|
// === Global variables ===
|
|
|
|
/// Pointer to current session, as a global variable. Only used by
|
|
/// <code>error()</code> and <code>reset_enable()</code>, which could not
|
|
/// have a pointer to current session passed in.
|
|
session_t *ps_g = NULL;
|
|
|
|
// === Execution control ===
|
|
|
|
struct sigaction usr_action;
|
|
sigset_t block_mask;
|
|
|
|
int my_exit_code = 3;
|
|
|
|
void write_pid_file(pid_t pid)
|
|
{
|
|
#ifdef WRITE_PID_FILE
|
|
const char *filename;
|
|
const char *pidfile = "compton-tde.pid";
|
|
char uidstr[sizeof(uid_t)*8+1];
|
|
sprintf(uidstr, "%d", getuid());
|
|
int n = strlen(P_tmpdir)+strlen(uidstr)+strlen(pidfile)+3;
|
|
filename = (char*)malloc(n*sizeof(char)+1);
|
|
memset(filename,0,n);
|
|
strcat(filename, P_tmpdir);
|
|
strcat(filename, "/.");
|
|
strcat(filename, uidstr);
|
|
strcat(filename, "-");
|
|
strcat(filename, pidfile);
|
|
|
|
/* now that we did all that by way of introduction...write the file! */
|
|
FILE *pFile;
|
|
char buffer[255];
|
|
sprintf(buffer, "%d", pid);
|
|
pFile = fopen(filename, "w");
|
|
if (pFile) {
|
|
fwrite(buffer,1,strlen(buffer), pFile);
|
|
fclose(pFile);
|
|
}
|
|
|
|
free(filename);
|
|
filename = NULL;
|
|
#endif
|
|
}
|
|
|
|
void delete_pid_file()
|
|
{
|
|
#ifdef WRITE_PID_FILE
|
|
const char *filename;
|
|
const char *pidfile = "compton-tde.pid";
|
|
char uidstr[sizeof(uid_t)*8+1];
|
|
sprintf(uidstr, "%d", getuid());
|
|
int n = strlen(P_tmpdir)+strlen(uidstr)+strlen(pidfile)+3;
|
|
filename = (char*)malloc(n*sizeof(char)+1);
|
|
memset(filename,0,n);
|
|
strcat(filename, P_tmpdir);
|
|
strcat(filename, "/.");
|
|
strcat(filename, uidstr);
|
|
strcat(filename, "-");
|
|
strcat(filename, pidfile);
|
|
|
|
/* now that we did all that by way of introduction...delete the file! */
|
|
unlink(filename);
|
|
|
|
free(filename);
|
|
filename = NULL;
|
|
#endif
|
|
|
|
#if WORK_AROUND_FGLRX
|
|
if ((my_exit_code == 3) && (restartOnSigterm)) {
|
|
printf("compton-tde lost connection to X server, restarting...\n"); fflush(stdout);
|
|
sleep(1);
|
|
char me[2048];
|
|
int chars = readlink("/proc/self/exe", me, sizeof(me));
|
|
me[chars] = 0;
|
|
me[2047] = 0;
|
|
execl(me, basename(me), (char*)NULL);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void handle_siguser (int sig)
|
|
{
|
|
int uidnum;
|
|
if (sig == SIGTERM) {
|
|
my_exit_code=0;
|
|
delete_pid_file();
|
|
exit(0);
|
|
}
|
|
if (sig == SIGUSR1) {
|
|
char newuid[1024];
|
|
#ifndef NDEBUG
|
|
printf("Enter the new user ID:\n"); fflush(stdout);
|
|
#endif
|
|
char *eof;
|
|
newuid[0] = '\0';
|
|
newuid[sizeof(newuid)-1] = '\0';
|
|
eof = fgets(newuid, sizeof(newuid), stdin);
|
|
uidnum = atoi(newuid);
|
|
#ifndef NDEBUG
|
|
printf("Setting compton-tde process uid to %d...\n", uidnum); fflush(stdout);
|
|
#endif
|
|
|
|
my_exit_code=4;
|
|
delete_pid_file();
|
|
my_exit_code=3;
|
|
setuid(uidnum);
|
|
write_pid_file(getpid());
|
|
}
|
|
else {
|
|
uidnum = getuid();
|
|
}
|
|
if ((sig == SIGUSR1) || (sig == SIGUSR2)) {
|
|
/* force redetection of the configuration file location */
|
|
if (ps_g->o.config_file) {
|
|
free(ps_g->o.config_file);
|
|
ps_g->o.config_file = NULL;
|
|
}
|
|
|
|
get_cfg(ps_g, 0, 0, false); /* reload the configuration file */
|
|
|
|
/* set background/shadow picture using the new settings */
|
|
ps_g->cshadow_picture = solid_picture(ps_g, true, 1, ps_g->o.shadow_red, ps_g->o.shadow_green, ps_g->o.shadow_blue);
|
|
|
|
/* regenerate shadows using the new settings */
|
|
ps_g->gaussian_map = make_gaussian_map(ps_g->o.shadow_radius);
|
|
presum_gaussian(ps_g, ps_g->gaussian_map);
|
|
|
|
init_alpha_picts(ps_g);
|
|
init_filters(ps_g);
|
|
}
|
|
}
|
|
|
|
// === Fading ===
|
|
|
|
/**
|
|
* Get the time left before next fading point.
|
|
*
|
|
* In milliseconds.
|
|
*/
|
|
static int
|
|
fade_timeout(session_t *ps) {
|
|
int diff = ps->o.fade_delta - get_time_ms() + ps->fade_time;
|
|
|
|
diff = normalize_i_range(diff, 0, ps->o.fade_delta * 2);
|
|
|
|
return diff;
|
|
}
|
|
|
|
/**
|
|
* Run fading on a window.
|
|
*
|
|
* @param steps steps of fading
|
|
*/
|
|
static void
|
|
run_fade(session_t *ps, win *w, unsigned steps) {
|
|
// If we have reached target opacity, return
|
|
if (w->opacity == w->opacity_tgt) {
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG_FADE
|
|
if (w->fade) {
|
|
printf_dbgf("(%#010lx): run_fade opacity: %u target: %u\n", w->id, w->opacity, w->opacity_tgt);
|
|
}
|
|
#endif
|
|
|
|
if (!w->fade) {
|
|
w->opacity = w->opacity_tgt;
|
|
}
|
|
else if (steps) {
|
|
// Use double below because opacity_t will probably overflow during
|
|
// calculations
|
|
if (w->opacity < w->opacity_tgt)
|
|
w->opacity = normalize_d_range(
|
|
(double) w->opacity + (double) ps->o.fade_in_step * steps,
|
|
0.0, w->opacity_tgt);
|
|
else
|
|
w->opacity = normalize_d_range(
|
|
(double) w->opacity - (double) ps->o.fade_out_step * steps,
|
|
w->opacity_tgt, OPAQUE);
|
|
}
|
|
|
|
if (w->opacity != w->opacity_tgt) {
|
|
ps->idling = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set fade callback of a window, and possibly execute the previous
|
|
* callback.
|
|
*
|
|
* @param exec_callback whether the previous callback is to be executed
|
|
*/
|
|
static void
|
|
set_fade_callback(session_t *ps, win *w,
|
|
void (*callback) (session_t *ps, win *w), bool exec_callback) {
|
|
void (*old_callback) (session_t *ps, 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) {
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(%#010lx): exec callback\n", w->id);
|
|
#endif
|
|
old_callback(ps, w);
|
|
// Although currently no callback function affects window state on
|
|
// next paint, it could, in the future
|
|
ps->idling = false;
|
|
}
|
|
}
|
|
|
|
// === Shadows ===
|
|
|
|
static double __attribute__((const))
|
|
gaussian(double r, double x, double y) {
|
|
return ((1 / (sqrt(2 * M_PI * r))) *
|
|
exp((- (x * x + y * y)) / (2 * r * r)));
|
|
}
|
|
|
|
static conv *
|
|
make_gaussian_map(double r) {
|
|
conv *c;
|
|
int size = ((int) ceil((r * 3)) + 1) & ~1;
|
|
int center = size / 2;
|
|
int x, y;
|
|
double t;
|
|
double g;
|
|
|
|
c = malloc(sizeof(conv) + size * size * sizeof(double));
|
|
c->size = size;
|
|
c->data = (double *) (c + 1);
|
|
t = 0.0;
|
|
|
|
for (y = 0; y < size; y++) {
|
|
for (x = 0; x < size; x++) {
|
|
g = gaussian(r, x - center, y - center);
|
|
t += g;
|
|
c->data[y * size + x] = g;
|
|
}
|
|
}
|
|
|
|
for (y = 0; y < size; y++) {
|
|
for (x = 0; x < size; x++) {
|
|
c->data[y * size + x] /= t;
|
|
}
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
/*
|
|
* A picture will help
|
|
*
|
|
* -center 0 width width+center
|
|
* -center +-----+-------------------+-----+
|
|
* | | | |
|
|
* | | | |
|
|
* 0 +-----+-------------------+-----+
|
|
* | | | |
|
|
* | | | |
|
|
* | | | |
|
|
* height +-----+-------------------+-----+
|
|
* | | | |
|
|
* height+ | | | |
|
|
* center +-----+-------------------+-----+
|
|
*/
|
|
|
|
static unsigned char
|
|
sum_gaussian(conv *map, double opacity,
|
|
int x, int y, int width, int height) {
|
|
int fx, fy;
|
|
double *g_data;
|
|
double *g_line = map->data;
|
|
int g_size = map->size;
|
|
int center = g_size / 2;
|
|
int fx_start, fx_end;
|
|
int fy_start, fy_end;
|
|
double v;
|
|
|
|
/*
|
|
* Compute set of filter values which are "in range",
|
|
* that's the set with:
|
|
* 0 <= x + (fx-center) && x + (fx-center) < width &&
|
|
* 0 <= y + (fy-center) && y + (fy-center) < height
|
|
*
|
|
* 0 <= x + (fx - center) x + fx - center < width
|
|
* center - x <= fx fx < width + center - x
|
|
*/
|
|
|
|
fx_start = center - x;
|
|
if (fx_start < 0) fx_start = 0;
|
|
fx_end = width + center - x;
|
|
if (fx_end > g_size) fx_end = g_size;
|
|
|
|
fy_start = center - y;
|
|
if (fy_start < 0) fy_start = 0;
|
|
fy_end = height + center - y;
|
|
if (fy_end > g_size) fy_end = g_size;
|
|
|
|
g_line = g_line + fy_start * g_size + fx_start;
|
|
|
|
v = 0;
|
|
|
|
for (fy = fy_start; fy < fy_end; fy++) {
|
|
g_data = g_line;
|
|
g_line += g_size;
|
|
|
|
for (fx = fx_start; fx < fx_end; fx++) {
|
|
v += *g_data++;
|
|
}
|
|
}
|
|
|
|
if (v > 1) v = 1;
|
|
|
|
return ((unsigned char) (v * opacity * 255.0));
|
|
}
|
|
|
|
/* precompute shadow corners and sides
|
|
to save time for large windows */
|
|
|
|
static void
|
|
presum_gaussian(session_t *ps, conv *map) {
|
|
int center = map->size / 2;
|
|
int opacity, x, y;
|
|
|
|
ps->cgsize = map->size;
|
|
|
|
if (ps->shadow_corner)
|
|
free(ps->shadow_corner);
|
|
if (ps->shadow_top)
|
|
free(ps->shadow_top);
|
|
|
|
ps->shadow_corner = malloc((ps->cgsize + 1) * (ps->cgsize + 1) * 26);
|
|
ps->shadow_top = malloc((ps->cgsize + 1) * 26);
|
|
|
|
for (x = 0; x <= ps->cgsize; x++) {
|
|
ps->shadow_top[25 * (ps->cgsize + 1) + x] =
|
|
sum_gaussian(map, 1, x - center, center, ps->cgsize * 2, ps->cgsize * 2);
|
|
|
|
for (opacity = 0; opacity < 25; opacity++) {
|
|
ps->shadow_top[opacity * (ps->cgsize + 1) + x] =
|
|
ps->shadow_top[25 * (ps->cgsize + 1) + x] * opacity / 25;
|
|
}
|
|
|
|
for (y = 0; y <= x; y++) {
|
|
ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + y * (ps->cgsize + 1) + x]
|
|
= sum_gaussian(map, 1, x - center, y - center, ps->cgsize * 2, ps->cgsize * 2);
|
|
ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + x * (ps->cgsize + 1) + y]
|
|
= ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + y * (ps->cgsize + 1) + x];
|
|
|
|
for (opacity = 0; opacity < 25; opacity++) {
|
|
ps->shadow_corner[opacity * (ps->cgsize + 1) * (ps->cgsize + 1)
|
|
+ y * (ps->cgsize + 1) + x]
|
|
= ps->shadow_corner[opacity * (ps->cgsize + 1) * (ps->cgsize + 1)
|
|
+ x * (ps->cgsize + 1) + y]
|
|
= ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1)
|
|
+ y * (ps->cgsize + 1) + x] * opacity / 25;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static XImage *
|
|
make_shadow(session_t *ps, double opacity,
|
|
int width, int height) {
|
|
XImage *ximage;
|
|
unsigned char *data;
|
|
int ylimit, xlimit;
|
|
int swidth = width + ps->cgsize;
|
|
int sheight = height + ps->cgsize;
|
|
int center = ps->cgsize / 2;
|
|
int x, y;
|
|
unsigned char d;
|
|
int x_diff;
|
|
int opacity_int = (int)(opacity * 25);
|
|
|
|
data = malloc(swidth * sheight * sizeof(unsigned char));
|
|
if (!data) return 0;
|
|
|
|
ximage = XCreateImage(ps->dpy, ps->vis, 8,
|
|
ZPixmap, 0, (char *) data, swidth, sheight, 8, swidth * sizeof(char));
|
|
|
|
if (!ximage) {
|
|
free(data);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Build the gaussian in sections
|
|
*/
|
|
|
|
/*
|
|
* center (fill the complete data array)
|
|
*/
|
|
|
|
// If clear_shadow is enabled and the border & corner shadow (which
|
|
// later will be filled) could entirely cover the area of the shadow
|
|
// that will be displayed, do not bother filling other pixels. If it
|
|
// can't, we must fill the other pixels here.
|
|
/* if (!(clear_shadow && ps->o.shadow_offset_x <= 0 && ps->o.shadow_offset_x >= -ps->cgsize
|
|
&& ps->o.shadow_offset_y <= 0 && ps->o.shadow_offset_y >= -ps->cgsize)) { */
|
|
if (ps->cgsize > 0) {
|
|
d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + ps->cgsize];
|
|
} else {
|
|
d = sum_gaussian(ps->gaussian_map,
|
|
opacity, center, center, width, height);
|
|
}
|
|
memset(data, d, sheight * swidth);
|
|
// }
|
|
|
|
/*
|
|
* corners
|
|
*/
|
|
|
|
ylimit = ps->cgsize;
|
|
if (ylimit > sheight / 2) ylimit = (sheight + 1) / 2;
|
|
|
|
xlimit = ps->cgsize;
|
|
if (xlimit > swidth / 2) xlimit = (swidth + 1) / 2;
|
|
|
|
for (y = 0; y < ylimit; y++) {
|
|
for (x = 0; x < xlimit; x++) {
|
|
if (xlimit == ps->cgsize && ylimit == ps->cgsize) {
|
|
d = ps->shadow_corner[opacity_int * (ps->cgsize + 1) * (ps->cgsize + 1)
|
|
+ y * (ps->cgsize + 1) + x];
|
|
} else {
|
|
d = sum_gaussian(ps->gaussian_map,
|
|
opacity, x - center, y - center, width, height);
|
|
}
|
|
data[y * swidth + x] = d;
|
|
data[(sheight - y - 1) * swidth + x] = d;
|
|
data[(sheight - y - 1) * swidth + (swidth - x - 1)] = d;
|
|
data[y * swidth + (swidth - x - 1)] = d;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* top/bottom
|
|
*/
|
|
|
|
x_diff = swidth - (ps->cgsize * 2);
|
|
if (x_diff > 0 && ylimit > 0) {
|
|
for (y = 0; y < ylimit; y++) {
|
|
if (ylimit == ps->cgsize) {
|
|
d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + y];
|
|
} else {
|
|
d = sum_gaussian(ps->gaussian_map,
|
|
opacity, center, y - center, width, height);
|
|
}
|
|
memset(&data[y * swidth + ps->cgsize], d, x_diff);
|
|
memset(&data[(sheight - y - 1) * swidth + ps->cgsize], d, x_diff);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* sides
|
|
*/
|
|
|
|
for (x = 0; x < xlimit; x++) {
|
|
if (xlimit == ps->cgsize) {
|
|
d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + x];
|
|
} else {
|
|
d = sum_gaussian(ps->gaussian_map,
|
|
opacity, x - center, center, width, height);
|
|
}
|
|
for (y = ps->cgsize; y < sheight - ps->cgsize; y++) {
|
|
data[y * swidth + x] = d;
|
|
data[y * swidth + (swidth - x - 1)] = d;
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (clear_shadow) {
|
|
// Clear the region in the shadow that the window would cover based
|
|
// on shadow_offset_{x,y} user provides
|
|
int xstart = normalize_i_range(- (int) ps->o.shadow_offset_x, 0, swidth);
|
|
int xrange = normalize_i_range(width - (int) ps->o.shadow_offset_x,
|
|
0, swidth) - xstart;
|
|
int ystart = normalize_i_range(- (int) ps->o.shadow_offset_y, 0, sheight);
|
|
int yend = normalize_i_range(height - (int) ps->o.shadow_offset_y,
|
|
0, sheight);
|
|
int y;
|
|
|
|
for (y = ystart; y < yend; y++) {
|
|
memset(&data[y * swidth + xstart], 0, xrange);
|
|
}
|
|
}
|
|
*/
|
|
|
|
return ximage;
|
|
}
|
|
|
|
/**
|
|
* Generate shadow <code>Picture</code> for a window.
|
|
*/
|
|
static bool
|
|
win_build_shadow(session_t *ps, win *w, double opacity) {
|
|
const int width = w->widthb;
|
|
const int height = w->heightb;
|
|
|
|
XImage *shadow_image = NULL;
|
|
Pixmap shadow_pixmap = None, shadow_pixmap_argb = None;
|
|
Picture shadow_picture = None, shadow_picture_argb = None;
|
|
GC gc = None;
|
|
|
|
shadow_image = make_shadow(ps, opacity, width, height);
|
|
if (!shadow_image)
|
|
return None;
|
|
|
|
shadow_pixmap = XCreatePixmap(ps->dpy, ps->root,
|
|
shadow_image->width, shadow_image->height, 8);
|
|
shadow_pixmap_argb = XCreatePixmap(ps->dpy, ps->root,
|
|
shadow_image->width, shadow_image->height, 32);
|
|
|
|
if (!shadow_pixmap || !shadow_pixmap_argb)
|
|
goto shadow_picture_err;
|
|
|
|
shadow_picture = XRenderCreatePicture(ps->dpy, shadow_pixmap,
|
|
XRenderFindStandardFormat(ps->dpy, PictStandardA8), 0, 0);
|
|
shadow_picture_argb = XRenderCreatePicture(ps->dpy, shadow_pixmap_argb,
|
|
XRenderFindStandardFormat(ps->dpy, PictStandardARGB32), 0, 0);
|
|
if (!shadow_picture || !shadow_picture_argb)
|
|
goto shadow_picture_err;
|
|
|
|
gc = XCreateGC(ps->dpy, shadow_pixmap, 0, 0);
|
|
if (!gc)
|
|
goto shadow_picture_err;
|
|
|
|
XPutImage(ps->dpy, shadow_pixmap, gc, shadow_image, 0, 0, 0, 0,
|
|
shadow_image->width, shadow_image->height);
|
|
XRenderComposite(ps->dpy, PictOpSrc, ps->cshadow_picture, shadow_picture,
|
|
shadow_picture_argb, 0, 0, 0, 0, 0, 0,
|
|
shadow_image->width, shadow_image->height);
|
|
|
|
assert(!w->shadow_paint.pixmap);
|
|
w->shadow_paint.pixmap = shadow_pixmap_argb;
|
|
assert(!w->shadow_paint.pict);
|
|
w->shadow_paint.pict = shadow_picture_argb;
|
|
|
|
// Sync it once and only once
|
|
xr_sync(ps, w->shadow_paint.pixmap, NULL);
|
|
|
|
XFreeGC(ps->dpy, gc);
|
|
XDestroyImage(shadow_image);
|
|
XFreePixmap(ps->dpy, shadow_pixmap);
|
|
XRenderFreePicture(ps->dpy, shadow_picture);
|
|
|
|
return true;
|
|
|
|
shadow_picture_err:
|
|
if (shadow_image)
|
|
XDestroyImage(shadow_image);
|
|
if (shadow_pixmap)
|
|
XFreePixmap(ps->dpy, shadow_pixmap);
|
|
if (shadow_pixmap_argb)
|
|
XFreePixmap(ps->dpy, shadow_pixmap_argb);
|
|
if (shadow_picture)
|
|
XRenderFreePicture(ps->dpy, shadow_picture);
|
|
if (shadow_picture_argb)
|
|
XRenderFreePicture(ps->dpy, shadow_picture_argb);
|
|
if (gc)
|
|
XFreeGC(ps->dpy, gc);
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Generate a 1x1 <code>Picture</code> of a particular color.
|
|
*/
|
|
static Picture
|
|
solid_picture(session_t *ps, bool argb, double a,
|
|
double r, double g, double b) {
|
|
Pixmap pixmap;
|
|
Picture picture;
|
|
XRenderPictureAttributes pa;
|
|
XRenderColor c;
|
|
|
|
pixmap = XCreatePixmap(ps->dpy, ps->root, 1, 1, argb ? 32 : 8);
|
|
|
|
if (!pixmap) return None;
|
|
|
|
pa.repeat = True;
|
|
picture = XRenderCreatePicture(ps->dpy, pixmap,
|
|
XRenderFindStandardFormat(ps->dpy, argb
|
|
? PictStandardARGB32 : PictStandardA8),
|
|
CPRepeat,
|
|
&pa);
|
|
|
|
if (!picture) {
|
|
XFreePixmap(ps->dpy, pixmap);
|
|
return None;
|
|
}
|
|
|
|
c.alpha = a * 0xffff;
|
|
c.red = r * 0xffff;
|
|
c.green = g * 0xffff;
|
|
c.blue = b * 0xffff;
|
|
|
|
XRenderFillRectangle(ps->dpy, PictOpSrc, picture, &c, 0, 0, 1, 1);
|
|
XFreePixmap(ps->dpy, pixmap);
|
|
|
|
return picture;
|
|
}
|
|
|
|
// === Error handling ===
|
|
|
|
static void
|
|
discard_ignore(session_t *ps, unsigned long sequence) {
|
|
while (ps->ignore_head) {
|
|
if ((long) (sequence - ps->ignore_head->sequence) > 0) {
|
|
ignore_t *next = ps->ignore_head->next;
|
|
free(ps->ignore_head);
|
|
ps->ignore_head = next;
|
|
if (!ps->ignore_head) {
|
|
ps->ignore_tail = &ps->ignore_head;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_ignore(session_t *ps, unsigned long sequence) {
|
|
if (ps->o.show_all_xerrors)
|
|
return;
|
|
|
|
ignore_t *i = malloc(sizeof(ignore_t));
|
|
if (!i) return;
|
|
|
|
i->sequence = sequence;
|
|
i->next = 0;
|
|
*ps->ignore_tail = i;
|
|
ps->ignore_tail = &i->next;
|
|
}
|
|
|
|
static int
|
|
should_ignore(session_t *ps, unsigned long sequence) {
|
|
discard_ignore(ps, sequence);
|
|
return ps->ignore_head && ps->ignore_head->sequence == sequence;
|
|
}
|
|
|
|
// === Windows ===
|
|
|
|
/**
|
|
* Get a specific attribute of a window.
|
|
*
|
|
* Returns a blank structure if the returned type and format does not
|
|
* match the requested type and format.
|
|
*
|
|
* @param ps current session
|
|
* @param w window
|
|
* @param atom atom of attribute to fetch
|
|
* @param length length to read
|
|
* @param rtype atom of the requested type
|
|
* @param rformat requested format
|
|
* @return a <code>winprop_t</code> structure containing the attribute
|
|
* and number of items. A blank one on failure.
|
|
*/
|
|
winprop_t
|
|
wid_get_prop_adv(const session_t *ps, Window w, Atom atom, long offset,
|
|
long length, Atom rtype, int rformat) {
|
|
Atom type = None;
|
|
int format = 0;
|
|
unsigned long nitems = 0, after = 0;
|
|
unsigned char *data = NULL;
|
|
|
|
if (Success == XGetWindowProperty(ps->dpy, w, atom, offset, length,
|
|
False, rtype, &type, &format, &nitems, &after, &data)
|
|
&& nitems && (AnyPropertyType == type || type == rtype)
|
|
&& (!rformat || format == rformat)
|
|
&& (8 == format || 16 == format || 32 == format)) {
|
|
return (winprop_t) {
|
|
.data.p8 = data,
|
|
.nitems = nitems,
|
|
.type = type,
|
|
.format = format,
|
|
};
|
|
}
|
|
|
|
cxfree(data);
|
|
|
|
return (winprop_t) {
|
|
.data.p8 = NULL,
|
|
.nitems = 0,
|
|
.type = AnyPropertyType,
|
|
.format = 0
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if a window has rounded corners.
|
|
*/
|
|
static void
|
|
win_rounded_corners(session_t *ps, win *w) {
|
|
w->rounded_corners = false;
|
|
|
|
if (!w->bounding_shaped)
|
|
return;
|
|
|
|
// Fetch its bounding region
|
|
if (!w->border_size)
|
|
w->border_size = border_size(ps, w, true);
|
|
|
|
// Quit if border_size() returns None
|
|
if (!w->border_size)
|
|
return;
|
|
|
|
// Determine the minimum width/height of a rectangle that could mark
|
|
// a window as having rounded corners
|
|
unsigned short minwidth = max_i(w->widthb * (1 - ROUNDED_PERCENT),
|
|
w->widthb - ROUNDED_PIXELS);
|
|
unsigned short minheight = max_i(w->heightb * (1 - ROUNDED_PERCENT),
|
|
w->heightb - ROUNDED_PIXELS);
|
|
|
|
// Get the rectangles in the bounding region
|
|
int nrects = 0, i;
|
|
XRectangle *rects = XFixesFetchRegion(ps->dpy, w->border_size, &nrects);
|
|
if (!rects)
|
|
return;
|
|
|
|
// Look for a rectangle large enough for this window be considered
|
|
// having rounded corners
|
|
for (i = 0; i < nrects; ++i)
|
|
if (rects[i].width >= minwidth && rects[i].height >= minheight) {
|
|
w->rounded_corners = true;
|
|
break;
|
|
}
|
|
|
|
cxfree(rects);
|
|
}
|
|
|
|
/**
|
|
* Add a pattern to a condition linked list.
|
|
*/
|
|
static bool
|
|
condlst_add(session_t *ps, c2_lptr_t **pcondlst, const char *pattern) {
|
|
if (!pattern)
|
|
return false;
|
|
|
|
#ifdef CONFIG_C2
|
|
if (!c2_parse(ps, pcondlst, pattern))
|
|
exit(1);
|
|
#else
|
|
printf_errfq(1, "(): Condition support not compiled in.");
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Determine the event mask for a window.
|
|
*/
|
|
static long
|
|
determine_evmask(session_t *ps, Window wid, win_evmode_t mode) {
|
|
long evmask = NoEventMask;
|
|
win *w = NULL;
|
|
|
|
// Check if it's a mapped frame window
|
|
if (WIN_EVMODE_FRAME == mode
|
|
|| ((w = find_win(ps, wid)) && IsViewable == w->a.map_state)) {
|
|
evmask |= PropertyChangeMask;
|
|
if (ps->o.track_focus && !ps->o.use_ewmh_active_win)
|
|
evmask |= FocusChangeMask;
|
|
}
|
|
|
|
// Check if it's a mapped client window
|
|
if (WIN_EVMODE_CLIENT == mode
|
|
|| ((w = find_toplevel(ps, wid)) && IsViewable == w->a.map_state)) {
|
|
if (ps->o.frame_opacity || ps->o.track_wdata || ps->track_atom_lst
|
|
|| ps->o.detect_client_opacity)
|
|
evmask |= PropertyChangeMask;
|
|
}
|
|
|
|
return evmask;
|
|
}
|
|
|
|
/**
|
|
* Find out the WM frame of a client window by querying X.
|
|
*
|
|
* @param ps current session
|
|
* @param wid window ID
|
|
* @return struct _win object of the found window, NULL if not found
|
|
*/
|
|
static win *
|
|
find_toplevel2(session_t *ps, Window wid) {
|
|
win *w = NULL;
|
|
|
|
// We traverse through its ancestors to find out the frame
|
|
while (wid && wid != ps->root && !(w = find_win(ps, wid))) {
|
|
Window troot;
|
|
Window parent;
|
|
Window *tchildren;
|
|
unsigned tnchildren;
|
|
|
|
// XQueryTree probably fails if you run compton when X is somehow
|
|
// initializing (like add it in .xinitrc). In this case
|
|
// just leave it alone.
|
|
if (!XQueryTree(ps->dpy, wid, &troot, &parent, &tchildren,
|
|
&tnchildren)) {
|
|
parent = 0;
|
|
break;
|
|
}
|
|
|
|
cxfree(tchildren);
|
|
|
|
wid = parent;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
/**
|
|
* Recheck currently focused window and set its <code>w->focused</code>
|
|
* to true.
|
|
*
|
|
* @param ps current session
|
|
* @return struct _win of currently focused window, NULL if not found
|
|
*/
|
|
static win *
|
|
recheck_focus(session_t *ps) {
|
|
// Use EWMH _NET_ACTIVE_WINDOW if enabled
|
|
if (ps->o.use_ewmh_active_win) {
|
|
update_ewmh_active_win(ps);
|
|
return ps->active_win;
|
|
}
|
|
|
|
// Determine the currently focused window so we can apply appropriate
|
|
// opacity on it
|
|
Window wid = 0;
|
|
int revert_to;
|
|
|
|
XGetInputFocus(ps->dpy, &wid, &revert_to);
|
|
|
|
win *w = find_win_all(ps, wid);
|
|
|
|
#ifdef DEBUG_EVENTS
|
|
print_timestamp(ps);
|
|
printf_dbgf("(): %#010lx (%#010lx \"%s\") focused.\n", wid,
|
|
(w ? w->id: None), (w ? w->name: NULL));
|
|
#endif
|
|
|
|
// And we set the focus state here
|
|
if (w) {
|
|
win_set_focused(ps, w, true);
|
|
return w;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Bool
|
|
determine_window_transparency_filter_greyscale(const session_t *ps, Window w);
|
|
|
|
static Bool
|
|
determine_window_transparency_filter_greyscale_blended(const session_t *ps, Window w);
|
|
|
|
static Bool
|
|
determine_window_transparent_to_black(const session_t *ps, Window w);
|
|
|
|
static Bool
|
|
determine_window_transparent_to_desktop(const session_t *ps, Window w);
|
|
|
|
static bool
|
|
get_root_tile(session_t *ps) {
|
|
/*
|
|
if (ps->o.paint_on_overlay) {
|
|
return ps->root_picture;
|
|
} */
|
|
|
|
assert(!ps->root_tile_paint.pixmap);
|
|
ps->root_tile_fill = false;
|
|
|
|
bool fill = false;
|
|
Pixmap pixmap = None;
|
|
|
|
// Get the values of background attributes
|
|
for (int p = 0; background_props_str[p]; p++) {
|
|
winprop_t prop = wid_get_prop(ps, ps->root,
|
|
get_atom(ps, background_props_str[p]),
|
|
1L, XA_PIXMAP, 32);
|
|
if (prop.nitems) {
|
|
pixmap = *prop.data.p32;
|
|
fill = false;
|
|
free_winprop(&prop);
|
|
break;
|
|
}
|
|
free_winprop(&prop);
|
|
}
|
|
|
|
// Make sure the pixmap we got is valid
|
|
if (pixmap && !validate_pixmap(ps, pixmap))
|
|
pixmap = None;
|
|
|
|
// Create a pixmap if there isn't any
|
|
if (!pixmap) {
|
|
pixmap = XCreatePixmap(ps->dpy, ps->root, 1, 1, ps->depth);
|
|
fill = true;
|
|
}
|
|
|
|
// Create Picture
|
|
{
|
|
XRenderPictureAttributes pa = {
|
|
.repeat = True,
|
|
};
|
|
ps->root_tile_paint.pict = XRenderCreatePicture(
|
|
ps->dpy, pixmap, XRenderFindVisualFormat(ps->dpy, ps->vis),
|
|
CPRepeat, &pa);
|
|
}
|
|
|
|
// Fill pixmap if needed
|
|
if (fill) {
|
|
XRenderColor c;
|
|
|
|
c.red = c.green = c.blue = 0x8080;
|
|
c.alpha = 0xffff;
|
|
XRenderFillRectangle(ps->dpy, PictOpSrc, ps->root_tile_paint.pict, &c, 0, 0, 1, 1);
|
|
}
|
|
|
|
ps->root_tile_fill = fill;
|
|
ps->root_tile_paint.pixmap = pixmap;
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (BKEND_GLX == ps->o.backend)
|
|
return glx_bind_pixmap(ps, &ps->root_tile_paint.ptex, ps->root_tile_paint.pixmap, 0, 0, 0);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Paint root window content.
|
|
*/
|
|
static void
|
|
paint_root(session_t *ps, XserverRegion reg_paint) {
|
|
if (!ps->root_tile_paint.pixmap)
|
|
get_root_tile(ps);
|
|
|
|
win_render(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint,
|
|
NULL, ps->root_tile_paint.pict);
|
|
}
|
|
|
|
/**
|
|
* Get a rectangular region a window occupies, excluding shadow.
|
|
*/
|
|
static XserverRegion
|
|
win_get_region(session_t *ps, win *w, bool use_offset) {
|
|
XRectangle r;
|
|
|
|
r.x = (use_offset ? w->a.x: 0);
|
|
r.y = (use_offset ? w->a.y: 0);
|
|
r.width = w->widthb;
|
|
r.height = w->heightb;
|
|
|
|
return XFixesCreateRegion(ps->dpy, &r, 1);
|
|
}
|
|
|
|
/**
|
|
* Get a rectangular region a window occupies, excluding frame and shadow.
|
|
*/
|
|
static XserverRegion
|
|
win_get_region_noframe(session_t *ps, win *w, bool use_offset) {
|
|
XRectangle r;
|
|
|
|
r.x = (use_offset ? w->a.x: 0) + w->a.border_width + w->left_width;
|
|
r.y = (use_offset ? w->a.y: 0) + w->a.border_width + w->top_width;
|
|
r.width = max_i(w->a.width - w->left_width - w->right_width, 0);
|
|
r.height = max_i(w->a.height - w->top_width - w->bottom_width, 0);
|
|
|
|
if (r.width > 0 && r.height > 0)
|
|
return XFixesCreateRegion(ps->dpy, &r, 1);
|
|
else
|
|
return XFixesCreateRegion(ps->dpy, NULL, 0);
|
|
}
|
|
|
|
/**
|
|
* Get a rectangular region a window (and possibly its shadow) occupies.
|
|
*
|
|
* Note w->shadow and shadow geometry must be correct before calling this
|
|
* function.
|
|
*/
|
|
static XserverRegion
|
|
win_extents(session_t *ps, win *w) {
|
|
XRectangle r;
|
|
|
|
r.x = w->a.x;
|
|
r.y = w->a.y;
|
|
r.width = w->widthb;
|
|
r.height = w->heightb;
|
|
|
|
if (w->shadow) {
|
|
XRectangle sr;
|
|
|
|
sr.x = w->a.x + w->shadow_dx;
|
|
sr.y = w->a.y + w->shadow_dy;
|
|
sr.width = w->shadow_width;
|
|
sr.height = w->shadow_height;
|
|
|
|
if (sr.x < r.x) {
|
|
r.width = (r.x + r.width) - sr.x;
|
|
r.x = sr.x;
|
|
}
|
|
|
|
if (sr.y < r.y) {
|
|
r.height = (r.y + r.height) - sr.y;
|
|
r.y = sr.y;
|
|
}
|
|
|
|
if (sr.x + sr.width > r.x + r.width) {
|
|
r.width = sr.x + sr.width - r.x;
|
|
}
|
|
|
|
if (sr.y + sr.height > r.y + r.height) {
|
|
r.height = sr.y + sr.height - r.y;
|
|
}
|
|
}
|
|
|
|
return XFixesCreateRegion(ps->dpy, &r, 1);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the bounding shape of a window.
|
|
*/
|
|
static XserverRegion
|
|
border_size(session_t *ps, win *w, bool use_offset) {
|
|
// Start with the window rectangular region
|
|
XserverRegion fin = win_get_region(ps, w, use_offset);
|
|
|
|
// Only request for a bounding region if the window is shaped
|
|
if (w->bounding_shaped) {
|
|
/*
|
|
* if window doesn't exist anymore, this will generate an error
|
|
* as well as not generate a region. Perhaps a better XFixes
|
|
* architecture would be to have a request that copies instead
|
|
* of creates, that way you'd just end up with an empty region
|
|
* instead of an invalid XID.
|
|
*/
|
|
|
|
XserverRegion border = XFixesCreateRegionFromWindow(
|
|
ps->dpy, w->id, WindowRegionBounding);
|
|
|
|
if (!border)
|
|
return fin;
|
|
|
|
if (use_offset) {
|
|
// Translate the region to the correct place
|
|
XFixesTranslateRegion(ps->dpy, border,
|
|
w->a.x + w->a.border_width,
|
|
w->a.y + w->a.border_width);
|
|
}
|
|
|
|
// Intersect the bounding region we got with the window rectangle, to
|
|
// make sure the bounding region is not bigger than the window
|
|
// rectangle
|
|
XFixesIntersectRegion(ps->dpy, fin, fin, border);
|
|
XFixesDestroyRegion(ps->dpy, border);
|
|
}
|
|
|
|
return fin;
|
|
}
|
|
|
|
/**
|
|
* Look for the client window of a particular window.
|
|
*/
|
|
static Window
|
|
find_client_win(session_t *ps, Window w) {
|
|
if (wid_has_prop(ps, w, ps->atom_client)) {
|
|
return w;
|
|
}
|
|
|
|
Window *children;
|
|
unsigned int nchildren;
|
|
unsigned int i;
|
|
Window ret = 0;
|
|
|
|
if (!wid_get_children(ps, w, &children, &nchildren)) {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < nchildren; ++i) {
|
|
if ((ret = find_client_win(ps, children[i])))
|
|
break;
|
|
}
|
|
|
|
cxfree(children);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Retrieve frame extents from a window.
|
|
*/
|
|
static void
|
|
get_frame_extents(session_t *ps, win *w, Window client) {
|
|
w->left_width = 0;
|
|
w->right_width = 0;
|
|
w->top_width = 0;
|
|
w->bottom_width = 0;
|
|
|
|
winprop_t prop = wid_get_prop(ps, client, ps->atom_frame_extents,
|
|
4L, XA_CARDINAL, 32);
|
|
|
|
if (4 == prop.nitems) {
|
|
const long * const extents = prop.data.p32;
|
|
w->left_width = extents[0];
|
|
w->right_width = extents[1];
|
|
w->top_width = extents[2];
|
|
w->bottom_width = extents[3];
|
|
|
|
if (ps->o.frame_opacity)
|
|
update_reg_ignore_expire(ps, w);
|
|
}
|
|
|
|
#ifdef DEBUG_FRAME
|
|
printf_dbgf("(%#010lx): %d, %d, %d, %d\n", w->id,
|
|
w->left_width, w->right_width, w->top_width, w->bottom_width);
|
|
#endif
|
|
|
|
free_winprop(&prop);
|
|
}
|
|
|
|
/**
|
|
* Get alpha <code>Picture</code> for an opacity in <code>double</code>.
|
|
*/
|
|
static inline Picture
|
|
get_alpha_pict_d(session_t *ps, double o) {
|
|
assert((round(normalize_d(o) / ps->o.alpha_step)) <= round(1.0 / ps->o.alpha_step));
|
|
return ps->alpha_picts[(int) (round(normalize_d(o)
|
|
/ ps->o.alpha_step))];
|
|
}
|
|
|
|
/**
|
|
* Get alpha <code>Picture</code> for an opacity in
|
|
* <code>opacity_t</code>.
|
|
*/
|
|
static inline Picture
|
|
get_alpha_pict_o(session_t *ps, opacity_t o) {
|
|
return get_alpha_pict_d(ps, (double) o / OPAQUE);
|
|
}
|
|
|
|
static win *
|
|
paint_preprocess(session_t *ps, win *list) {
|
|
win *t = NULL, *next = NULL;
|
|
|
|
// Fading step calculation
|
|
time_ms_t steps = 0L;
|
|
if (ps->fade_time) {
|
|
steps = ((get_time_ms() - ps->fade_time) + FADE_DELTA_TOLERANCE * ps->o.fade_delta) / ps->o.fade_delta;
|
|
}
|
|
// Reset fade_time if unset, or there appears to be a time disorder
|
|
if (!ps->fade_time || steps < 0L) {
|
|
ps->fade_time = get_time_ms();
|
|
steps = 0L;
|
|
}
|
|
ps->fade_time += steps * ps->o.fade_delta;
|
|
|
|
XserverRegion last_reg_ignore = None;
|
|
|
|
bool unredir_possible = false;
|
|
// Trace whether it's the highest window to paint
|
|
bool is_highest = true;
|
|
for (win *w = list; w; w = next) {
|
|
bool to_paint = true;
|
|
const winmode_t mode_old = w->mode;
|
|
|
|
// In case calling the fade callback function destroys this window
|
|
next = w->next;
|
|
opacity_t opacity_old = w->opacity;
|
|
|
|
// Data expiration
|
|
{
|
|
// Remove built shadow if needed
|
|
if (w->flags & WFLAG_SIZE_CHANGE)
|
|
free_paint(ps, &w->shadow_paint);
|
|
|
|
// Destroy reg_ignore on all windows if they should expire
|
|
if (ps->reg_ignore_expire)
|
|
free_region(ps, &w->reg_ignore);
|
|
}
|
|
|
|
// Restore flags from last paint if the window is being faded out
|
|
if (IsUnmapped == w->a.map_state) {
|
|
win_set_shadow(ps, w, w->shadow_last);
|
|
w->fade = w->fade_last;
|
|
win_set_invert_color(ps, w, w->invert_color_last);
|
|
win_set_blur_background(ps, w, w->blur_background_last);
|
|
win_set_greyscale_background(ps, w, w->greyscale_background_last);
|
|
}
|
|
|
|
// Update window opacity target and dim state if asked
|
|
if (WFLAG_OPCT_CHANGE & w->flags) {
|
|
calc_opacity(ps, w);
|
|
calc_dim(ps, w);
|
|
}
|
|
|
|
// Run fading
|
|
run_fade(ps, w, steps);
|
|
|
|
// Opacity will not change, from now on.
|
|
|
|
// Give up if it's not damaged or invisible, or it's unmapped and its
|
|
// pixmap is gone (for example due to a ConfigureNotify), or when it's
|
|
// excluded
|
|
if (!w->damaged
|
|
|| w->a.x + w->a.width < 1 || w->a.y + w->a.height < 1
|
|
|| w->a.x >= ps->root_width || w->a.y >= ps->root_height
|
|
|| ((IsUnmapped == w->a.map_state || w->destroyed) && !w->paint.pixmap)
|
|
|| get_alpha_pict_o(ps, w->opacity) == ps->alpha_picts[0]
|
|
|| w->paint_excluded)
|
|
to_paint = false;
|
|
|
|
// to_paint will never change afterward
|
|
|
|
// Determine mode as early as possible
|
|
if (to_paint && (!w->to_paint || w->opacity != opacity_old))
|
|
win_determine_mode(ps, w);
|
|
|
|
if (to_paint) {
|
|
// Fetch bounding region
|
|
if (!w->border_size)
|
|
w->border_size = border_size(ps, w, true);
|
|
|
|
// Fetch window extents
|
|
if (!w->extents)
|
|
w->extents = win_extents(ps, w);
|
|
|
|
// Calculate frame_opacity
|
|
{
|
|
double frame_opacity_old = w->frame_opacity;
|
|
|
|
if (ps->o.frame_opacity && 1.0 != ps->o.frame_opacity
|
|
&& win_has_frame(w))
|
|
w->frame_opacity = get_opacity_percent(w) *
|
|
ps->o.frame_opacity;
|
|
else
|
|
w->frame_opacity = 0.0;
|
|
|
|
// Destroy all reg_ignore above when frame opaque state changes on
|
|
// SOLID mode
|
|
if (w->to_paint && WMODE_SOLID == mode_old
|
|
&& (0.0 == frame_opacity_old) != (0.0 == w->frame_opacity))
|
|
ps->reg_ignore_expire = true;
|
|
}
|
|
|
|
// Calculate shadow opacity
|
|
if (w->frame_opacity)
|
|
w->shadow_opacity = ps->o.shadow_opacity * w->frame_opacity;
|
|
else
|
|
w->shadow_opacity = ps->o.shadow_opacity * get_opacity_percent(w);
|
|
}
|
|
|
|
// Add window to damaged area if its painting status changes
|
|
// or opacity changes
|
|
if (to_paint != w->to_paint || w->opacity != opacity_old)
|
|
add_damage_win(ps, w);
|
|
|
|
// Destroy all reg_ignore above when window mode changes
|
|
if ((to_paint && WMODE_SOLID == w->mode)
|
|
!= (w->to_paint && WMODE_SOLID == mode_old))
|
|
ps->reg_ignore_expire = true;
|
|
|
|
if (to_paint) {
|
|
// Generate ignore region for painting to reduce GPU load
|
|
if (ps->reg_ignore_expire || !w->to_paint) {
|
|
free_region(ps, &w->reg_ignore);
|
|
|
|
// If the window is solid, we add the window region to the
|
|
// ignored region
|
|
if (win_is_solid(ps, w)) {
|
|
if (!w->frame_opacity) {
|
|
if (w->border_size)
|
|
w->reg_ignore = copy_region(ps, w->border_size);
|
|
else
|
|
w->reg_ignore = win_get_region(ps, w, true);
|
|
}
|
|
else {
|
|
w->reg_ignore = win_get_region_noframe(ps, w, true);
|
|
if (w->border_size)
|
|
XFixesIntersectRegion(ps->dpy, w->reg_ignore, w->reg_ignore,
|
|
w->border_size);
|
|
}
|
|
|
|
if (last_reg_ignore)
|
|
XFixesUnionRegion(ps->dpy, w->reg_ignore, w->reg_ignore,
|
|
last_reg_ignore);
|
|
}
|
|
// Otherwise we copy the last region over
|
|
else if (last_reg_ignore)
|
|
w->reg_ignore = copy_region(ps, last_reg_ignore);
|
|
else
|
|
w->reg_ignore = None;
|
|
}
|
|
|
|
last_reg_ignore = w->reg_ignore;
|
|
|
|
// (Un)redirect screen
|
|
// We could definitely unredirect the screen when there's no window to
|
|
// paint, but this is typically unnecessary, may cause flickering when
|
|
// fading is enabled, and could create inconsistency when the wallpaper
|
|
// is not correctly set.
|
|
if (ps->o.unredir_if_possible && is_highest && to_paint) {
|
|
is_highest = false;
|
|
if (win_is_solid(ps, w)
|
|
&& (!w->frame_opacity || !win_has_frame(w))
|
|
&& win_is_fullscreen(ps, w)
|
|
&& !w->unredir_if_possible_excluded)
|
|
unredir_possible = true;
|
|
}
|
|
|
|
// Reset flags
|
|
w->flags = 0;
|
|
}
|
|
|
|
// Avoid setting w->to_paint if w is to be freed
|
|
bool destroyed = (w->opacity_tgt == w->opacity && w->destroyed);
|
|
|
|
if (to_paint) {
|
|
w->prev_trans = t;
|
|
t = w;
|
|
}
|
|
else {
|
|
assert(w->destroyed == (w->fade_callback == destroy_callback));
|
|
check_fade_fin(ps, w);
|
|
}
|
|
|
|
if (!destroyed) {
|
|
w->to_paint = to_paint;
|
|
|
|
if (w->to_paint) {
|
|
// Save flags
|
|
w->shadow_last = w->shadow;
|
|
w->fade_last = w->fade;
|
|
w->invert_color_last = w->invert_color;
|
|
w->blur_background_last = w->blur_background;
|
|
w->greyscale_background_last = w->greyscale_background;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// If possible, unredirect all windows and stop painting
|
|
if (UNSET != ps->o.redirected_force)
|
|
unredir_possible = !ps->o.redirected_force;
|
|
|
|
// If there's no window to paint, and the screen isn't redirected,
|
|
// don't redirect it.
|
|
if (ps->o.unredir_if_possible && is_highest && !ps->redirected)
|
|
unredir_possible = true;
|
|
if (unredir_possible) {
|
|
if (ps->redirected) {
|
|
if (!ps->o.unredir_if_possible_delay || ps->tmout_unredir_hit)
|
|
redir_stop(ps);
|
|
else if (!ps->tmout_unredir->enabled) {
|
|
timeout_reset(ps, ps->tmout_unredir);
|
|
ps->tmout_unredir->enabled = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
ps->tmout_unredir->enabled = false;
|
|
redir_start(ps);
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
/**
|
|
* Paint the shadow of a window.
|
|
*/
|
|
static inline void
|
|
win_paint_shadow(session_t *ps, win *w,
|
|
XserverRegion reg_paint, const reg_data_t *pcache_reg) {
|
|
// Bind shadow pixmap to GLX texture if needed
|
|
paint_bind_tex(ps, &w->shadow_paint, 0, 0, 32, false);
|
|
|
|
if (!paint_isvalid(ps, &w->shadow_paint)) {
|
|
printf_errf("(%#010lx): Missing painting data. This is a bad sign.", w->id);
|
|
return;
|
|
}
|
|
|
|
render(ps, 0, 0, w->a.x + w->shadow_dx, w->a.y + w->shadow_dy,
|
|
w->shadow_width, w->shadow_height, w->shadow_opacity, true, false,
|
|
w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, pcache_reg, NULL);
|
|
}
|
|
|
|
/**
|
|
* Create an picture.
|
|
*/
|
|
static inline Picture
|
|
xr_build_picture(session_t *ps, int wid, int hei,
|
|
XRenderPictFormat *pictfmt) {
|
|
if (!pictfmt)
|
|
pictfmt = XRenderFindVisualFormat(ps->dpy, ps->vis);
|
|
|
|
int depth = pictfmt->depth;
|
|
|
|
Pixmap tmp_pixmap = XCreatePixmap(ps->dpy, ps->root, wid, hei, depth);
|
|
if (!tmp_pixmap)
|
|
return None;
|
|
|
|
Picture tmp_picture = XRenderCreatePicture(ps->dpy, tmp_pixmap,
|
|
pictfmt, 0, 0);
|
|
free_pixmap(ps, &tmp_pixmap);
|
|
|
|
return tmp_picture;
|
|
}
|
|
|
|
/**
|
|
* @brief Blur an area on a buffer.
|
|
*
|
|
* @param ps current session
|
|
* @param tgt_buffer a buffer as both source and destination
|
|
* @param x x pos
|
|
* @param y y pos
|
|
* @param wid width
|
|
* @param hei height
|
|
* @param blur_kerns blur kernels, ending with a NULL, guaranteed to have at
|
|
* least one kernel
|
|
* @param reg_clip a clipping region to be applied on intermediate buffers
|
|
*
|
|
* @return true if successful, false otherwise
|
|
*/
|
|
static bool
|
|
xr_blur_dst(session_t *ps, Picture tgt_buffer,
|
|
int x, int y, int wid, int hei, XFixed **blur_kerns,
|
|
XserverRegion reg_clip) {
|
|
assert(blur_kerns[0]);
|
|
|
|
// Directly copying from tgt_buffer to it does not work, so we create a
|
|
// Picture in the middle.
|
|
Picture tmp_picture = xr_build_picture(ps, wid, hei, NULL);
|
|
|
|
if (!tmp_picture) {
|
|
printf_errf("(): Failed to build intermediate Picture.");
|
|
return false;
|
|
}
|
|
|
|
if (reg_clip && tmp_picture)
|
|
XFixesSetPictureClipRegion(ps->dpy, tmp_picture, reg_clip, 0, 0);
|
|
|
|
Picture src_pict = tgt_buffer, dst_pict = tmp_picture;
|
|
for (int i = 0; blur_kerns[i]; ++i) {
|
|
assert(i < MAX_BLUR_PASS - 1);
|
|
XFixed *convolution_blur = blur_kerns[i];
|
|
int kwid = XFixedToDouble(convolution_blur[0]),
|
|
khei = XFixedToDouble(convolution_blur[1]);
|
|
bool rd_from_tgt = (tgt_buffer == src_pict);
|
|
|
|
// Copy from source picture to destination. The filter must
|
|
// be applied on source picture, to get the nearby pixels outside the
|
|
// window.
|
|
XRenderSetPictureFilter(ps->dpy, src_pict, XRFILTER_CONVOLUTION,
|
|
convolution_blur, kwid * khei + 2);
|
|
XRenderComposite(ps->dpy, PictOpSrc, src_pict, None, dst_pict,
|
|
(rd_from_tgt ? x: 0), (rd_from_tgt ? y: 0), 0, 0,
|
|
(rd_from_tgt ? 0: x), (rd_from_tgt ? 0: y), wid, hei);
|
|
xrfilter_reset(ps, src_pict);
|
|
|
|
{
|
|
XserverRegion tmp = src_pict;
|
|
src_pict = dst_pict;
|
|
dst_pict = tmp;
|
|
}
|
|
}
|
|
|
|
if (src_pict != tgt_buffer)
|
|
XRenderComposite(ps->dpy, PictOpSrc, src_pict, None, tgt_buffer,
|
|
0, 0, 0, 0, x, y, wid, hei);
|
|
|
|
free_picture(ps, &tmp_picture);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Make an area on a buffer greyscale.
|
|
*
|
|
* @param ps current session
|
|
* @param tgt_buffer a buffer as both source and destination
|
|
* @param x x pos
|
|
* @param y y pos
|
|
* @param wid width
|
|
* @param hei height
|
|
* @param reg_clip a clipping region to be applied on intermediate buffers
|
|
*
|
|
* @return true if successful, false otherwise
|
|
*/
|
|
static bool
|
|
xr_greyscale_dst(session_t *ps, Picture tgt_buffer,
|
|
int x, int y, int wid, int hei, XserverRegion reg_clip) {
|
|
|
|
// Directly copying from tgt_buffer to it does not work, so we create a
|
|
// Picture in the middle.
|
|
Picture tmp_picture = xr_build_picture(ps, wid, hei, NULL);
|
|
|
|
if (!tmp_picture) {
|
|
printf_errf("(): Failed to build intermediate Picture.");
|
|
return false;
|
|
}
|
|
|
|
if (reg_clip && tmp_picture)
|
|
XFixesSetPictureClipRegion(ps->dpy, tmp_picture, reg_clip, 0, 0);
|
|
|
|
XRenderComposite(ps->dpy, PictOpSrc, ps->black_picture, None,
|
|
tmp_picture, 0, 0, 0, 0, 0, 0, wid, hei);
|
|
|
|
XRenderComposite(ps->dpy, PictOpHSLLuminosity, tgt_buffer, None,
|
|
tmp_picture, x, y, 0, 0, 0, 0, wid, hei);
|
|
|
|
XRenderComposite(ps->dpy, PictOpSrc, tmp_picture, None, tgt_buffer,
|
|
0, 0, 0, 0, x, y, wid, hei);
|
|
|
|
free_picture(ps, &tmp_picture);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* WORK-IN-PROGRESS!
|
|
static void
|
|
xr_take_screenshot(session_t *ps) {
|
|
XImage *img = XGetImage(ps->dpy, get_tgt_window(ps), 0, 0,
|
|
ps->root_width, ps->root_height, AllPlanes, XYPixmap);
|
|
if (!img) {
|
|
printf_errf("(): Failed to get XImage.");
|
|
return NULL;
|
|
}
|
|
assert(0 == img->xoffset);
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Blur the background of a window.
|
|
*/
|
|
static inline void
|
|
win_blur_background(session_t *ps, win *w, Picture tgt_buffer,
|
|
XserverRegion reg_paint, const reg_data_t *pcache_reg) {
|
|
const int x = w->a.x;
|
|
const int y = w->a.y;
|
|
const int wid = w->widthb;
|
|
const int hei = w->heightb;
|
|
|
|
double factor_center = 1.0;
|
|
// Adjust blur strength according to window opacity, to make it appear
|
|
// better during fading
|
|
if (!ps->o.blur_background_fixed) {
|
|
double pct = 1.0 - get_opacity_percent(w) * (1.0 - 1.0 / 9.0);
|
|
factor_center = pct * 8.0 / (1.1 - pct);
|
|
}
|
|
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
case BKEND_XR_GLX_HYBRID:
|
|
{
|
|
// Normalize blur kernels
|
|
for (int i = 0; i < MAX_BLUR_PASS; ++i) {
|
|
XFixed *kern_src = ps->o.blur_kerns[i];
|
|
XFixed *kern_dst = ps->blur_kerns_cache[i];
|
|
assert(i < MAX_BLUR_PASS);
|
|
if (!kern_src) {
|
|
assert(!kern_dst);
|
|
break;
|
|
}
|
|
|
|
assert(!kern_dst
|
|
|| (kern_src[0] == kern_dst[0] && kern_src[1] == kern_dst[1]));
|
|
|
|
// Skip for fixed factor_center if the cache exists already
|
|
if (ps->o.blur_background_fixed && kern_dst) continue;
|
|
|
|
int kwid = XFixedToDouble(kern_src[0]),
|
|
khei = XFixedToDouble(kern_src[1]);
|
|
|
|
// Allocate cache space if needed
|
|
if (!kern_dst) {
|
|
kern_dst = malloc((kwid * khei + 2) * sizeof(XFixed));
|
|
if (!kern_dst) {
|
|
printf_errf("(): Failed to allocate memory for blur kernel.");
|
|
return;
|
|
}
|
|
ps->blur_kerns_cache[i] = kern_dst;
|
|
}
|
|
|
|
// Modify the factor of the center pixel
|
|
kern_src[2 + (khei / 2) * kwid + kwid / 2] =
|
|
XDoubleToFixed(factor_center);
|
|
|
|
// Copy over
|
|
memcpy(kern_dst, kern_src, (kwid * khei + 2) * sizeof(XFixed));
|
|
normalize_conv_kern(kwid, khei, kern_dst + 2);
|
|
}
|
|
|
|
// Minimize the region we try to blur, if the window itself is not
|
|
// opaque, only the frame is.
|
|
XserverRegion reg_noframe = None;
|
|
if (win_is_solid(ps, w)) {
|
|
XserverRegion reg_all = border_size(ps, w, false);
|
|
reg_noframe = win_get_region_noframe(ps, w, false);
|
|
XFixesSubtractRegion(ps->dpy, reg_noframe, reg_all, reg_noframe);
|
|
free_region(ps, ®_all);
|
|
}
|
|
xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache,
|
|
reg_noframe);
|
|
free_region(ps, ®_noframe);
|
|
}
|
|
break;
|
|
#ifdef CONFIG_VSYNC_OPENGL_GLSL
|
|
case BKEND_GLX:
|
|
// TODO: Handle frame opacity
|
|
glx_blur_dst(ps, x, y, wid, hei, ps->psglx->z - 0.5, factor_center,
|
|
reg_paint, pcache_reg, &w->glx_blur_cache);
|
|
break;
|
|
#endif
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the background of a window to greyscale.
|
|
*/
|
|
static inline void
|
|
win_greyscale_background(session_t *ps, win *w, Picture tgt_buffer,
|
|
XserverRegion reg_paint, const reg_data_t *pcache_reg) {
|
|
const int x = w->a.x;
|
|
const int y = w->a.y;
|
|
const int wid = w->widthb;
|
|
const int hei = w->heightb;
|
|
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
case BKEND_XR_GLX_HYBRID:
|
|
{
|
|
xr_greyscale_dst(ps, tgt_buffer, x, y, wid, hei, reg_paint);
|
|
}
|
|
break;
|
|
#ifdef CONFIG_VSYNC_OPENGL_GLSL
|
|
case BKEND_GLX:
|
|
glx_greyscale_dst(ps, x, y, wid, hei, ps->psglx->z - 0.5,
|
|
NULL, reg_paint, pcache_reg, &w->glx_greyscale_cache);
|
|
break;
|
|
#endif
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
render_(session_t *ps, int x, int y, int dx, int dy, int wid, int hei,
|
|
double opacity, bool argb, bool neg,
|
|
Picture pict, glx_texture_t *ptex,
|
|
XserverRegion reg_paint, const reg_data_t *pcache_reg
|
|
#ifdef CONFIG_VSYNC_OPENGL_GLSL
|
|
, const glx_prog_main_t *pprogram
|
|
#endif
|
|
) {
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
case BKEND_XR_GLX_HYBRID:
|
|
{
|
|
Picture alpha_pict = get_alpha_pict_d(ps, opacity);
|
|
if (alpha_pict != ps->alpha_picts[0]) {
|
|
int op = ((!argb && !alpha_pict) ? PictOpSrc: PictOpOver);
|
|
XRenderComposite(ps->dpy, op, pict, alpha_pict,
|
|
ps->tgt_buffer.pict, x, y, 0, 0, dx, dy, wid, hei);
|
|
}
|
|
break;
|
|
}
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
case BKEND_GLX:
|
|
glx_render(ps, ptex, x, y, dx, dy, wid, hei,
|
|
ps->psglx->z, opacity, argb, neg, reg_paint, pcache_reg, pprogram);
|
|
ps->psglx->z += 1;
|
|
break;
|
|
#endif
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Paint a window itself and dim it if asked.
|
|
*/
|
|
static inline void
|
|
win_paint_win(session_t *ps, win *w, XserverRegion reg_paint,
|
|
const reg_data_t *pcache_reg) {
|
|
glx_mark(ps, w->id, true);
|
|
|
|
// Fetch Pixmap
|
|
if (!w->paint.pixmap && ps->has_name_pixmap) {
|
|
set_ignore_next(ps);
|
|
w->paint.pixmap = XCompositeNameWindowPixmap(ps->dpy, w->id);
|
|
if (w->paint.pixmap)
|
|
free_fence(ps, &w->fence);
|
|
}
|
|
|
|
Drawable draw = w->paint.pixmap;
|
|
if (!draw)
|
|
draw = w->id;
|
|
|
|
// XRender: Build picture
|
|
if (bkend_use_xrender(ps) && !w->paint.pict) {
|
|
{
|
|
XRenderPictureAttributes pa = {
|
|
.subwindow_mode = IncludeInferiors,
|
|
};
|
|
|
|
w->paint.pict = XRenderCreatePicture(ps->dpy, draw, w->pictfmt,
|
|
CPSubwindowMode, &pa);
|
|
}
|
|
}
|
|
|
|
if (IsViewable == w->a.map_state)
|
|
xr_sync(ps, draw, &w->fence);
|
|
|
|
// GLX: Build texture
|
|
// Let glx_bind_pixmap() determine pixmap size, because if the user
|
|
// is resizing windows, the width and height we get may not be up-to-date,
|
|
// causing the jittering issue M4he reported in #7.
|
|
if (!paint_bind_tex(ps, &w->paint, 0, 0, 0,
|
|
(!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) {
|
|
printf_errf("(%#010lx): Failed to bind texture. Expect troubles.", w->id);
|
|
}
|
|
w->pixmap_damaged = false;
|
|
|
|
if (!paint_isvalid(ps, &w->paint)) {
|
|
printf_errf("(%#010lx): Missing painting data. This is a bad sign.", w->id);
|
|
return;
|
|
}
|
|
|
|
const int x = w->a.x;
|
|
const int y = w->a.y;
|
|
const int wid = w->widthb;
|
|
const int hei = w->heightb;
|
|
|
|
Picture pict = w->paint.pict;
|
|
|
|
if (w->greyscale_blended_background) {
|
|
// Set window background to greyscale
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
case BKEND_XR_GLX_HYBRID:
|
|
{
|
|
// Blend here such that 0 window alpha is fully colored and 100 window alpha is fully greyscale
|
|
// PictOpInReverse is used to copy alpha from the source to the destination while preserving destination color
|
|
// provided that the source has alpha set to 1 (equivalent of CAIRO_OPERATOR_DEST_IN)
|
|
const int x = w->a.x;
|
|
const int y = w->a.y;
|
|
const int wid = w->widthb;
|
|
const int hei = w->heightb;
|
|
XserverRegion reg_clip = reg_paint;
|
|
Picture tgt_buffer = ps->tgt_buffer.pict;
|
|
|
|
// Apply clipping region to save some CPU
|
|
if (reg_paint) {
|
|
XserverRegion reg = copy_region(ps, reg_paint);
|
|
XFixesTranslateRegion(ps->dpy, reg, -x, -y);
|
|
XFixesSetPictureClipRegion(ps->dpy, pict, 0, 0, reg);
|
|
free_region(ps, ®);
|
|
}
|
|
|
|
// Create greyscale version of background
|
|
Picture greyscale_picture = xr_build_picture(ps, wid, hei, w->pictfmt);
|
|
XRenderComposite(ps->dpy, PictOpSrc, tgt_buffer, None,
|
|
greyscale_picture, x, y, 0, 0, 0, 0, wid, hei);
|
|
win_greyscale_background(ps, w, greyscale_picture, reg_paint, pcache_reg);
|
|
|
|
Picture tmp_picture = xr_build_picture(ps, wid, hei, w->pictfmt);
|
|
|
|
if (!tmp_picture) {
|
|
printf_errf("(): Failed to build intermediate Picture.");
|
|
}
|
|
else {
|
|
if (reg_clip && tmp_picture)
|
|
XFixesSetPictureClipRegion(ps->dpy, tmp_picture, reg_clip, 0, 0);
|
|
|
|
// Transfer greyscale picture to temporary picture
|
|
XRenderComposite(ps->dpy, PictOpSrc, greyscale_picture, None,
|
|
tmp_picture, 0, 0, 0, 0, 0, 0, wid, hei);
|
|
|
|
// Transfer alpha of window to temporary picture
|
|
XRenderComposite(ps->dpy, PictOpInReverse, pict, None,
|
|
tmp_picture, 0, 0, 0, 0, 0, 0, wid, hei);
|
|
|
|
// Blend greyscale picture over main color buffer
|
|
XRenderComposite(ps->dpy, PictOpOver, tmp_picture, None, tgt_buffer,
|
|
0, 0, 0, 0, x, y, wid, hei);
|
|
|
|
free_picture(ps, &tmp_picture);
|
|
free_picture(ps, &greyscale_picture);
|
|
}
|
|
}
|
|
break;
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
case BKEND_GLX:
|
|
glx_greyscale_dst(ps, x, y, wid, hei, ps->psglx->z - 0.5,
|
|
w->paint.ptex, reg_paint, pcache_reg, &w->glx_greyscale_cache);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Invert window color, if required
|
|
if (bkend_use_xrender(ps) && w->invert_color) {
|
|
Picture newpict = xr_build_picture(ps, wid, hei, w->pictfmt);
|
|
if (newpict) {
|
|
// Apply clipping region to save some CPU
|
|
if (reg_paint) {
|
|
XserverRegion reg = copy_region(ps, reg_paint);
|
|
XFixesTranslateRegion(ps->dpy, reg, -x, -y);
|
|
XFixesSetPictureClipRegion(ps->dpy, newpict, 0, 0, reg);
|
|
free_region(ps, ®);
|
|
}
|
|
|
|
XRenderComposite(ps->dpy, PictOpSrc, pict, None,
|
|
newpict, 0, 0, 0, 0, 0, 0, wid, hei);
|
|
XRenderComposite(ps->dpy, PictOpDifference, ps->white_picture, None,
|
|
newpict, 0, 0, 0, 0, 0, 0, wid, hei);
|
|
// We use an extra PictOpInReverse operation to get correct pixel
|
|
// alpha. There could be a better solution.
|
|
if (WMODE_ARGB == w->mode)
|
|
XRenderComposite(ps->dpy, PictOpInReverse, pict, None,
|
|
newpict, 0, 0, 0, 0, 0, 0, wid, hei);
|
|
pict = newpict;
|
|
}
|
|
}
|
|
|
|
double dopacity = get_opacity_percent(w);
|
|
|
|
if (w->greyscale_blended_background) {
|
|
double scaling_factor = (1.0 / w->greyscale_blended_background_alpha_divisor);
|
|
dopacity = dopacity * scaling_factor;
|
|
}
|
|
|
|
if (!w->frame_opacity) {
|
|
win_render(ps, w, 0, 0, wid, hei, dopacity, reg_paint, pcache_reg, pict);
|
|
}
|
|
else {
|
|
// Painting parameters
|
|
const int t = w->a.border_width + w->top_width;
|
|
const int l = w->a.border_width + w->left_width;
|
|
const int b = w->a.border_width + w->bottom_width;
|
|
const int r = w->a.border_width + w->right_width;
|
|
|
|
#define COMP_BDR(cx, cy, cwid, chei) \
|
|
win_render(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity, \
|
|
reg_paint, pcache_reg, pict)
|
|
|
|
// The following complicated logic is required because some broken
|
|
// window managers (I'm talking about you, Openbox!) that makes
|
|
// top_width + bottom_width > height in some cases.
|
|
|
|
// top
|
|
int phei = min_i(t, hei);
|
|
if (phei > 0)
|
|
COMP_BDR(0, 0, wid, phei);
|
|
|
|
if (hei > t) {
|
|
phei = min_i(hei - t, b);
|
|
|
|
// bottom
|
|
if (phei > 0)
|
|
COMP_BDR(0, hei - phei, wid, phei);
|
|
|
|
phei = hei - t - phei;
|
|
if (phei > 0) {
|
|
int pwid = min_i(l, wid);
|
|
// left
|
|
if (pwid > 0)
|
|
COMP_BDR(0, t, pwid, phei);
|
|
|
|
if (wid > l) {
|
|
pwid = min_i(wid - l, r);
|
|
|
|
// right
|
|
if (pwid > 0)
|
|
COMP_BDR(wid - pwid, t, pwid, phei);
|
|
|
|
pwid = wid - l - pwid;
|
|
if (pwid > 0) {
|
|
// body
|
|
win_render(ps, w, l, t, pwid, phei, dopacity, reg_paint, pcache_reg, pict);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef COMP_BDR
|
|
|
|
if (pict != w->paint.pict)
|
|
free_picture(ps, &pict);
|
|
|
|
// Dimming the window if needed
|
|
if (w->dim) {
|
|
double dim_opacity = ps->o.inactive_dim;
|
|
if (!ps->o.inactive_dim_fixed)
|
|
dim_opacity *= get_opacity_percent(w);
|
|
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
case BKEND_XR_GLX_HYBRID:
|
|
{
|
|
unsigned short cval = 0xffff * dim_opacity;
|
|
|
|
// Premultiply color
|
|
XRenderColor color = {
|
|
.red = 0, .green = 0, .blue = 0, .alpha = cval,
|
|
};
|
|
|
|
XRectangle rect = {
|
|
.x = x,
|
|
.y = y,
|
|
.width = wid,
|
|
.height = hei,
|
|
};
|
|
|
|
XRenderFillRectangles(ps->dpy, PictOpOver, ps->tgt_buffer.pict,
|
|
&color, &rect, 1);
|
|
}
|
|
break;
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
case BKEND_GLX:
|
|
glx_dim_dst(ps, x, y, wid, hei, ps->psglx->z - 0.7, dim_opacity,
|
|
reg_paint, pcache_reg);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
glx_mark(ps, w->id, false);
|
|
}
|
|
|
|
/**
|
|
* Rebuild cached <code>screen_reg</code>.
|
|
*/
|
|
static void
|
|
rebuild_screen_reg(session_t *ps) {
|
|
if (ps->screen_reg)
|
|
XFixesDestroyRegion(ps->dpy, ps->screen_reg);
|
|
ps->screen_reg = get_screen_region(ps);
|
|
}
|
|
|
|
/**
|
|
* Rebuild <code>shadow_exclude_reg</code>.
|
|
*/
|
|
static void
|
|
rebuild_shadow_exclude_reg(session_t *ps) {
|
|
free_region(ps, &ps->shadow_exclude_reg);
|
|
XRectangle rect = geom_to_rect(ps, &ps->o.shadow_exclude_reg_geom, NULL);
|
|
ps->shadow_exclude_reg = rect_to_reg(ps, &rect);
|
|
}
|
|
|
|
static void
|
|
paint_all(session_t *ps, XserverRegion region, XserverRegion region_real, win *t) {
|
|
if (!region_real)
|
|
region_real = region;
|
|
|
|
#ifdef DEBUG_REPAINT
|
|
static struct timespec last_paint = { 0 };
|
|
#endif
|
|
XserverRegion reg_paint = None, reg_tmp = None, reg_tmp2 = None;
|
|
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (bkend_use_glx(ps)) {
|
|
glx_paint_pre(ps, ®ion);
|
|
}
|
|
#endif
|
|
|
|
if (!region) {
|
|
region_real = region = get_screen_region(ps);
|
|
}
|
|
else {
|
|
// Remove the damaged area out of screen
|
|
XFixesIntersectRegion(ps->dpy, region, region, ps->screen_reg);
|
|
}
|
|
|
|
#ifdef MONITOR_REPAINT
|
|
// Note: MONITOR_REPAINT cannot work with DBE right now.
|
|
// Picture old_tgt_buffer = ps->tgt_buffer.pict;
|
|
ps->tgt_buffer.pict = ps->tgt_picture;
|
|
#else
|
|
if (!paint_isvalid(ps, &ps->tgt_buffer)) {
|
|
// DBE painting mode: Directly paint to a Picture of the back buffer
|
|
if (BKEND_XRENDER == ps->o.backend && ps->o.dbe) {
|
|
ps->tgt_buffer.pict = XRenderCreatePicture(ps->dpy, ps->root_dbe,
|
|
XRenderFindVisualFormat(ps->dpy, ps->vis),
|
|
0, 0);
|
|
}
|
|
// No-DBE painting mode: Paint to an intermediate Picture then paint
|
|
// the Picture to root window
|
|
else {
|
|
if (!ps->tgt_buffer.pixmap) {
|
|
free_paint(ps, &ps->tgt_buffer);
|
|
ps->tgt_buffer.pixmap = XCreatePixmap(ps->dpy, ps->root,
|
|
ps->root_width, ps->root_height, ps->depth);
|
|
}
|
|
|
|
if (BKEND_GLX != ps->o.backend)
|
|
ps->tgt_buffer.pict = XRenderCreatePicture(ps->dpy,
|
|
ps->tgt_buffer.pixmap, XRenderFindVisualFormat(ps->dpy, ps->vis),
|
|
0, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (BKEND_XRENDER == ps->o.backend)
|
|
XFixesSetPictureClipRegion(ps->dpy, ps->tgt_picture, 0, 0, region_real);
|
|
|
|
#ifdef MONITOR_REPAINT
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
XRenderComposite(ps->dpy, PictOpSrc, ps->black_picture, None,
|
|
ps->tgt_picture, 0, 0, 0, 0, 0, 0,
|
|
ps->root_width, ps->root_height);
|
|
break;
|
|
case BKEND_GLX:
|
|
case BKEND_XR_GLX_HYBRID:
|
|
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
if (t && t->reg_ignore) {
|
|
// Calculate the region upon which the root window is to be painted
|
|
// based on the ignore region of the lowest window, if available
|
|
reg_paint = reg_tmp = XFixesCreateRegion(ps->dpy, NULL, 0);
|
|
XFixesSubtractRegion(ps->dpy, reg_paint, region, t->reg_ignore);
|
|
}
|
|
else {
|
|
reg_paint = region;
|
|
}
|
|
|
|
set_tgt_clip(ps, reg_paint, NULL);
|
|
paint_root(ps, reg_paint);
|
|
|
|
// Create temporary regions for use during painting
|
|
if (!reg_tmp)
|
|
reg_tmp = XFixesCreateRegion(ps->dpy, NULL, 0);
|
|
reg_tmp2 = XFixesCreateRegion(ps->dpy, NULL, 0);
|
|
|
|
for (win *w = t; w; w = w->prev_trans) {
|
|
// Painting shadow
|
|
if (w->shadow) {
|
|
// Lazy shadow building
|
|
if (!w->shadow_paint.pixmap)
|
|
win_build_shadow(ps, w, 1);
|
|
|
|
// Shadow is to be painted based on the ignore region of current
|
|
// window
|
|
if (w->reg_ignore) {
|
|
if (w == t) {
|
|
// If it's the first cycle and reg_tmp2 is not ready, calculate
|
|
// the paint region here
|
|
reg_paint = reg_tmp;
|
|
XFixesSubtractRegion(ps->dpy, reg_paint, region, w->reg_ignore);
|
|
}
|
|
else {
|
|
// Otherwise, used the cached region during last cycle
|
|
reg_paint = reg_tmp2;
|
|
}
|
|
XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, w->extents);
|
|
}
|
|
else {
|
|
reg_paint = reg_tmp;
|
|
XFixesIntersectRegion(ps->dpy, reg_paint, region, w->extents);
|
|
}
|
|
|
|
if (ps->shadow_exclude_reg)
|
|
XFixesSubtractRegion(ps->dpy, reg_paint, reg_paint,
|
|
ps->shadow_exclude_reg);
|
|
|
|
// Might be worthwhile to crop the region to shadow border
|
|
{
|
|
XRectangle rec_shadow_border = {
|
|
.x = w->a.x + w->shadow_dx,
|
|
.y = w->a.y + w->shadow_dy,
|
|
.width = w->shadow_width,
|
|
.height = w->shadow_height
|
|
};
|
|
XserverRegion reg_shadow = XFixesCreateRegion(ps->dpy,
|
|
&rec_shadow_border, 1);
|
|
XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, reg_shadow);
|
|
free_region(ps, ®_shadow);
|
|
}
|
|
|
|
// Clear the shadow here instead of in make_shadow() for saving GPU
|
|
// power and handling shaped windows
|
|
if (ps->o.clear_shadow && w->border_size)
|
|
XFixesSubtractRegion(ps->dpy, reg_paint, reg_paint, w->border_size);
|
|
|
|
#ifdef CONFIG_XINERAMA
|
|
if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0)
|
|
XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint,
|
|
ps->xinerama_scr_regs[w->xinerama_scr]);
|
|
#endif
|
|
|
|
// Detect if the region is empty before painting
|
|
{
|
|
reg_data_t cache_reg = REG_DATA_INIT;
|
|
if (region == reg_paint
|
|
|| !is_region_empty(ps, reg_paint, &cache_reg)) {
|
|
set_tgt_clip(ps, reg_paint, &cache_reg);
|
|
|
|
win_paint_shadow(ps, w, reg_paint, &cache_reg);
|
|
}
|
|
free_reg_data(&cache_reg);
|
|
}
|
|
}
|
|
|
|
// Calculate the region based on the reg_ignore of the next (higher)
|
|
// window and the bounding region
|
|
reg_paint = reg_tmp;
|
|
if (w->prev_trans && w->prev_trans->reg_ignore) {
|
|
XFixesSubtractRegion(ps->dpy, reg_paint, region,
|
|
w->prev_trans->reg_ignore);
|
|
// Copy the subtracted region to be used for shadow painting in next
|
|
// cycle
|
|
XFixesCopyRegion(ps->dpy, reg_tmp2, reg_paint);
|
|
|
|
if (w->border_size)
|
|
XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, w->border_size);
|
|
}
|
|
else {
|
|
if (w->border_size)
|
|
XFixesIntersectRegion(ps->dpy, reg_paint, region, w->border_size);
|
|
else
|
|
reg_paint = region;
|
|
}
|
|
|
|
{
|
|
reg_data_t cache_reg = REG_DATA_INIT;
|
|
if (!is_region_empty(ps, reg_paint, &cache_reg)) {
|
|
set_tgt_clip(ps, reg_paint, &cache_reg);
|
|
|
|
/* Here we redraw the background of the transparent window if we want
|
|
to do anything special (i.e. anything other than showing the
|
|
windows and desktop prestacked behind of the window).
|
|
For example, if you want to blur the background or show another
|
|
background pixmap entirely here is the place to do it; simply
|
|
draw the new background onto ps->tgt_buffer.pict before continuing! */
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
case BKEND_XR_GLX_HYBRID:
|
|
{
|
|
if (w->show_root_tile) {
|
|
XRenderComposite (ps->dpy, PictOpSrc, ps->root_tile_paint.pict, None, ps->tgt_buffer.pict,
|
|
w->a.x, w->a.y, w->a.x, w->a.y,
|
|
w->a.x, w->a.y, w->widthb, w->heightb);
|
|
}
|
|
else if (w->show_black_background) {
|
|
XRenderComposite (ps->dpy, PictOpSrc, ps->black_picture, None, ps->tgt_buffer.pict,
|
|
w->a.x, w->a.y, w->a.x, w->a.y,
|
|
w->a.x, w->a.y, w->widthb, w->heightb);
|
|
}
|
|
break;
|
|
}
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
case BKEND_GLX:
|
|
{
|
|
if (w->show_root_tile) {
|
|
glx_render(ps, ps->root_tile_paint.ptex, w->a.x, w->a.y, w->a.x, w->a.y, w->widthb, w->heightb,
|
|
ps->psglx->z, 1.0, false, false, reg_paint, &cache_reg, NULL);
|
|
}
|
|
else if (w->show_black_background) {
|
|
glx_render_specified_color(ps, 0, w->a.x, w->a.y, w->widthb, w->heightb,
|
|
ps->psglx->z, reg_paint, &cache_reg);
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Blur window background
|
|
if (w->blur_background && (!win_is_solid(ps, w)
|
|
|| (ps->o.blur_background_frame && w->frame_opacity))) {
|
|
win_blur_background(ps, w, ps->tgt_buffer.pict, reg_paint, &cache_reg);
|
|
}
|
|
|
|
// Set window background to greyscale
|
|
if (w->greyscale_background && (!win_is_solid(ps, w))) {
|
|
win_greyscale_background(ps, w, ps->tgt_buffer.pict, reg_paint, &cache_reg);
|
|
}
|
|
|
|
// Painting the window
|
|
win_paint_win(ps, w, reg_paint, &cache_reg);
|
|
}
|
|
free_reg_data(&cache_reg);
|
|
}
|
|
}
|
|
|
|
// Free up all temporary regions
|
|
XFixesDestroyRegion(ps->dpy, reg_tmp);
|
|
XFixesDestroyRegion(ps->dpy, reg_tmp2);
|
|
|
|
// Do this as early as possible
|
|
if (!ps->o.dbe)
|
|
set_tgt_clip(ps, None, NULL);
|
|
|
|
if (ps->o.vsync) {
|
|
// Make sure all previous requests are processed to achieve best
|
|
// effect
|
|
XSync(ps->dpy, False);
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (glx_has_context(ps)) {
|
|
if (ps->o.vsync_use_glfinish)
|
|
glFinish();
|
|
else
|
|
glFlush();
|
|
glXWaitX();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Wait for VBlank. We could do it aggressively (send the painting
|
|
// request and XFlush() on VBlank) or conservatively (send the request
|
|
// only on VBlank).
|
|
if (!ps->o.vsync_aggressive)
|
|
vsync_wait(ps);
|
|
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
// DBE painting mode, only need to swap the buffer
|
|
if (ps->o.dbe) {
|
|
XdbeSwapInfo swap_info = {
|
|
.swap_window = get_tgt_window(ps),
|
|
// Is it safe to use XdbeUndefined?
|
|
.swap_action = XdbeCopied
|
|
};
|
|
XdbeSwapBuffers(ps->dpy, &swap_info, 1);
|
|
}
|
|
// No-DBE painting mode
|
|
else if (ps->tgt_buffer.pict != ps->tgt_picture) {
|
|
XRenderComposite(
|
|
ps->dpy, PictOpSrc, ps->tgt_buffer.pict, None,
|
|
ps->tgt_picture, 0, 0, 0, 0,
|
|
0, 0, ps->root_width, ps->root_height);
|
|
}
|
|
break;
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
case BKEND_XR_GLX_HYBRID:
|
|
XSync(ps->dpy, False);
|
|
if (ps->o.vsync_use_glfinish)
|
|
glFinish();
|
|
else
|
|
glFlush();
|
|
glXWaitX();
|
|
assert(ps->tgt_buffer.pixmap);
|
|
xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence);
|
|
paint_bind_tex_real(ps, &ps->tgt_buffer,
|
|
ps->root_width, ps->root_height, ps->depth,
|
|
!ps->o.glx_no_rebind_pixmap);
|
|
// See #163
|
|
xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence);
|
|
if (ps->o.vsync_use_glfinish)
|
|
glFinish();
|
|
else
|
|
glFlush();
|
|
glXWaitX();
|
|
glx_render(ps, ps->tgt_buffer.ptex, 0, 0, 0, 0,
|
|
ps->root_width, ps->root_height, 0, 1.0, false, false,
|
|
region_real, NULL, NULL);
|
|
// No break here!
|
|
case BKEND_GLX:
|
|
if (ps->o.glx_use_copysubbuffermesa)
|
|
glx_swap_copysubbuffermesa(ps, region_real);
|
|
else
|
|
glXSwapBuffers(ps->dpy, get_tgt_window(ps));
|
|
break;
|
|
#endif
|
|
default:
|
|
assert(0);
|
|
}
|
|
glx_mark_frame(ps);
|
|
|
|
if (ps->o.vsync_aggressive)
|
|
vsync_wait(ps);
|
|
|
|
XFlush(ps->dpy);
|
|
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (glx_has_context(ps)) {
|
|
glFlush();
|
|
glXWaitX();
|
|
}
|
|
#endif
|
|
|
|
XFixesDestroyRegion(ps->dpy, region);
|
|
|
|
#ifdef DEBUG_REPAINT
|
|
print_timestamp(ps);
|
|
struct timespec now = get_time_timespec();
|
|
struct timespec diff = { 0 };
|
|
timespec_subtract(&diff, &now, &last_paint);
|
|
printf("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec);
|
|
last_paint = now;
|
|
printf("paint:");
|
|
for (win *w = t; w; w = w->prev_trans)
|
|
printf(" %#010lx", w->id);
|
|
putchar('\n');
|
|
fflush(stdout);
|
|
#endif
|
|
|
|
// Check if fading is finished on all painted windows
|
|
{
|
|
win *pprev = NULL;
|
|
for (win *w = t; w; w = pprev) {
|
|
pprev = w->prev_trans;
|
|
check_fade_fin(ps, w);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
add_damage(session_t *ps, XserverRegion damage) {
|
|
// Ignore damage when screen isn't redirected
|
|
if (!ps->redirected)
|
|
free_region(ps, &damage);
|
|
|
|
if (!damage) return;
|
|
if (ps->all_damage) {
|
|
XFixesUnionRegion(ps->dpy, ps->all_damage, ps->all_damage, damage);
|
|
XFixesDestroyRegion(ps->dpy, damage);
|
|
} else {
|
|
ps->all_damage = damage;
|
|
}
|
|
}
|
|
|
|
static void
|
|
repair_win(session_t *ps, win *w) {
|
|
if (IsViewable != w->a.map_state)
|
|
return;
|
|
|
|
XserverRegion parts;
|
|
|
|
if (!w->damaged) {
|
|
parts = win_extents(ps, w);
|
|
set_ignore_next(ps);
|
|
XDamageSubtract(ps->dpy, w->damage, None, None);
|
|
} else {
|
|
parts = XFixesCreateRegion(ps->dpy, 0, 0);
|
|
set_ignore_next(ps);
|
|
XDamageSubtract(ps->dpy, w->damage, None, parts);
|
|
XFixesTranslateRegion(ps->dpy, parts,
|
|
w->a.x + w->a.border_width,
|
|
w->a.y + w->a.border_width);
|
|
}
|
|
|
|
w->damaged = true;
|
|
w->pixmap_damaged = true;
|
|
|
|
// Why care about damage when screen is unredirected?
|
|
// We will force full-screen repaint on redirection.
|
|
if (!ps->redirected) return;
|
|
|
|
// Remove the part in the damage area that could be ignored
|
|
if (!ps->reg_ignore_expire && w->prev_trans && w->prev_trans->reg_ignore)
|
|
XFixesSubtractRegion(ps->dpy, parts, parts, w->prev_trans->reg_ignore);
|
|
|
|
add_damage(ps, parts);
|
|
}
|
|
|
|
static wintype_t
|
|
wid_get_prop_wintype(session_t *ps, Window wid) {
|
|
set_ignore_next(ps);
|
|
winprop_t prop = wid_get_prop(ps, wid, ps->atom_win_type, 32L, XA_ATOM, 32);
|
|
|
|
for (unsigned i = 0; i < prop.nitems; ++i) {
|
|
for (wintype_t j = 1; j < NUM_WINTYPES; ++j) {
|
|
if (ps->atoms_wintypes[j] == (Atom) prop.data.p32[i]) {
|
|
free_winprop(&prop);
|
|
return j;
|
|
}
|
|
}
|
|
}
|
|
|
|
free_winprop(&prop);
|
|
|
|
return WINTYPE_UNKNOWN;
|
|
}
|
|
|
|
static void
|
|
map_win(session_t *ps, Window id) {
|
|
// Unmap overlay window if it got mapped but we are currently not
|
|
// in redirected state.
|
|
if (ps->overlay && id == ps->overlay && !ps->redirected) {
|
|
XUnmapWindow(ps->dpy, ps->overlay);
|
|
XFlush(ps->dpy);
|
|
}
|
|
|
|
win *w = find_win(ps, id);
|
|
|
|
#ifdef DEBUG_EVENTS
|
|
printf_dbgf("(%#010lx \"%s\"): %p\n", id, (w ? w->name: NULL), w);
|
|
#endif
|
|
|
|
// Don't care about window mapping if it's an InputOnly window
|
|
// Try avoiding mapping a window twice
|
|
if (!w || InputOnly == w->a.class
|
|
|| IsViewable == w->a.map_state)
|
|
return;
|
|
|
|
assert(!win_is_focused_real(ps, w));
|
|
|
|
w->a.map_state = IsViewable;
|
|
|
|
cxinerama_win_upd_scr(ps, w);
|
|
|
|
// Call XSelectInput() before reading properties so that no property
|
|
// changes are lost
|
|
XSelectInput(ps->dpy, id, determine_evmask(ps, id, WIN_EVMODE_FRAME));
|
|
|
|
// Notify compton when the shape of a window changes
|
|
if (ps->shape_exists) {
|
|
XShapeSelectInput(ps->dpy, id, ShapeNotifyMask);
|
|
}
|
|
|
|
// Make sure the XSelectInput() requests are sent
|
|
XFlush(ps->dpy);
|
|
|
|
/* This needs to be here since we don't get PropertyNotify when unmapped */
|
|
calc_opacity(ps, w);
|
|
w->greyscale_blended_background = determine_window_transparency_filter_greyscale_blended(ps, id);
|
|
w->show_root_tile = determine_window_transparent_to_desktop(ps, id);
|
|
w->show_black_background = determine_window_transparent_to_black(ps, id);
|
|
win_determine_greyscale_background(ps, w);
|
|
|
|
// Update window mode here to check for ARGB windows
|
|
win_determine_mode(ps, w);
|
|
|
|
// Detect client window here instead of in add_win() as the client
|
|
// window should have been prepared at this point
|
|
if (!w->client_win) {
|
|
win_recheck_client(ps, w);
|
|
}
|
|
else {
|
|
// Re-mark client window here
|
|
win_mark_client(ps, w, w->client_win);
|
|
}
|
|
|
|
assert(w->client_win);
|
|
|
|
#ifdef DEBUG_WINTYPE
|
|
printf_dbgf("(%#010lx): type %s\n", w->id, WINTYPES[w->window_type]);
|
|
#endif
|
|
|
|
// Detect if the window is shaped or has rounded corners
|
|
win_update_shape_raw(ps, w);
|
|
|
|
// FocusIn/Out may be ignored when the window is unmapped, so we must
|
|
// recheck focus here
|
|
if (ps->o.track_focus)
|
|
recheck_focus(ps);
|
|
|
|
// Update window focus state
|
|
win_update_focused(ps, w);
|
|
|
|
// Update opacity and dim state
|
|
win_update_opacity_prop(ps, w);
|
|
w->flags |= WFLAG_OPCT_CHANGE;
|
|
|
|
// Check for _TDE_WM_WINDOW_SHADOW
|
|
if (ps->o.respect_prop_shadow)
|
|
win_update_prop_shadow_raw(ps, w);
|
|
|
|
// Many things above could affect shadow
|
|
win_determine_shadow(ps, w);
|
|
|
|
// Set fading state
|
|
w->in_openclose = true;
|
|
set_fade_callback(ps, w, finish_map_win, true);
|
|
win_determine_fade(ps, w);
|
|
|
|
win_determine_blur_background(ps, w);
|
|
|
|
w->damaged = false;
|
|
|
|
/* if any configure events happened while
|
|
the window was unmapped, then configure
|
|
the window to its correct place */
|
|
if (w->need_configure) {
|
|
configure_win(ps, &w->queue_configure);
|
|
}
|
|
|
|
#ifdef CONFIG_DBUS
|
|
// Send D-Bus signal
|
|
if (ps->o.dbus) {
|
|
cdbus_ev_win_mapped(ps, w);
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(%#010lx): map_win opacity: %u target: %u\n", w->id, w->opacity, w->opacity_tgt);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
finish_map_win(session_t *ps, win *w) {
|
|
w->in_openclose = false;
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(%#010lx)\n", w->id);
|
|
#endif
|
|
if (ps->o.no_fading_openclose) {
|
|
win_determine_fade(ps, w);
|
|
}
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(%#010lx): finish_map_win opacity: %u target: %u\n", w->id, w->opacity, w->opacity_tgt);
|
|
printf_dbgf("(%#010lx): end\n", w->id);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
finish_unmap_win(session_t *ps, win *w) {
|
|
w->damaged = false;
|
|
|
|
w->in_openclose = false;
|
|
|
|
update_reg_ignore_expire(ps, w);
|
|
|
|
if (w->extents != None) {
|
|
/* destroys region */
|
|
add_damage(ps, w->extents);
|
|
w->extents = None;
|
|
}
|
|
|
|
free_wpaint(ps, w);
|
|
free_region(ps, &w->border_size);
|
|
free_paint(ps, &w->shadow_paint);
|
|
}
|
|
|
|
static void
|
|
unmap_callback(session_t *ps, win *w) {
|
|
finish_unmap_win(ps, w);
|
|
}
|
|
|
|
static void
|
|
unmap_win(session_t *ps, win *w) {
|
|
if (!w || IsUnmapped == w->a.map_state) return;
|
|
|
|
// One last synchronization
|
|
if (w->paint.pixmap)
|
|
xr_sync(ps, w->paint.pixmap, &w->fence);
|
|
free_fence(ps, &w->fence);
|
|
|
|
// Set focus out
|
|
win_set_focused(ps, w, false);
|
|
|
|
w->a.map_state = IsUnmapped;
|
|
|
|
// Fading out
|
|
w->flags |= WFLAG_OPCT_CHANGE;
|
|
set_fade_callback(ps, w, unmap_callback, false);
|
|
w->in_openclose = true;
|
|
win_determine_fade(ps, w);
|
|
|
|
// Validate pixmap if we have to do fading
|
|
if (w->fade)
|
|
win_validate_pixmap(ps, w);
|
|
|
|
// don't care about properties anymore
|
|
win_ev_stop(ps, w);
|
|
|
|
#ifdef CONFIG_DBUS
|
|
// Send D-Bus signal
|
|
if (ps->o.dbus) {
|
|
cdbus_ev_win_unmapped(ps, w);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static opacity_t
|
|
wid_get_opacity_prop(session_t *ps, Window wid, opacity_t def) {
|
|
opacity_t val = def;
|
|
|
|
winprop_t prop = wid_get_prop(ps, wid, ps->atom_opacity, 1L,
|
|
XA_CARDINAL, 32);
|
|
|
|
if (prop.nitems)
|
|
val = *prop.data.p32;
|
|
|
|
free_winprop(&prop);
|
|
|
|
return val;
|
|
}
|
|
|
|
static double
|
|
get_opacity_percent(win *w) {
|
|
return ((double) w->opacity) / OPAQUE;
|
|
}
|
|
|
|
static Bool
|
|
get_window_transparency_filter_greyscale(const session_t *ps, Window w)
|
|
{
|
|
Atom actual;
|
|
int format;
|
|
unsigned long n, left;
|
|
|
|
unsigned char *data;
|
|
int result = XGetWindowProperty (ps->dpy, w, ps->atom_win_type_tde_transparency_filter_greyscale, 0L, 1L, False,
|
|
XA_ATOM, &actual, &format,
|
|
&n, &left, &data);
|
|
|
|
if (result == Success && data != None && format == 32 )
|
|
{
|
|
Atom a;
|
|
a = *(long*)data;
|
|
XFree ( (void *) data);
|
|
return True;
|
|
}
|
|
return False;
|
|
}
|
|
|
|
static Bool
|
|
get_window_transparency_filter_greyscale_blended(const session_t *ps, Window w)
|
|
{
|
|
Atom actual;
|
|
int format;
|
|
unsigned long n, left;
|
|
|
|
unsigned char *data;
|
|
int result = XGetWindowProperty (ps->dpy, w, ps->atom_win_type_tde_transparency_filter_greyscale_blend, 0L, 1L, False,
|
|
XA_ATOM, &actual, &format,
|
|
&n, &left, &data);
|
|
|
|
if (result == Success && data != None && format == 32 )
|
|
{
|
|
Atom a;
|
|
a = *(long*)data;
|
|
XFree ( (void *) data);
|
|
return True;
|
|
}
|
|
return False;
|
|
}
|
|
|
|
static Bool
|
|
get_window_transparent_to_desktop(const session_t *ps, Window w)
|
|
{
|
|
Atom actual;
|
|
int format;
|
|
unsigned long n, left;
|
|
|
|
unsigned char *data;
|
|
int result = XGetWindowProperty (ps->dpy, w, ps->atom_win_type_tde_transparent_to_desktop, 0L, 1L, False,
|
|
XA_ATOM, &actual, &format,
|
|
&n, &left, &data);
|
|
|
|
if (result == Success && data != None && format == 32 )
|
|
{
|
|
Atom a;
|
|
a = *(long*)data;
|
|
XFree ( (void *) data);
|
|
return True;
|
|
}
|
|
return False;
|
|
}
|
|
|
|
static Bool
|
|
get_window_transparent_to_black(const session_t *ps, Window w)
|
|
{
|
|
Atom actual;
|
|
int format;
|
|
unsigned long n, left;
|
|
|
|
unsigned char *data;
|
|
int result = XGetWindowProperty (ps->dpy, w, ps->atom_win_type_tde_transparent_to_black, 0L, 1L, False,
|
|
XA_ATOM, &actual, &format,
|
|
&n, &left, &data);
|
|
|
|
if (result == Success && data != None && format == 32 )
|
|
{
|
|
Atom a;
|
|
a = *(long*)data;
|
|
XFree ( (void *) data);
|
|
return True;
|
|
}
|
|
return False;
|
|
}
|
|
|
|
static Bool
|
|
determine_window_transparency_filter_greyscale (const session_t *ps, Window w)
|
|
{
|
|
Window root_return, parent_return;
|
|
Window *children = NULL;
|
|
unsigned int nchildren, i;
|
|
Bool type;
|
|
|
|
type = get_window_transparency_filter_greyscale (ps, w);
|
|
if (type == True) {
|
|
return True;
|
|
}
|
|
|
|
if (!XQueryTree (ps->dpy, w, &root_return, &parent_return, &children,
|
|
&nchildren))
|
|
{
|
|
/* XQueryTree failed. */
|
|
if (children)
|
|
XFree ((void *)children);
|
|
return False;
|
|
}
|
|
|
|
for (i = 0;i < nchildren;i++)
|
|
{
|
|
type = determine_window_transparency_filter_greyscale (ps, children[i]);
|
|
if (type == True)
|
|
return True;
|
|
}
|
|
|
|
if (children)
|
|
XFree ((void *)children);
|
|
|
|
return False;
|
|
}
|
|
|
|
static Bool
|
|
determine_window_transparency_filter_greyscale_blended (const session_t *ps, Window w)
|
|
{
|
|
Window root_return, parent_return;
|
|
Window *children = NULL;
|
|
unsigned int nchildren, i;
|
|
Bool type;
|
|
|
|
type = get_window_transparency_filter_greyscale_blended (ps, w);
|
|
if (type == True) {
|
|
return True;
|
|
}
|
|
|
|
if (!XQueryTree (ps->dpy, w, &root_return, &parent_return, &children,
|
|
&nchildren))
|
|
{
|
|
/* XQueryTree failed. */
|
|
if (children)
|
|
XFree ((void *)children);
|
|
return False;
|
|
}
|
|
|
|
for (i = 0;i < nchildren;i++)
|
|
{
|
|
type = determine_window_transparency_filter_greyscale_blended (ps, children[i]);
|
|
if (type == True)
|
|
return True;
|
|
}
|
|
|
|
if (children)
|
|
XFree ((void *)children);
|
|
|
|
return False;
|
|
}
|
|
|
|
static Bool
|
|
determine_window_transparent_to_desktop (const session_t *ps, Window w)
|
|
{
|
|
Window root_return, parent_return;
|
|
Window *children = NULL;
|
|
unsigned int nchildren, i;
|
|
Bool type;
|
|
|
|
type = get_window_transparent_to_desktop (ps, w);
|
|
if (type == True) {
|
|
return True;
|
|
}
|
|
|
|
if (!XQueryTree (ps->dpy, w, &root_return, &parent_return, &children,
|
|
&nchildren))
|
|
{
|
|
/* XQueryTree failed. */
|
|
if (children)
|
|
XFree ((void *)children);
|
|
return False;
|
|
}
|
|
|
|
for (i = 0;i < nchildren;i++)
|
|
{
|
|
type = determine_window_transparent_to_desktop (ps, children[i]);
|
|
if (type == True)
|
|
return True;
|
|
}
|
|
|
|
if (children)
|
|
XFree ((void *)children);
|
|
|
|
return False;
|
|
}
|
|
|
|
static Bool
|
|
determine_window_transparent_to_black (const session_t *ps, Window w)
|
|
{
|
|
Window root_return, parent_return;
|
|
Window *children = NULL;
|
|
unsigned int nchildren, i;
|
|
Bool type;
|
|
Bool ret = False;
|
|
|
|
type = get_window_transparent_to_black (ps, w);
|
|
if (type == True) {
|
|
return True;
|
|
}
|
|
|
|
if (!XQueryTree (ps->dpy, w, &root_return, &parent_return, &children,
|
|
&nchildren))
|
|
{
|
|
/* XQueryTree failed. */
|
|
if (children) {
|
|
XFree ((void *)children);
|
|
}
|
|
return False;
|
|
}
|
|
|
|
for (i = 0;i < nchildren;i++)
|
|
{
|
|
type = determine_window_transparent_to_black (ps, children[i]);
|
|
if (type == True) {
|
|
ret = True;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (children) {
|
|
XFree ((void *)children);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
win_determine_mode(session_t *ps, win *w) {
|
|
winmode_t mode = WMODE_SOLID;
|
|
|
|
if (w->pictfmt && w->pictfmt->type == PictTypeDirect
|
|
&& w->pictfmt->direct.alphaMask) {
|
|
mode = WMODE_ARGB;
|
|
} else if (w->opacity != OPAQUE) {
|
|
mode = WMODE_TRANS;
|
|
} else {
|
|
mode = WMODE_SOLID;
|
|
}
|
|
|
|
w->mode = mode;
|
|
}
|
|
|
|
/**
|
|
* Calculate and set the opacity of a window.
|
|
*
|
|
* If window is inactive and inactive_opacity_override is set, the
|
|
* priority is: (Simulates the old behavior)
|
|
*
|
|
* inactive_opacity > _NET_WM_WINDOW_OPACITY (if not opaque)
|
|
* > window type default opacity
|
|
*
|
|
* Otherwise:
|
|
*
|
|
* _NET_WM_WINDOW_OPACITY (if not opaque)
|
|
* > window type default opacity (if not opaque)
|
|
* > inactive_opacity
|
|
*
|
|
* @param ps current session
|
|
* @param w struct _win object representing the window
|
|
*/
|
|
static void
|
|
calc_opacity(session_t *ps, win *w) {
|
|
opacity_t opacity = OPAQUE;
|
|
|
|
if (w->destroyed || IsViewable != w->a.map_state) {
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(%#010lx): calc_opacity forcing full transparency\n");
|
|
#endif
|
|
opacity = 0;
|
|
}
|
|
else {
|
|
// Try obeying opacity property and window type opacity firstly
|
|
if (OPAQUE == (opacity = w->opacity_prop)
|
|
&& OPAQUE == (opacity = w->opacity_prop_client)) {
|
|
opacity = ps->o.wintype_opacity[w->window_type] * OPAQUE;
|
|
}
|
|
|
|
// Respect inactive_opacity in some cases
|
|
if (ps->o.inactive_opacity && false == w->focused
|
|
&& (OPAQUE == opacity || ps->o.inactive_opacity_override)) {
|
|
opacity = ps->o.inactive_opacity;
|
|
}
|
|
|
|
// Respect active_opacity only when the window is physically focused
|
|
if (OPAQUE == opacity && ps->o.active_opacity && win_is_focused_real(ps, w))
|
|
opacity = ps->o.active_opacity;
|
|
}
|
|
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(%#010lx): calc_opacity opacity: %u\n", w->id, opacity);
|
|
#endif
|
|
w->opacity_tgt = opacity;
|
|
}
|
|
|
|
/**
|
|
* Determine whether a window is to be dimmed.
|
|
*/
|
|
static void
|
|
calc_dim(session_t *ps, win *w) {
|
|
bool dim;
|
|
|
|
// Make sure we do nothing if the window is unmapped / destroyed
|
|
if (w->destroyed || IsViewable != w->a.map_state)
|
|
return;
|
|
|
|
if (ps->o.inactive_dim && !(w->focused)) {
|
|
dim = true;
|
|
} else {
|
|
dim = false;
|
|
}
|
|
|
|
if (dim != w->dim) {
|
|
w->dim = dim;
|
|
add_damage_win(ps, w);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine if a window should fade on opacity change.
|
|
*/
|
|
static void
|
|
win_determine_fade(session_t *ps, win *w) {
|
|
if (UNSET != w->fade_force) {
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(%#010lx): fade forced\n", w->id);
|
|
#endif
|
|
w->fade = w->fade_force;
|
|
}
|
|
else if (ps->o.no_fading_openclose && (w->in_openclose || w->destroyed)) {
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(): no_fading_openclose and in_openclose\n");
|
|
#endif
|
|
w->fade = false;
|
|
}
|
|
else if (ps->o.no_fading_opacitychange && (!w->in_openclose)) {
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(): no_fading_opacitychange and !in_openclose\n");
|
|
#endif
|
|
w->fade = false;
|
|
}
|
|
else if (ps->o.no_fading_destroyed_argb && w->destroyed
|
|
&& WMODE_ARGB == w->mode && w->client_win && w->client_win != w->id) {
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(): no_fading_destroyed_argb\n");
|
|
#endif
|
|
w->fade = false;
|
|
// Prevent it from being overwritten by last-paint value
|
|
w->fade_last = false;
|
|
}
|
|
// Ignore other possible causes of fading state changes after window
|
|
// gets unmapped
|
|
else if (IsViewable != w->a.map_state) {
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(): ignored: !IsViewable\n");
|
|
#endif
|
|
}
|
|
else if (win_match(ps, w, ps->o.fade_blacklist, &w->cache_fblst)) {
|
|
|
|
w->fade = false;
|
|
}
|
|
else if (ps->o.wintype_fade[w->window_type] != NULL) {
|
|
w->fade = ps->o.wintype_fade[w->window_type];
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(%#010lx): via wintype_fade[%s]\n",
|
|
w->id, WINTYPES[w->window_type]);
|
|
#endif
|
|
} else {
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(): fallthrough\n");
|
|
#endif
|
|
}
|
|
|
|
#ifdef DEBUG_FADE
|
|
printf_dbgf("(%#010lx): fade = %d\n", w->id, w->fade ? w->fade : NULL);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Update window-shape.
|
|
*/
|
|
static void
|
|
win_update_shape_raw(session_t *ps, win *w) {
|
|
if (ps->shape_exists) {
|
|
w->bounding_shaped = wid_bounding_shaped(ps, w->id);
|
|
if (w->bounding_shaped && ps->o.detect_rounded_corners)
|
|
win_rounded_corners(ps, w);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update window-shape related information.
|
|
*/
|
|
static void
|
|
win_update_shape(session_t *ps, win *w) {
|
|
if (ps->shape_exists) {
|
|
// bool bounding_shaped_old = w->bounding_shaped;
|
|
|
|
win_update_shape_raw(ps, w);
|
|
|
|
win_on_factor_change(ps, w);
|
|
|
|
/*
|
|
// If clear_shadow state on the window possibly changed, destroy the old
|
|
// shadow_pict
|
|
if (ps->o.clear_shadow && w->bounding_shaped != bounding_shaped_old)
|
|
free_paint(ps, &w->shadow_paint);
|
|
*/
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reread _TDE_WM_WINDOW_SHADOW property from a window.
|
|
*
|
|
* The property must be set on the outermost window, usually the WM frame.
|
|
*/
|
|
static void
|
|
win_update_prop_shadow_raw(session_t *ps, win *w) {
|
|
winprop_t prop = wid_get_prop(ps, w->id, ps->atom_compton_shadow, 1,
|
|
XA_CARDINAL, 32);
|
|
|
|
if (!prop.nitems) {
|
|
w->prop_shadow = -1;
|
|
}
|
|
else {
|
|
w->prop_shadow = *prop.data.p32;
|
|
}
|
|
|
|
free_winprop(&prop);
|
|
}
|
|
|
|
/**
|
|
* Reread _TDE_WM_WINDOW_SHADOW property from a window and update related
|
|
* things.
|
|
*/
|
|
static void
|
|
win_update_prop_shadow(session_t *ps, win *w) {
|
|
long attr_shadow_old = w->prop_shadow;
|
|
|
|
win_update_prop_shadow_raw(ps, w);
|
|
|
|
if (w->prop_shadow != attr_shadow_old)
|
|
win_determine_shadow(ps, w);
|
|
}
|
|
|
|
static void
|
|
win_set_shadow(session_t *ps, win *w, bool shadow_new) {
|
|
if (w->shadow == shadow_new) return;
|
|
|
|
w->shadow = shadow_new;
|
|
|
|
// Window extents need update on shadow state change
|
|
// Shadow geometry currently doesn't change on shadow state change
|
|
// calc_shadow_geometry(ps, w);
|
|
if (w->extents) {
|
|
// Mark the old extents as damaged if the shadow is removed
|
|
if (!w->shadow)
|
|
add_damage(ps, w->extents);
|
|
else
|
|
free_region(ps, &w->extents);
|
|
w->extents = win_extents(ps, w);
|
|
// Mark the new extents as damaged if the shadow is added
|
|
if (w->shadow)
|
|
add_damage_win(ps, w);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine if a window should have shadow, and update things depending
|
|
* on shadow state.
|
|
*/
|
|
static void
|
|
win_determine_shadow(session_t *ps, win *w) {
|
|
bool shadow_new = w->shadow;
|
|
|
|
if (UNSET != w->shadow_force)
|
|
shadow_new = w->shadow_force;
|
|
else if (IsViewable == w->a.map_state)
|
|
shadow_new = (ps->o.wintype_shadow[w->window_type]
|
|
&& !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));
|
|
|
|
win_set_shadow(ps, w, shadow_new);
|
|
}
|
|
|
|
static void
|
|
win_set_invert_color(session_t *ps, win *w, bool invert_color_new) {
|
|
if (w->invert_color == invert_color_new) return;
|
|
|
|
w->invert_color = invert_color_new;
|
|
|
|
add_damage_win(ps, w);
|
|
}
|
|
|
|
/**
|
|
* Determine if a window should have color inverted.
|
|
*/
|
|
static void
|
|
win_determine_invert_color(session_t *ps, win *w) {
|
|
bool invert_color_new = w->invert_color;
|
|
|
|
if (UNSET != w->invert_color_force)
|
|
invert_color_new = w->invert_color_force;
|
|
else if (IsViewable == w->a.map_state)
|
|
invert_color_new = win_match(ps, w, ps->o.invert_color_list,
|
|
&w->cache_ivclst);
|
|
|
|
win_set_invert_color(ps, w, invert_color_new);
|
|
}
|
|
|
|
static void
|
|
win_set_blur_background(session_t *ps, win *w, bool blur_background_new) {
|
|
if (w->blur_background == blur_background_new) return;
|
|
|
|
w->blur_background = blur_background_new;
|
|
|
|
// Only consider window damaged if it's previously painted with background
|
|
// blurred
|
|
if (!win_is_solid(ps, w)
|
|
|| (ps->o.blur_background_frame && w->frame_opacity))
|
|
add_damage_win(ps, w);
|
|
}
|
|
|
|
/**
|
|
* Determine if a window should have background blurred.
|
|
*/
|
|
static void
|
|
win_determine_blur_background(session_t *ps, win *w) {
|
|
if (IsViewable != w->a.map_state)
|
|
return;
|
|
|
|
bool blur_background_new = ps->o.blur_background
|
|
&& !win_match(ps, w, ps->o.blur_background_blacklist, &w->cache_bbblst);
|
|
|
|
win_set_blur_background(ps, w, blur_background_new);
|
|
}
|
|
|
|
static void
|
|
win_set_greyscale_background(session_t *ps, win *w, bool greyscale_background_new) {
|
|
if (w->greyscale_background == greyscale_background_new) return;
|
|
|
|
w->greyscale_background = greyscale_background_new;
|
|
|
|
// Only consider window damaged if it's previously painted with background
|
|
// set to greyscale
|
|
if (!win_is_solid(ps, w))
|
|
add_damage_win(ps, w);
|
|
}
|
|
|
|
/**
|
|
* Determine if a window should have a greyscale background.
|
|
*/
|
|
static void
|
|
win_determine_greyscale_background(session_t *ps, win *w) {
|
|
if (IsViewable != w->a.map_state)
|
|
return;
|
|
|
|
bool greyscale_background_new = (determine_window_transparency_filter_greyscale(ps, &w->id) ||
|
|
(ps->o.greyscale_background && !win_match(ps, w, ps->o.greyscale_background_blacklist, &w->cache_bbblst)));
|
|
|
|
win_set_greyscale_background(ps, w, greyscale_background_new);
|
|
}
|
|
|
|
/**
|
|
* Update window opacity according to opacity rules.
|
|
*/
|
|
static void
|
|
win_update_opacity_rule(session_t *ps, win *w) {
|
|
if (IsViewable != w->a.map_state)
|
|
return;
|
|
|
|
#ifdef CONFIG_C2
|
|
// If long is 32-bit, unfortunately there's no way could we express "unset",
|
|
// so we just entirely don't distinguish "unset" and OPAQUE
|
|
opacity_t opacity = OPAQUE;
|
|
void *val = NULL;
|
|
if (c2_matchd(ps, w, ps->o.opacity_rules, &w->cache_oparule, &val))
|
|
opacity = ((double) (long) val) / 100.0 * OPAQUE;
|
|
|
|
if (opacity == w->opacity_set)
|
|
return;
|
|
|
|
if (OPAQUE != opacity)
|
|
wid_set_opacity_prop(ps, w->id, opacity);
|
|
else if (OPAQUE != w->opacity_set)
|
|
wid_rm_opacity_prop(ps, w->id);
|
|
w->opacity_set = opacity;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Function to be called on window type changes.
|
|
*/
|
|
static void
|
|
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);
|
|
if (ps->o.opacity_rules)
|
|
win_update_opacity_rule(ps, w);
|
|
}
|
|
|
|
/**
|
|
* Function to be called on window data changes.
|
|
*/
|
|
static void
|
|
win_on_factor_change(session_t *ps, win *w) {
|
|
if (ps->o.shadow_blacklist)
|
|
win_determine_shadow(ps, w);
|
|
if (ps->o.fade_blacklist)
|
|
win_determine_fade(ps, w);
|
|
if (ps->o.invert_color_list)
|
|
win_determine_invert_color(ps, w);
|
|
if (ps->o.focus_blacklist)
|
|
win_update_focused(ps, w);
|
|
if (ps->o.blur_background_blacklist)
|
|
win_determine_blur_background(ps, w);
|
|
if (ps->o.greyscale_background_blacklist)
|
|
win_determine_greyscale_background(ps, w);
|
|
if (ps->o.opacity_rules)
|
|
win_update_opacity_rule(ps, w);
|
|
if (IsViewable == w->a.map_state && ps->o.paint_blacklist)
|
|
w->paint_excluded = win_match(ps, w, ps->o.paint_blacklist,
|
|
&w->cache_pblst);
|
|
if (IsViewable == w->a.map_state && ps->o.unredir_if_possible_blacklist)
|
|
w->unredir_if_possible_excluded = win_match(ps, w,
|
|
ps->o.unredir_if_possible_blacklist, &w->cache_uipblst);
|
|
}
|
|
|
|
/**
|
|
* Process needed window updates.
|
|
*/
|
|
static void
|
|
win_upd_run(session_t *ps, win *w, win_upd_t *pupd) {
|
|
if (pupd->shadow) {
|
|
win_determine_shadow(ps, w);
|
|
pupd->shadow = false;
|
|
}
|
|
if (pupd->fade) {
|
|
win_determine_fade(ps, w);
|
|
pupd->fade = false;
|
|
}
|
|
if (pupd->invert_color) {
|
|
win_determine_invert_color(ps, w);
|
|
pupd->invert_color = false;
|
|
}
|
|
if (pupd->focus) {
|
|
win_update_focused(ps, w);
|
|
pupd->focus = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update cache data in struct _win that depends on window size.
|
|
*/
|
|
static void
|
|
calc_win_size(session_t *ps, win *w) {
|
|
w->widthb = w->a.width + w->a.border_width * 2;
|
|
w->heightb = w->a.height + w->a.border_width * 2;
|
|
calc_shadow_geometry(ps, w);
|
|
w->flags |= WFLAG_SIZE_CHANGE;
|
|
}
|
|
|
|
/**
|
|
* Calculate and update geometry of the shadow of a window.
|
|
*/
|
|
static void
|
|
calc_shadow_geometry(session_t *ps, win *w) {
|
|
static const int shadowRadius = 100;
|
|
w->shadow_dx = (((- ps->o.shadow_radius * 7 / 5) - ps->o.shadow_offset_x * shadowRadius / 100) * w->shadow_size) / 100;
|
|
w->shadow_dy = (((- ps->o.shadow_radius * 7 / 5) - ps->o.shadow_offset_y * shadowRadius / 100) * w->shadow_size) / 100;
|
|
w->shadow_width = w->widthb + ps->gaussian_map->size;
|
|
w->shadow_height = w->heightb + ps->gaussian_map->size;
|
|
}
|
|
|
|
/**
|
|
* Update window type.
|
|
*/
|
|
static void
|
|
win_upd_wintype(session_t *ps, win *w) {
|
|
const wintype_t wtype_old = w->window_type;
|
|
|
|
// Detect window type here
|
|
w->window_type = wid_get_prop_wintype(ps, w->client_win);
|
|
|
|
// Conform to EWMH standard, if _NET_WM_WINDOW_TYPE is not present, take
|
|
// override-redirect windows or windows without WM_TRANSIENT_FOR as
|
|
// _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG.
|
|
if (WINTYPE_UNKNOWN == w->window_type) {
|
|
if (w->a.override_redirect
|
|
|| !wid_has_prop(ps, w->client_win, ps->atom_transient))
|
|
w->window_type = WINTYPE_NORMAL;
|
|
else
|
|
w->window_type = WINTYPE_DIALOG;
|
|
}
|
|
|
|
if (w->window_type != wtype_old)
|
|
win_on_wtype_change(ps, w);
|
|
}
|
|
|
|
/**
|
|
* Mark a window as the client window of another.
|
|
*
|
|
* @param ps current session
|
|
* @param w struct _win of the parent window
|
|
* @param client window ID of the client window
|
|
*/
|
|
static void
|
|
win_mark_client(session_t *ps, win *w, Window client) {
|
|
w->client_win = client;
|
|
|
|
// If the window isn't mapped yet, stop here, as the function will be
|
|
// called in map_win()
|
|
if (IsViewable != w->a.map_state)
|
|
return;
|
|
|
|
XSelectInput(ps->dpy, client,
|
|
determine_evmask(ps, client, WIN_EVMODE_CLIENT));
|
|
|
|
// Make sure the XSelectInput() requests are sent
|
|
XFlush(ps->dpy);
|
|
|
|
win_upd_wintype(ps, w);
|
|
|
|
// Get frame widths. The window is in damaged area already.
|
|
if (ps->o.frame_opacity)
|
|
get_frame_extents(ps, w, client);
|
|
|
|
// Get window group
|
|
if (ps->o.track_leader)
|
|
win_update_leader(ps, w);
|
|
|
|
// Get window name and class if we are tracking them
|
|
if (ps->o.track_wdata) {
|
|
win_get_name(ps, w);
|
|
win_get_class(ps, w);
|
|
win_get_role(ps, w);
|
|
}
|
|
|
|
// Update everything related to conditions
|
|
win_on_factor_change(ps, w);
|
|
|
|
// Update window focus state
|
|
win_update_focused(ps, w);
|
|
}
|
|
|
|
/**
|
|
* Unmark current client window of a window.
|
|
*
|
|
* @param ps current session
|
|
* @param w struct _win of the parent window
|
|
*/
|
|
static void
|
|
win_unmark_client(session_t *ps, win *w) {
|
|
Window client = w->client_win;
|
|
|
|
w->client_win = None;
|
|
|
|
// Recheck event mask
|
|
XSelectInput(ps->dpy, client,
|
|
determine_evmask(ps, client, WIN_EVMODE_UNKNOWN));
|
|
}
|
|
|
|
/**
|
|
* Recheck client window of a window.
|
|
*
|
|
* @param ps current session
|
|
* @param w struct _win of the parent window
|
|
*/
|
|
static void
|
|
win_recheck_client(session_t *ps, win *w) {
|
|
// Initialize wmwin to false
|
|
w->wmwin = false;
|
|
|
|
// Look for the client window
|
|
|
|
// Always recursively look for a window with WM_STATE, as Fluxbox
|
|
// sets override-redirect flags on all frame windows.
|
|
Window cw = find_client_win(ps, w->id);
|
|
#ifdef DEBUG_CLIENTWIN
|
|
if (cw)
|
|
printf_dbgf("(%#010lx): client %#010lx\n", w->id, cw);
|
|
#endif
|
|
// Set a window's client window to itself if we couldn't find a
|
|
// client window
|
|
if (!cw) {
|
|
cw = w->id;
|
|
w->wmwin = !w->a.override_redirect;
|
|
#ifdef DEBUG_CLIENTWIN
|
|
printf_dbgf("(%#010lx): client self (%s)\n", w->id,
|
|
(w->wmwin ? "wmwin": "override-redirected"));
|
|
#endif
|
|
}
|
|
|
|
// Unmark the old one
|
|
if (w->client_win && w->client_win != cw)
|
|
win_unmark_client(ps, w);
|
|
|
|
// Mark the new one
|
|
win_mark_client(ps, w, cw);
|
|
}
|
|
|
|
static bool
|
|
add_win(session_t *ps, Window id, Window prev) {
|
|
const static win win_def = {
|
|
.next = NULL,
|
|
.prev_trans = NULL,
|
|
|
|
.id = None,
|
|
.a = { },
|
|
#ifdef CONFIG_XINERAMA
|
|
.xinerama_scr = -1,
|
|
#endif
|
|
.pictfmt = NULL,
|
|
.mode = WMODE_TRANS,
|
|
.damaged = false,
|
|
.damage = None,
|
|
.pixmap_damaged = false,
|
|
.paint = PAINT_INIT,
|
|
.border_size = None,
|
|
.extents = None,
|
|
.flags = 0,
|
|
.need_configure = false,
|
|
.queue_configure = { },
|
|
.reg_ignore = None,
|
|
.widthb = 0,
|
|
.heightb = 0,
|
|
.destroyed = false,
|
|
.bounding_shaped = false,
|
|
.rounded_corners = false,
|
|
.to_paint = false,
|
|
.in_openclose = false,
|
|
|
|
.client_win = None,
|
|
.window_type = WINTYPE_UNKNOWN,
|
|
.wmwin = false,
|
|
.leader = None,
|
|
.cache_leader = None,
|
|
|
|
.focused = false,
|
|
.focused_force = UNSET,
|
|
|
|
.name = NULL,
|
|
.class_instance = NULL,
|
|
.class_general = NULL,
|
|
.role = NULL,
|
|
.cache_sblst = NULL,
|
|
.cache_fblst = NULL,
|
|
.cache_fcblst = NULL,
|
|
.cache_ivclst = NULL,
|
|
.cache_bbblst = NULL,
|
|
.cache_oparule = NULL,
|
|
|
|
.opacity = 0,
|
|
.opacity_tgt = 0,
|
|
.opacity_prop = OPAQUE,
|
|
.opacity_prop_client = OPAQUE,
|
|
.opacity_set = OPAQUE,
|
|
|
|
.fade = false,
|
|
.fade_force = UNSET,
|
|
.fade_callback = NULL,
|
|
|
|
.frame_opacity = 0.0,
|
|
.left_width = 0,
|
|
.right_width = 0,
|
|
.top_width = 0,
|
|
.bottom_width = 0,
|
|
|
|
.shadow = false,
|
|
.shadow_force = UNSET,
|
|
.shadow_opacity = 0.0,
|
|
.shadow_dx = 0,
|
|
.shadow_dy = 0,
|
|
.shadow_width = 0,
|
|
.shadow_height = 0,
|
|
.shadow_size = 100,
|
|
.shadow_paint = PAINT_INIT,
|
|
.prop_shadow = -1,
|
|
|
|
.dim = false,
|
|
|
|
.invert_color = false,
|
|
.invert_color_force = UNSET,
|
|
|
|
.blur_background = false,
|
|
.greyscale_background = false,
|
|
.greyscale_blended_background = false,
|
|
.greyscale_blended_background_alpha_divisor = 2,
|
|
|
|
.show_black_background = false,
|
|
.show_root_tile = false,
|
|
};
|
|
|
|
// Reject overlay window and already added windows
|
|
if (id == ps->overlay || find_win(ps, id)) {
|
|
return false;
|
|
}
|
|
|
|
// Allocate and initialize the new win structure
|
|
win *new = malloc(sizeof(win));
|
|
|
|
#ifdef DEBUG_EVENTS
|
|
printf_dbgf("(%#010lx): %p\n", id, new);
|
|
#endif
|
|
|
|
if (!new) {
|
|
printf_errf("(%#010lx): Failed to allocate memory for the new window.", id);
|
|
return false;
|
|
}
|
|
|
|
memcpy(new, &win_def, sizeof(win));
|
|
|
|
// Find window insertion point
|
|
win **p = NULL;
|
|
if (prev) {
|
|
for (p = &ps->list; *p; p = &(*p)->next) {
|
|
if ((*p)->id == prev && !(*p)->destroyed)
|
|
break;
|
|
}
|
|
} else {
|
|
p = &ps->list;
|
|
}
|
|
|
|
// Fill structure
|
|
new->id = id;
|
|
|
|
set_ignore_next(ps);
|
|
if (!XGetWindowAttributes(ps->dpy, id, &new->a)
|
|
|| IsUnviewable == new->a.map_state) {
|
|
// Failed to get window attributes probably means the window is gone
|
|
// already. IsUnviewable means the window is already reparented
|
|
// elsewhere.
|
|
free(new);
|
|
return false;
|
|
}
|
|
|
|
// Delay window mapping
|
|
int map_state = new->a.map_state;
|
|
assert(IsViewable == map_state || IsUnmapped == map_state);
|
|
new->a.map_state = IsUnmapped;
|
|
|
|
if (InputOutput == new->a.class) {
|
|
// Get window picture format
|
|
new->pictfmt = XRenderFindVisualFormat(ps->dpy, new->a.visual);
|
|
|
|
// Create Damage for window
|
|
set_ignore_next(ps);
|
|
new->damage = XDamageCreate(ps->dpy, id, XDamageReportNonEmpty);
|
|
}
|
|
|
|
calc_win_size(ps, new);
|
|
|
|
new->next = *p;
|
|
*p = new;
|
|
|
|
#ifdef CONFIG_DBUS
|
|
// Send D-Bus signal
|
|
if (ps->o.dbus) {
|
|
cdbus_ev_win_added(ps, new);
|
|
}
|
|
#endif
|
|
|
|
if (IsViewable == map_state) {
|
|
map_win(ps, id);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
restack_win(session_t *ps, win *w, Window new_above) {
|
|
Window old_above;
|
|
|
|
update_reg_ignore_expire(ps, w);
|
|
|
|
if (w->next) {
|
|
old_above = w->next->id;
|
|
} else {
|
|
old_above = None;
|
|
}
|
|
|
|
if (old_above != new_above) {
|
|
win **prev = NULL, **prev_old = NULL;
|
|
|
|
// unhook
|
|
for (prev = &ps->list; *prev; prev = &(*prev)->next) {
|
|
if ((*prev) == w) break;
|
|
}
|
|
|
|
prev_old = prev;
|
|
|
|
bool found = false;
|
|
|
|
// rehook
|
|
for (prev = &ps->list; *prev; prev = &(*prev)->next) {
|
|
if ((*prev)->id == new_above && !(*prev)->destroyed) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (new_above && !found) {
|
|
printf_errf("(%#010lx, %#010lx): "
|
|
"Failed to found new above window.", w->id, new_above);
|
|
return;
|
|
}
|
|
|
|
*prev_old = w->next;
|
|
|
|
w->next = *prev;
|
|
*prev = w;
|
|
|
|
#ifdef DEBUG_RESTACK
|
|
{
|
|
const char *desc;
|
|
char *window_name = NULL;
|
|
bool to_free;
|
|
win* c = ps->list;
|
|
|
|
printf_dbgf("(%#010lx, %#010lx): "
|
|
"Window stack modified. Current stack:\n", w->id, new_above);
|
|
|
|
for (; c; c = c->next) {
|
|
window_name = "(Failed to get title)";
|
|
|
|
to_free = ev_window_name(ps, c->id, &window_name);
|
|
|
|
desc = "";
|
|
if (c->destroyed) desc = "(D) ";
|
|
printf("%#010lx \"%s\" %s", c->id, window_name, desc);
|
|
if (c->next)
|
|
printf("-> ");
|
|
|
|
if (to_free) {
|
|
cxfree(window_name);
|
|
window_name = NULL;
|
|
}
|
|
}
|
|
fputs("\n", stdout);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static bool
|
|
init_filters(session_t *ps);
|
|
|
|
static void
|
|
configure_win(session_t *ps, XConfigureEvent *ce) {
|
|
// On root window changes
|
|
if (ce->window == ps->root) {
|
|
free_paint(ps, &ps->tgt_buffer);
|
|
|
|
ps->root_width = ce->width;
|
|
ps->root_height = ce->height;
|
|
|
|
rebuild_screen_reg(ps);
|
|
rebuild_shadow_exclude_reg(ps);
|
|
free_all_damage_last(ps);
|
|
|
|
// Re-redirect screen if required
|
|
if (ps->o.reredir_on_root_change && ps->redirected) {
|
|
redir_stop(ps);
|
|
redir_start(ps);
|
|
}
|
|
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
// Reinitialize GLX on root change
|
|
if (ps->o.glx_reinit_on_root_change && ps->psglx) {
|
|
if (!glx_reinit(ps, bkend_use_glx(ps)))
|
|
printf_errf("(): Failed to reinitialize GLX, troubles ahead.");
|
|
if (BKEND_GLX == ps->o.backend && !init_filters(ps))
|
|
printf_errf("(): Failed to initialize filters.");
|
|
}
|
|
|
|
// GLX root change callback
|
|
if (BKEND_GLX == ps->o.backend)
|
|
glx_on_root_change(ps);
|
|
#endif
|
|
|
|
force_repaint(ps);
|
|
|
|
return;
|
|
}
|
|
|
|
// Other window changes
|
|
win *w = find_win(ps, ce->window);
|
|
XserverRegion damage = None;
|
|
|
|
if (!w)
|
|
return;
|
|
|
|
if (w->a.map_state == IsUnmapped) {
|
|
/* save the configure event for when the window maps */
|
|
w->need_configure = true;
|
|
w->queue_configure = *ce;
|
|
restack_win(ps, w, ce->above);
|
|
} else {
|
|
if (!(w->need_configure)) {
|
|
restack_win(ps, w, ce->above);
|
|
}
|
|
|
|
bool factor_change = false;
|
|
|
|
// Windows restack (including window restacks happened when this
|
|
// window is not mapped) could mess up all reg_ignore
|
|
ps->reg_ignore_expire = true;
|
|
|
|
w->need_configure = false;
|
|
|
|
damage = XFixesCreateRegion(ps->dpy, 0, 0);
|
|
if (w->extents != None) {
|
|
XFixesCopyRegion(ps->dpy, damage, w->extents);
|
|
}
|
|
|
|
// If window geometry did not change, don't free extents here
|
|
if (w->a.x != ce->x || w->a.y != ce->y
|
|
|| w->a.width != ce->width || w->a.height != ce->height
|
|
|| w->a.border_width != ce->border_width) {
|
|
factor_change = true;
|
|
free_region(ps, &w->extents);
|
|
free_region(ps, &w->border_size);
|
|
}
|
|
|
|
w->a.x = ce->x;
|
|
w->a.y = ce->y;
|
|
|
|
if (w->a.width != ce->width || w->a.height != ce->height
|
|
|| w->a.border_width != ce->border_width)
|
|
free_wpaint(ps, w);
|
|
|
|
if (w->a.width != ce->width || w->a.height != ce->height
|
|
|| w->a.border_width != ce->border_width) {
|
|
w->a.width = ce->width;
|
|
w->a.height = ce->height;
|
|
w->a.border_width = ce->border_width;
|
|
calc_win_size(ps, w);
|
|
|
|
// Rounded corner detection is affected by window size
|
|
if (ps->shape_exists && ps->o.shadow_ignore_shaped
|
|
&& ps->o.detect_rounded_corners && w->bounding_shaped)
|
|
win_update_shape(ps, w);
|
|
}
|
|
|
|
if (damage) {
|
|
XserverRegion extents = win_extents(ps, w);
|
|
XFixesUnionRegion(ps->dpy, damage, damage, extents);
|
|
XFixesDestroyRegion(ps->dpy, extents);
|
|
add_damage(ps, damage);
|
|
}
|
|
|
|
if (factor_change) {
|
|
cxinerama_win_upd_scr(ps, w);
|
|
win_on_factor_change(ps, w);
|
|
}
|
|
}
|
|
|
|
// override_redirect flag cannot be changed after window creation, as far
|
|
// as I know, so there's no point to re-match windows here.
|
|
w->a.override_redirect = ce->override_redirect;
|
|
}
|
|
|
|
static void
|
|
circulate_win(session_t *ps, XCirculateEvent *ce) {
|
|
win *w = find_win(ps, ce->window);
|
|
Window new_above;
|
|
|
|
if (!w) return;
|
|
|
|
if (ce->place == PlaceOnTop) {
|
|
new_above = ps->list->id;
|
|
} else {
|
|
new_above = None;
|
|
}
|
|
|
|
restack_win(ps, w, new_above);
|
|
}
|
|
|
|
static void
|
|
finish_destroy_win(session_t *ps, Window id) {
|
|
win **prev = NULL, *w = NULL;
|
|
|
|
#ifdef DEBUG_EVENTS
|
|
printf_dbgf("(%#010lx): Starting...\n", id);
|
|
#endif
|
|
|
|
for (prev = &ps->list; (w = *prev); prev = &w->next) {
|
|
if (w->id == id && w->destroyed) {
|
|
#ifdef DEBUG_EVENTS
|
|
printf_dbgf("(%#010lx \"%s\"): %p\n", id, w->name, w);
|
|
#endif
|
|
|
|
finish_unmap_win(ps, w);
|
|
*prev = w->next;
|
|
|
|
// Clear active_win if it's pointing to the destroyed window
|
|
if (w == ps->active_win)
|
|
ps->active_win = NULL;
|
|
|
|
free_win_res(ps, w);
|
|
|
|
// Drop w from all prev_trans to avoid accessing freed memory in
|
|
// repair_win()
|
|
for (win *w2 = ps->list; w2; w2 = w2->next)
|
|
if (w == w2->prev_trans)
|
|
w2->prev_trans = NULL;
|
|
|
|
free(w);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
destroy_callback(session_t *ps, win *w) {
|
|
finish_destroy_win(ps, w->id);
|
|
}
|
|
|
|
static void
|
|
destroy_win(session_t *ps, Window id) {
|
|
win *w = find_win(ps, id);
|
|
|
|
#ifdef DEBUG_EVENTS
|
|
printf_dbgf("(%#010lx \"%s\"): %p\n", id, (w ? w->name: NULL), w);
|
|
#endif
|
|
|
|
if (w) {
|
|
unmap_win(ps, w);
|
|
|
|
w->destroyed = true;
|
|
|
|
if (ps->o.no_fading_destroyed_argb)
|
|
win_determine_fade(ps, w);
|
|
|
|
// Set fading callback
|
|
set_fade_callback(ps, w, destroy_callback, false);
|
|
|
|
#ifdef CONFIG_DBUS
|
|
// Send D-Bus signal
|
|
if (ps->o.dbus) {
|
|
cdbus_ev_win_destroyed(ps, w);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
root_damaged(session_t *ps) {
|
|
if (ps->root_tile_paint.pixmap) {
|
|
XClearArea(ps->dpy, ps->root, 0, 0, 0, 0, true);
|
|
// if (ps->root_picture != ps->root_tile) {
|
|
free_root_tile(ps);
|
|
/* }
|
|
if (root_damage) {
|
|
XserverRegion parts = XFixesCreateRegion(ps->dpy, 0, 0);
|
|
XDamageSubtract(ps->dpy, root_damage, None, parts);
|
|
add_damage(ps, parts);
|
|
} */
|
|
}
|
|
|
|
// Mark screen damaged
|
|
force_repaint(ps);
|
|
}
|
|
|
|
static void
|
|
damage_win(session_t *ps, XDamageNotifyEvent *de) {
|
|
/*
|
|
if (ps->root == de->drawable) {
|
|
root_damaged();
|
|
return;
|
|
} */
|
|
|
|
win *w = find_win(ps, de->drawable);
|
|
|
|
if (!w) return;
|
|
|
|
repair_win(ps, w);
|
|
}
|
|
|
|
/**
|
|
* Xlib error handler function.
|
|
*/
|
|
static int
|
|
xerror(Display __attribute__((unused)) *dpy, XErrorEvent *ev) {
|
|
session_t * const ps = ps_g;
|
|
|
|
int o = 0;
|
|
const char *name = "Unknown";
|
|
|
|
if (should_ignore(ps, ev->serial)) {
|
|
return 0;
|
|
}
|
|
|
|
if (ev->request_code == ps->composite_opcode
|
|
&& ev->minor_code == X_CompositeRedirectSubwindows) {
|
|
fprintf(stderr, "Another composite manager is already running\n");
|
|
exit(1);
|
|
}
|
|
|
|
#define CASESTRRET2(s) case s: name = #s; break
|
|
|
|
o = ev->error_code - ps->xfixes_error;
|
|
switch (o) {
|
|
CASESTRRET2(BadRegion);
|
|
}
|
|
|
|
o = ev->error_code - ps->damage_error;
|
|
switch (o) {
|
|
CASESTRRET2(BadDamage);
|
|
}
|
|
|
|
o = ev->error_code - ps->render_error;
|
|
switch (o) {
|
|
CASESTRRET2(BadPictFormat);
|
|
CASESTRRET2(BadPicture);
|
|
CASESTRRET2(BadPictOp);
|
|
CASESTRRET2(BadGlyphSet);
|
|
CASESTRRET2(BadGlyph);
|
|
}
|
|
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (ps->glx_exists) {
|
|
o = ev->error_code - ps->glx_error;
|
|
switch (o) {
|
|
CASESTRRET2(GLX_BAD_SCREEN);
|
|
CASESTRRET2(GLX_BAD_ATTRIBUTE);
|
|
CASESTRRET2(GLX_NO_EXTENSION);
|
|
CASESTRRET2(GLX_BAD_VISUAL);
|
|
CASESTRRET2(GLX_BAD_CONTEXT);
|
|
CASESTRRET2(GLX_BAD_VALUE);
|
|
CASESTRRET2(GLX_BAD_ENUM);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_XSYNC
|
|
if (ps->xsync_exists) {
|
|
o = ev->error_code - ps->xsync_error;
|
|
switch (o) {
|
|
CASESTRRET2(XSyncBadCounter);
|
|
CASESTRRET2(XSyncBadAlarm);
|
|
CASESTRRET2(XSyncBadFence);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
switch (ev->error_code) {
|
|
CASESTRRET2(BadAccess);
|
|
CASESTRRET2(BadAlloc);
|
|
CASESTRRET2(BadAtom);
|
|
CASESTRRET2(BadColor);
|
|
CASESTRRET2(BadCursor);
|
|
CASESTRRET2(BadDrawable);
|
|
CASESTRRET2(BadFont);
|
|
CASESTRRET2(BadGC);
|
|
CASESTRRET2(BadIDChoice);
|
|
CASESTRRET2(BadImplementation);
|
|
CASESTRRET2(BadLength);
|
|
CASESTRRET2(BadMatch);
|
|
CASESTRRET2(BadName);
|
|
CASESTRRET2(BadPixmap);
|
|
CASESTRRET2(BadRequest);
|
|
CASESTRRET2(BadValue);
|
|
CASESTRRET2(BadWindow);
|
|
}
|
|
|
|
#undef CASESTRRET2
|
|
|
|
print_timestamp(ps);
|
|
{
|
|
char buf[BUF_LEN] = "";
|
|
XGetErrorText(ps->dpy, ev->error_code, buf, BUF_LEN);
|
|
printf("error %4d %-12s request %4d minor %4d serial %6lu: \"%s\"\n",
|
|
ev->error_code, name, ev->request_code,
|
|
ev->minor_code, ev->serial, buf);
|
|
}
|
|
|
|
// print_backtrace();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
expose_root(session_t *ps, XRectangle *rects, int nrects) {
|
|
free_all_damage_last(ps);
|
|
XserverRegion region = XFixesCreateRegion(ps->dpy, rects, nrects);
|
|
add_damage(ps, region);
|
|
}
|
|
|
|
/**
|
|
* Get the value of a type-<code>Window</code> property of a window.
|
|
*
|
|
* @return the value if successful, 0 otherwise
|
|
*/
|
|
static Window
|
|
wid_get_prop_window(session_t *ps, Window wid, Atom aprop) {
|
|
// Get the attribute
|
|
Window p = None;
|
|
winprop_t prop = wid_get_prop(ps, wid, aprop, 1L, XA_WINDOW, 32);
|
|
|
|
// Return it
|
|
if (prop.nitems) {
|
|
p = *prop.data.p32;
|
|
}
|
|
|
|
free_winprop(&prop);
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Update focused state of a window.
|
|
*/
|
|
static void
|
|
win_update_focused(session_t *ps, win *w) {
|
|
bool focused_old = w->focused;
|
|
|
|
if (UNSET != w->focused_force) {
|
|
w->focused = w->focused_force;
|
|
}
|
|
else {
|
|
w->focused = win_is_focused_real(ps, w);
|
|
|
|
// Use wintype_focus, and treat WM windows and override-redirected
|
|
// windows specially
|
|
if (ps->o.wintype_focus[w->window_type]
|
|
|| (ps->o.mark_wmwin_focused && w->wmwin)
|
|
|| (ps->o.mark_ovredir_focused
|
|
&& w->id == w->client_win && !w->wmwin)
|
|
|| (IsViewable == w->a.map_state && 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
|
|
// its group is
|
|
if (ps->o.track_leader && ps->active_leader
|
|
&& win_get_leader(ps, w) == ps->active_leader) {
|
|
w->focused = true;
|
|
}
|
|
}
|
|
|
|
if (w->focused != focused_old)
|
|
w->flags |= WFLAG_OPCT_CHANGE;
|
|
}
|
|
|
|
/**
|
|
* Set real focused state of a window.
|
|
*/
|
|
static void
|
|
win_set_focused(session_t *ps, win *w, bool focused) {
|
|
// Unmapped windows will have their focused state reset on map
|
|
if (IsUnmapped == w->a.map_state)
|
|
return;
|
|
|
|
if (win_is_focused_real(ps, w) == focused) return;
|
|
|
|
if (focused) {
|
|
if (ps->active_win)
|
|
win_set_focused(ps, ps->active_win, false);
|
|
ps->active_win = w;
|
|
}
|
|
else if (w == ps->active_win)
|
|
ps->active_win = NULL;
|
|
|
|
assert(win_is_focused_real(ps, w) == focused);
|
|
|
|
win_on_focus_change(ps, w);
|
|
}
|
|
|
|
/**
|
|
* Handle window focus change.
|
|
*/
|
|
static void
|
|
win_on_focus_change(session_t *ps, win *w) {
|
|
// If window grouping detection is enabled
|
|
if (ps->o.track_leader) {
|
|
Window leader = win_get_leader(ps, w);
|
|
|
|
// If the window gets focused, replace the old active_leader
|
|
if (win_is_focused_real(ps, w) && leader != ps->active_leader) {
|
|
Window active_leader_old = ps->active_leader;
|
|
|
|
ps->active_leader = leader;
|
|
|
|
group_update_focused(ps, active_leader_old);
|
|
group_update_focused(ps, leader);
|
|
}
|
|
// If the group get unfocused, remove it from active_leader
|
|
else if (!win_is_focused_real(ps, w) && leader && leader == ps->active_leader
|
|
&& !group_is_focused(ps, leader)) {
|
|
ps->active_leader = None;
|
|
group_update_focused(ps, leader);
|
|
}
|
|
|
|
// The window itself must be updated anyway
|
|
win_update_focused(ps, w);
|
|
}
|
|
// Otherwise, only update the window itself
|
|
else {
|
|
win_update_focused(ps, w);
|
|
}
|
|
|
|
// Update everything related to conditions
|
|
win_on_factor_change(ps, w);
|
|
|
|
#ifdef CONFIG_DBUS
|
|
// Send D-Bus signal
|
|
if (ps->o.dbus) {
|
|
if (win_is_focused_real(ps, w))
|
|
cdbus_ev_win_focusin(ps, w);
|
|
else
|
|
cdbus_ev_win_focusout(ps, w);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Update leader of a window.
|
|
*/
|
|
static void
|
|
win_update_leader(session_t *ps, win *w) {
|
|
Window leader = None;
|
|
|
|
// Read the leader properties
|
|
if (ps->o.detect_transient && !leader)
|
|
leader = wid_get_prop_window(ps, w->client_win, ps->atom_transient);
|
|
|
|
if (ps->o.detect_client_leader && !leader)
|
|
leader = wid_get_prop_window(ps, w->client_win, ps->atom_client_leader);
|
|
|
|
win_set_leader(ps, w, leader);
|
|
|
|
#ifdef DEBUG_LEADER
|
|
printf_dbgf("(%#010lx): client %#010lx, leader %#010lx, cache %#010lx\n", w->id, w->client_win, w->leader, win_get_leader(ps, w));
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Set leader of a window.
|
|
*/
|
|
static void
|
|
win_set_leader(session_t *ps, win *w, Window nleader) {
|
|
// If the leader changes
|
|
if (w->leader != nleader) {
|
|
Window cache_leader_old = win_get_leader(ps, w);
|
|
|
|
w->leader = nleader;
|
|
|
|
// Forcefully do this to deal with the case when a child window
|
|
// gets mapped before parent, or when the window is a waypoint
|
|
clear_cache_win_leaders(ps);
|
|
|
|
// Update the old and new window group and active_leader if the window
|
|
// could affect their state.
|
|
Window cache_leader = win_get_leader(ps, w);
|
|
if (win_is_focused_real(ps, w) && cache_leader_old != cache_leader) {
|
|
ps->active_leader = cache_leader;
|
|
|
|
group_update_focused(ps, cache_leader_old);
|
|
group_update_focused(ps, cache_leader);
|
|
}
|
|
// Otherwise, at most the window itself is affected
|
|
else {
|
|
win_update_focused(ps, w);
|
|
}
|
|
|
|
// Update everything related to conditions
|
|
win_on_factor_change(ps, w);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal function of win_get_leader().
|
|
*/
|
|
static Window
|
|
win_get_leader_raw(session_t *ps, win *w, int recursions) {
|
|
// Rebuild the cache if needed
|
|
if (!w->cache_leader && (w->client_win || w->leader)) {
|
|
// Leader defaults to client window
|
|
if (!(w->cache_leader = w->leader))
|
|
w->cache_leader = w->client_win;
|
|
|
|
// If the leader of this window isn't itself, look for its ancestors
|
|
if (w->cache_leader && w->cache_leader != w->client_win) {
|
|
win *wp = find_toplevel(ps, w->cache_leader);
|
|
if (wp) {
|
|
// Dead loop?
|
|
if (recursions > WIN_GET_LEADER_MAX_RECURSION)
|
|
return None;
|
|
|
|
w->cache_leader = win_get_leader_raw(ps, wp, recursions + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return w->cache_leader;
|
|
}
|
|
|
|
/**
|
|
* Get the value of a text property of a window.
|
|
*/
|
|
bool
|
|
wid_get_text_prop(session_t *ps, Window wid, Atom prop,
|
|
char ***pstrlst, int *pnstr) {
|
|
XTextProperty text_prop = { NULL, None, 0, 0 };
|
|
|
|
if (!(XGetTextProperty(ps->dpy, wid, &text_prop, prop) && text_prop.value))
|
|
return false;
|
|
|
|
if (Success !=
|
|
XmbTextPropertyToTextList(ps->dpy, &text_prop, pstrlst, pnstr)
|
|
|| !*pnstr) {
|
|
*pnstr = 0;
|
|
if (*pstrlst)
|
|
XFreeStringList(*pstrlst);
|
|
cxfree(text_prop.value);
|
|
return false;
|
|
}
|
|
|
|
cxfree(text_prop.value);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get the name of a window from window ID.
|
|
*/
|
|
static bool
|
|
wid_get_name(session_t *ps, Window wid, char **name) {
|
|
XTextProperty text_prop = { NULL, None, 0, 0 };
|
|
char **strlst = NULL;
|
|
int nstr = 0;
|
|
|
|
if (!(wid_get_text_prop(ps, wid, ps->atom_name_ewmh, &strlst, &nstr))) {
|
|
#ifdef DEBUG_WINDATA
|
|
printf_dbgf("(%#010lx): _NET_WM_NAME unset, falling back to WM_NAME.\n", wid);
|
|
#endif
|
|
|
|
if (!(XGetWMName(ps->dpy, wid, &text_prop) && text_prop.value)) {
|
|
return false;
|
|
}
|
|
if (Success !=
|
|
XmbTextPropertyToTextList(ps->dpy, &text_prop, &strlst, &nstr)
|
|
|| !nstr || !strlst) {
|
|
if (strlst)
|
|
XFreeStringList(strlst);
|
|
cxfree(text_prop.value);
|
|
return false;
|
|
}
|
|
cxfree(text_prop.value);
|
|
}
|
|
|
|
*name = mstrcpy(strlst[0]);
|
|
|
|
XFreeStringList(strlst);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get the role of a window from window ID.
|
|
*/
|
|
static bool
|
|
wid_get_role(session_t *ps, Window wid, char **role) {
|
|
char **strlst = NULL;
|
|
int nstr = 0;
|
|
|
|
if (!wid_get_text_prop(ps, wid, ps->atom_role, &strlst, &nstr)) {
|
|
return false;
|
|
}
|
|
|
|
*role = mstrcpy(strlst[0]);
|
|
|
|
XFreeStringList(strlst);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Retrieve a string property of a window and update its <code>win</code>
|
|
* structure.
|
|
*/
|
|
static int
|
|
win_get_prop_str(session_t *ps, win *w, char **tgt,
|
|
bool (*func_wid_get_prop_str)(session_t *ps, Window wid, char **tgt)) {
|
|
int ret = -1;
|
|
char *prop_old = *tgt;
|
|
|
|
// Can't do anything if there's no client window
|
|
if (!w->client_win)
|
|
return false;
|
|
|
|
// Get the property
|
|
ret = func_wid_get_prop_str(ps, w->client_win, tgt);
|
|
|
|
// Return -1 if func_wid_get_prop_str() failed, 0 if the property
|
|
// doesn't change, 1 if it changes
|
|
if (!ret)
|
|
ret = -1;
|
|
else if (prop_old && !strcmp(*tgt, prop_old))
|
|
ret = 0;
|
|
else
|
|
ret = 1;
|
|
|
|
// Keep the old property if there's no new one
|
|
if (*tgt != prop_old)
|
|
free(prop_old);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the <code>WM_CLASS</code> of a window and update its
|
|
* <code>win</code> structure.
|
|
*/
|
|
static bool
|
|
win_get_class(session_t *ps, 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(ps, w->client_win, ps->atom_class, &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_dbgf("(%#010lx): client = %#010lx, "
|
|
"instance = \"%s\", general = \"%s\"\n",
|
|
w->id, w->client_win, w->class_instance, w->class_general);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Force a full-screen repaint.
|
|
*/
|
|
void
|
|
force_repaint(session_t *ps) {
|
|
assert(ps->screen_reg);
|
|
XserverRegion reg = None;
|
|
if (ps->screen_reg && (reg = copy_region(ps, ps->screen_reg))) {
|
|
ps->ev_received = true;
|
|
add_damage(ps, reg);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_DBUS
|
|
/** @name DBus hooks
|
|
*/
|
|
///@{
|
|
|
|
/**
|
|
* Set w->shadow_force of a window.
|
|
*/
|
|
void
|
|
win_set_shadow_force(session_t *ps, win *w, switch_t val) {
|
|
if (val != w->shadow_force) {
|
|
w->shadow_force = val;
|
|
win_determine_shadow(ps, w);
|
|
ps->ev_received = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set w->fade_force of a window.
|
|
*/
|
|
void
|
|
win_set_fade_force(session_t *ps, win *w, switch_t val) {
|
|
if (val != w->fade_force) {
|
|
w->fade_force = val;
|
|
win_determine_fade(ps, w);
|
|
ps->ev_received = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set w->focused_force of a window.
|
|
*/
|
|
void
|
|
win_set_focused_force(session_t *ps, win *w, switch_t val) {
|
|
if (val != w->focused_force) {
|
|
w->focused_force = val;
|
|
win_update_focused(ps, w);
|
|
ps->ev_received = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set w->invert_color_force of a window.
|
|
*/
|
|
void
|
|
win_set_invert_color_force(session_t *ps, win *w, switch_t val) {
|
|
if (val != w->invert_color_force) {
|
|
w->invert_color_force = val;
|
|
win_determine_invert_color(ps, w);
|
|
ps->ev_received = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable focus tracking.
|
|
*/
|
|
void
|
|
opts_init_track_focus(session_t *ps) {
|
|
// Already tracking focus
|
|
if (ps->o.track_focus)
|
|
return;
|
|
|
|
ps->o.track_focus = true;
|
|
|
|
if (!ps->o.use_ewmh_active_win) {
|
|
// Start listening to FocusChange events
|
|
for (win *w = ps->list; w; w = w->next)
|
|
if (IsViewable == w->a.map_state)
|
|
XSelectInput(ps->dpy, w->id,
|
|
determine_evmask(ps, w->id, WIN_EVMODE_FRAME));
|
|
}
|
|
|
|
// Recheck focus
|
|
recheck_focus(ps);
|
|
}
|
|
|
|
/**
|
|
* Set no_fading_openclose option.
|
|
*/
|
|
void
|
|
opts_set_no_fading_openclose(session_t *ps, bool newval) {
|
|
if (newval != ps->o.no_fading_openclose) {
|
|
ps->o.no_fading_openclose = newval;
|
|
for (win *w = ps->list; w; w = w->next)
|
|
win_determine_fade(ps, w);
|
|
ps->ev_received = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set no_fading_opacitychange option.
|
|
*/
|
|
void
|
|
opts_set_no_fading_opacitychange(session_t *ps, bool newval) {
|
|
if (newval != ps->o.no_fading_opacitychange) {
|
|
ps->o.no_fading_opacitychange = newval;
|
|
for (win *w = ps->list; w; w = w->next)
|
|
win_determine_fade(ps, w);
|
|
ps->ev_received = true;
|
|
}
|
|
}
|
|
|
|
//!@}
|
|
#endif
|
|
|
|
#ifdef DEBUG_EVENTS
|
|
static int
|
|
ev_serial(XEvent *ev) {
|
|
if ((ev->type & 0x7f) != KeymapNotify) {
|
|
return ev->xany.serial;
|
|
}
|
|
return NextRequest(ev->xany.display);
|
|
}
|
|
|
|
static const char *
|
|
ev_name(session_t *ps, XEvent *ev) {
|
|
static char buf[128];
|
|
switch (ev->type & 0x7f) {
|
|
CASESTRRET(FocusIn);
|
|
CASESTRRET(FocusOut);
|
|
CASESTRRET(CreateNotify);
|
|
CASESTRRET(ConfigureNotify);
|
|
CASESTRRET(DestroyNotify);
|
|
CASESTRRET(MapNotify);
|
|
CASESTRRET(UnmapNotify);
|
|
CASESTRRET(ReparentNotify);
|
|
CASESTRRET(CirculateNotify);
|
|
CASESTRRET(Expose);
|
|
CASESTRRET(PropertyNotify);
|
|
CASESTRRET(ClientMessage);
|
|
}
|
|
|
|
if (isdamagenotify(ps, ev))
|
|
return "Damage";
|
|
|
|
if (ps->shape_exists && ev->type == ps->shape_event)
|
|
return "ShapeNotify";
|
|
|
|
#ifdef CONFIG_XSYNC
|
|
if (ps->xsync_exists) {
|
|
int o = ev->type - ps->xsync_event;
|
|
switch (o) {
|
|
CASESTRRET(CounterNotify);
|
|
CASESTRRET(AlarmNotify);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
sprintf(buf, "Event %d", ev->type);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static Window
|
|
ev_window(session_t *ps, XEvent *ev) {
|
|
switch (ev->type) {
|
|
case FocusIn:
|
|
case FocusOut:
|
|
return ev->xfocus.window;
|
|
case CreateNotify:
|
|
return ev->xcreatewindow.window;
|
|
case ConfigureNotify:
|
|
return ev->xconfigure.window;
|
|
case DestroyNotify:
|
|
return ev->xdestroywindow.window;
|
|
case MapNotify:
|
|
return ev->xmap.window;
|
|
case UnmapNotify:
|
|
return ev->xunmap.window;
|
|
case ReparentNotify:
|
|
return ev->xreparent.window;
|
|
case CirculateNotify:
|
|
return ev->xcirculate.window;
|
|
case Expose:
|
|
return ev->xexpose.window;
|
|
case PropertyNotify:
|
|
return ev->xproperty.window;
|
|
case ClientMessage:
|
|
return ev->xclient.window;
|
|
default:
|
|
if (isdamagenotify(ps, ev)) {
|
|
return ((XDamageNotifyEvent *)ev)->drawable;
|
|
}
|
|
|
|
if (ps->shape_exists && ev->type == ps->shape_event) {
|
|
return ((XShapeEvent *) ev)->window;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static inline const char *
|
|
ev_focus_mode_name(XFocusChangeEvent* ev) {
|
|
switch (ev->mode) {
|
|
CASESTRRET(NotifyNormal);
|
|
CASESTRRET(NotifyWhileGrabbed);
|
|
CASESTRRET(NotifyGrab);
|
|
CASESTRRET(NotifyUngrab);
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
static inline const char *
|
|
ev_focus_detail_name(XFocusChangeEvent* ev) {
|
|
switch (ev->detail) {
|
|
CASESTRRET(NotifyAncestor);
|
|
CASESTRRET(NotifyVirtual);
|
|
CASESTRRET(NotifyInferior);
|
|
CASESTRRET(NotifyNonlinear);
|
|
CASESTRRET(NotifyNonlinearVirtual);
|
|
CASESTRRET(NotifyPointer);
|
|
CASESTRRET(NotifyPointerRoot);
|
|
CASESTRRET(NotifyDetailNone);
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
static inline void
|
|
ev_focus_report(XFocusChangeEvent* ev) {
|
|
printf(" { mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
|
|
ev_focus_detail_name(ev));
|
|
}
|
|
|
|
#endif
|
|
|
|
// === Events ===
|
|
|
|
/**
|
|
* Determine whether we should respond to a <code>FocusIn/Out</code>
|
|
* event.
|
|
*/
|
|
/*
|
|
inline static bool
|
|
ev_focus_accept(XFocusChangeEvent *ev) {
|
|
return NotifyNormal == ev->mode || NotifyUngrab == ev->mode;
|
|
}
|
|
*/
|
|
|
|
static inline void
|
|
ev_focus_in(session_t *ps, XFocusChangeEvent *ev) {
|
|
#ifdef DEBUG_EVENTS
|
|
ev_focus_report(ev);
|
|
#endif
|
|
|
|
recheck_focus(ps);
|
|
}
|
|
|
|
inline static void
|
|
ev_focus_out(session_t *ps, XFocusChangeEvent *ev) {
|
|
#ifdef DEBUG_EVENTS
|
|
ev_focus_report(ev);
|
|
#endif
|
|
|
|
recheck_focus(ps);
|
|
}
|
|
|
|
inline static void
|
|
ev_create_notify(session_t *ps, XCreateWindowEvent *ev) {
|
|
assert(ev->parent == ps->root);
|
|
add_win(ps, ev->window, 0);
|
|
}
|
|
|
|
inline static void
|
|
ev_configure_notify(session_t *ps, XConfigureEvent *ev) {
|
|
#ifdef DEBUG_EVENTS
|
|
printf(" { send_event: %d, "
|
|
" above: %#010lx, "
|
|
" override_redirect: %d }\n",
|
|
ev->send_event, ev->above, ev->override_redirect);
|
|
#endif
|
|
configure_win(ps, ev);
|
|
}
|
|
|
|
inline static void
|
|
ev_destroy_notify(session_t *ps, XDestroyWindowEvent *ev) {
|
|
destroy_win(ps, ev->window);
|
|
}
|
|
|
|
inline static void
|
|
ev_map_notify(session_t *ps, XMapEvent *ev) {
|
|
map_win(ps, ev->window);
|
|
}
|
|
|
|
inline static void
|
|
ev_unmap_notify(session_t *ps, XUnmapEvent *ev) {
|
|
win *w = find_win(ps, ev->window);
|
|
|
|
if (w)
|
|
unmap_win(ps, w);
|
|
}
|
|
|
|
inline static void
|
|
ev_reparent_notify(session_t *ps, XReparentEvent *ev) {
|
|
#ifdef DEBUG_EVENTS
|
|
printf_dbg(" { new_parent: %#010lx, override_redirect: %d }\n",
|
|
ev->parent, ev->override_redirect);
|
|
#endif
|
|
|
|
if (ev->parent == ps->root) {
|
|
add_win(ps, ev->window, 0);
|
|
} else {
|
|
destroy_win(ps, ev->window);
|
|
|
|
// Reset event mask in case something wrong happens
|
|
XSelectInput(ps->dpy, ev->window,
|
|
determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN));
|
|
|
|
// Check if the window is an undetected client window
|
|
// Firstly, check if it's a known client window
|
|
if (!find_toplevel(ps, ev->window)) {
|
|
// If not, look for its frame window
|
|
win *w_top = find_toplevel2(ps, ev->parent);
|
|
// If found, and the client window has not been determined, or its
|
|
// frame may not have a correct client, continue
|
|
if (w_top && (!w_top->client_win
|
|
|| w_top->client_win == w_top->id)) {
|
|
// If it has WM_STATE, mark it the client window
|
|
if (wid_has_prop(ps, ev->window, ps->atom_client)) {
|
|
w_top->wmwin = false;
|
|
win_unmark_client(ps, w_top);
|
|
win_mark_client(ps, w_top, ev->window);
|
|
}
|
|
// Otherwise, watch for WM_STATE on it
|
|
else {
|
|
XSelectInput(ps->dpy, ev->window,
|
|
determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)
|
|
| PropertyChangeMask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
inline static void
|
|
ev_circulate_notify(session_t *ps, XCirculateEvent *ev) {
|
|
circulate_win(ps, ev);
|
|
}
|
|
|
|
inline static void
|
|
ev_expose(session_t *ps, XExposeEvent *ev) {
|
|
if (ev->window == ps->root || (ps->overlay && ev->window == ps->overlay)) {
|
|
int more = ev->count + 1;
|
|
if (ps->n_expose == ps->size_expose) {
|
|
if (ps->expose_rects) {
|
|
ps->expose_rects = realloc(ps->expose_rects,
|
|
(ps->size_expose + more) * sizeof(XRectangle));
|
|
ps->size_expose += more;
|
|
} else {
|
|
ps->expose_rects = malloc(more * sizeof(XRectangle));
|
|
ps->size_expose = more;
|
|
}
|
|
}
|
|
|
|
ps->expose_rects[ps->n_expose].x = ev->x;
|
|
ps->expose_rects[ps->n_expose].y = ev->y;
|
|
ps->expose_rects[ps->n_expose].width = ev->width;
|
|
ps->expose_rects[ps->n_expose].height = ev->height;
|
|
ps->n_expose++;
|
|
|
|
if (ev->count == 0) {
|
|
expose_root(ps, ps->expose_rects, ps->n_expose);
|
|
ps->n_expose = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update current active window based on EWMH _NET_ACTIVE_WIN.
|
|
*
|
|
* Does not change anything if we fail to get the attribute or the window
|
|
* returned could not be found.
|
|
*/
|
|
static void
|
|
update_ewmh_active_win(session_t *ps) {
|
|
// Search for the window
|
|
Window wid = wid_get_prop_window(ps, ps->root, ps->atom_ewmh_active_win);
|
|
win *w = find_win_all(ps, wid);
|
|
|
|
// Mark the window focused. No need to unfocus the previous one.
|
|
if (w) win_set_focused(ps, w, true);
|
|
}
|
|
|
|
inline static void
|
|
ev_property_notify(session_t *ps, XPropertyEvent *ev) {
|
|
#ifdef DEBUG_EVENTS
|
|
{
|
|
// Print out changed atom
|
|
char *name = XGetAtomName(ps->dpy, ev->atom);
|
|
printf_dbg(" { atom = %s }\n", name);
|
|
cxfree(name);
|
|
}
|
|
#endif
|
|
|
|
if (ps->root == ev->window) {
|
|
if (ps->o.track_focus && ps->o.use_ewmh_active_win
|
|
&& ps->atom_ewmh_active_win == ev->atom) {
|
|
update_ewmh_active_win(ps);
|
|
}
|
|
else {
|
|
// Destroy the root "image" if the wallpaper probably changed
|
|
for (int p = 0; background_props_str[p]; p++) {
|
|
if (ev->atom == get_atom(ps, background_props_str[p])) {
|
|
root_damaged(ps);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unconcerned about any other proprties on root window
|
|
return;
|
|
}
|
|
|
|
// If WM_STATE changes
|
|
if (ev->atom == ps->atom_client) {
|
|
// Check whether it could be a client window
|
|
if (!find_toplevel(ps, ev->window)) {
|
|
// Reset event mask anyway
|
|
XSelectInput(ps->dpy, ev->window,
|
|
determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN));
|
|
|
|
win *w_top = find_toplevel2(ps, ev->window);
|
|
// Initialize client_win as early as possible
|
|
if (w_top && (!w_top->client_win || w_top->client_win == w_top->id)
|
|
&& wid_has_prop(ps, ev->window, ps->atom_client)) {
|
|
w_top->wmwin = false;
|
|
win_unmark_client(ps, w_top);
|
|
win_mark_client(ps, w_top, ev->window);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
|
|
// there are always some stupid applications. (#144)
|
|
if (ev->atom == ps->atom_win_type) {
|
|
win *w = NULL;
|
|
if ((w = find_toplevel(ps, ev->window)))
|
|
win_upd_wintype(ps, w);
|
|
}
|
|
|
|
// If _NET_WM_OPACITY changes
|
|
if (ev->atom == ps->atom_opacity) {
|
|
win *w = NULL;
|
|
if ((w = find_win(ps, ev->window)))
|
|
w->opacity_prop = wid_get_opacity_prop(ps, w->id, OPAQUE);
|
|
else if (ps->o.detect_client_opacity
|
|
&& (w = find_toplevel(ps, ev->window)))
|
|
w->opacity_prop_client = wid_get_opacity_prop(ps, w->client_win,
|
|
OPAQUE);
|
|
if (w) {
|
|
w->flags |= WFLAG_OPCT_CHANGE;
|
|
}
|
|
}
|
|
|
|
// If frame extents property changes
|
|
if (ps->o.frame_opacity && ev->atom == ps->atom_frame_extents) {
|
|
win *w = find_toplevel(ps, ev->window);
|
|
if (w) {
|
|
get_frame_extents(ps, w, ev->window);
|
|
// If frame extents change, the window needs repaint
|
|
add_damage_win(ps, w);
|
|
}
|
|
}
|
|
|
|
// If name changes
|
|
if (ps->o.track_wdata
|
|
&& (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_factor_change(ps, w);
|
|
}
|
|
}
|
|
|
|
// If class changes
|
|
if (ps->o.track_wdata && ps->atom_class == ev->atom) {
|
|
win *w = find_toplevel(ps, ev->window);
|
|
if (w) {
|
|
win_get_class(ps, w);
|
|
win_on_factor_change(ps, w);
|
|
}
|
|
}
|
|
|
|
// If role changes
|
|
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_factor_change(ps, w);
|
|
}
|
|
}
|
|
|
|
// If _TDE_WM_WINDOW_SHADOW changes
|
|
if (ps->o.respect_prop_shadow && ps->atom_compton_shadow == ev->atom) {
|
|
win *w = find_win(ps, ev->window);
|
|
if (w)
|
|
win_update_prop_shadow(ps, w);
|
|
}
|
|
|
|
// If a leader property changes
|
|
if ((ps->o.detect_transient && ps->atom_transient == ev->atom)
|
|
|| (ps->o.detect_client_leader && ps->atom_client_leader == ev->atom)) {
|
|
win *w = find_toplevel(ps, ev->window);
|
|
if (w) {
|
|
win_update_leader(ps, w);
|
|
}
|
|
}
|
|
|
|
// Check for other atoms we are tracking
|
|
for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
|
|
if (platom->atom == ev->atom) {
|
|
win *w = find_win(ps, ev->window);
|
|
if (!w)
|
|
w = find_toplevel(ps, ev->window);
|
|
if (w)
|
|
win_on_factor_change(ps, w);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline static void
|
|
ev_damage_notify(session_t *ps, XDamageNotifyEvent *ev) {
|
|
damage_win(ps, ev);
|
|
}
|
|
|
|
inline static void
|
|
ev_shape_notify(session_t *ps, XShapeEvent *ev) {
|
|
win *w = find_win(ps, ev->window);
|
|
if (!w || IsUnmapped == w->a.map_state) return;
|
|
|
|
/*
|
|
* Empty border_size may indicated an
|
|
* unmapped/destroyed window, in which case
|
|
* seemingly BadRegion errors would be triggered
|
|
* if we attempt to rebuild border_size
|
|
*/
|
|
if (w->border_size) {
|
|
// Mark the old border_size as damaged
|
|
add_damage(ps, w->border_size);
|
|
|
|
w->border_size = border_size(ps, w, true);
|
|
|
|
// Mark the new border_size as damaged
|
|
add_damage(ps, copy_region(ps, w->border_size));
|
|
}
|
|
|
|
// Redo bounding shape detection and rounded corner detection
|
|
win_update_shape(ps, w);
|
|
|
|
update_reg_ignore_expire(ps, w);
|
|
}
|
|
|
|
#ifdef CONFIG_XRANDR
|
|
/**
|
|
* Handle ScreenChangeNotify events from X RandR extension.
|
|
*/
|
|
static void
|
|
ev_screen_change_notify(session_t *ps,
|
|
XRRScreenChangeNotifyEvent __attribute__((unused)) *ev) {
|
|
if (ps->o.xinerama_shadow_crop)
|
|
cxinerama_upd_scrs(ps);
|
|
|
|
if (ps->o.sw_opti && !ps->o.refresh_rate) {
|
|
update_refresh_rate(ps);
|
|
if (!ps->refresh_rate) {
|
|
fprintf(stderr, "ev_screen_change_notify(): Refresh rate detection "
|
|
"failed, --sw-opti disabled.");
|
|
ps->o.sw_opti = false;
|
|
}
|
|
}
|
|
}
|
|
#endif // CONFIG_XRANDR
|
|
|
|
#if defined(DEBUG_EVENTS) || defined(DEBUG_RESTACK)
|
|
/**
|
|
* Get a window's name from window ID.
|
|
*/
|
|
static bool
|
|
ev_window_name(session_t *ps, Window wid, char **name) {
|
|
bool to_free = false;
|
|
|
|
*name = "";
|
|
if (wid) {
|
|
*name = "(Failed to get title)";
|
|
if (ps->root == wid)
|
|
*name = "(Root window)";
|
|
else if (ps->overlay == wid)
|
|
*name = "(Overlay)";
|
|
else {
|
|
win *w = find_win(ps, wid);
|
|
if (!w)
|
|
w = find_toplevel(ps, wid);
|
|
|
|
if (w && w->name)
|
|
*name = w->name;
|
|
else if (!(w && w->client_win
|
|
&& (to_free = wid_get_name(ps, w->client_win, name))))
|
|
to_free = wid_get_name(ps, wid, name);
|
|
}
|
|
}
|
|
|
|
return to_free;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
ev_handle(session_t *ps, XEvent *ev) {
|
|
if ((ev->type & 0x7f) != KeymapNotify) {
|
|
discard_ignore(ps, ev->xany.serial);
|
|
}
|
|
|
|
#ifdef DEBUG_EVENTS
|
|
if (!isdamagenotify(ps, ev)) {
|
|
Window wid = ev_window(ps, ev);
|
|
char *window_name = NULL;
|
|
bool to_free = false;
|
|
|
|
to_free = ev_window_name(ps, wid, &window_name);
|
|
|
|
print_timestamp(ps);
|
|
printf("event %10.10s serial %#010x window %#010lx \"%s\"\n",
|
|
ev_name(ps, ev), ev_serial(ev), wid, window_name);
|
|
|
|
if (to_free) {
|
|
cxfree(window_name);
|
|
window_name = NULL;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
switch (ev->type) {
|
|
case FocusIn:
|
|
ev_focus_in(ps, (XFocusChangeEvent *)ev);
|
|
break;
|
|
case FocusOut:
|
|
ev_focus_out(ps, (XFocusChangeEvent *)ev);
|
|
break;
|
|
case CreateNotify:
|
|
ev_create_notify(ps, (XCreateWindowEvent *)ev);
|
|
break;
|
|
case ConfigureNotify:
|
|
ev_configure_notify(ps, (XConfigureEvent *)ev);
|
|
break;
|
|
case DestroyNotify:
|
|
ev_destroy_notify(ps, (XDestroyWindowEvent *)ev);
|
|
break;
|
|
case MapNotify:
|
|
ev_map_notify(ps, (XMapEvent *)ev);
|
|
break;
|
|
case UnmapNotify:
|
|
ev_unmap_notify(ps, (XUnmapEvent *)ev);
|
|
break;
|
|
case ReparentNotify:
|
|
ev_reparent_notify(ps, (XReparentEvent *)ev);
|
|
break;
|
|
case CirculateNotify:
|
|
ev_circulate_notify(ps, (XCirculateEvent *)ev);
|
|
break;
|
|
case Expose:
|
|
ev_expose(ps, (XExposeEvent *)ev);
|
|
break;
|
|
case PropertyNotify:
|
|
ev_property_notify(ps, (XPropertyEvent *)ev);
|
|
break;
|
|
default:
|
|
if (ps->shape_exists && ev->type == ps->shape_event) {
|
|
ev_shape_notify(ps, (XShapeEvent *) ev);
|
|
break;
|
|
}
|
|
#ifdef CONFIG_XRANDR
|
|
if (ps->randr_exists && ev->type == (ps->randr_event + RRScreenChangeNotify)) {
|
|
ev_screen_change_notify(ps, (XRRScreenChangeNotifyEvent *) ev);
|
|
break;
|
|
}
|
|
#endif // CONFIG_XRANDR
|
|
if (isdamagenotify(ps, ev)) {
|
|
ev_damage_notify(ps, (XDamageNotifyEvent *) ev);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// === Main ===
|
|
|
|
/**
|
|
* Print usage text and exit.
|
|
*/
|
|
static void
|
|
usage(int ret) {
|
|
#define WARNING_DISABLED " (DISABLED AT COMPILE TIME)"
|
|
#define WARNING
|
|
const static char *usage_text =
|
|
"compton (" COMPTON_VERSION ")\n"
|
|
"usage: compton [options]\n"
|
|
"Options:\n"
|
|
"\n"
|
|
"-d display\n"
|
|
" Which display should be managed.\n"
|
|
"-r radius\n"
|
|
" The blur radius for shadows. (default 12)\n"
|
|
"-o opacity\n"
|
|
" The translucency for shadows. (default .75)\n"
|
|
"-l left-offset\n"
|
|
" The left offset for shadows. (default -15)\n"
|
|
"-t top-offset\n"
|
|
" The top offset for shadows. (default -15)\n"
|
|
"-I fade-in-step\n"
|
|
" Opacity change between steps while fading in. (default 0.028)\n"
|
|
"-O fade-out-step\n"
|
|
" Opacity change between steps while fading out. (default 0.03)\n"
|
|
"-D fade-delta-time\n"
|
|
" The time between steps in a fade in milliseconds. (default 10)\n"
|
|
"-m opacity\n"
|
|
" The opacity for menus. (default 1.0)\n"
|
|
"-c\n"
|
|
" Enabled client-side shadows on windows.\n"
|
|
"-C\n"
|
|
" Avoid drawing shadows on dock/panel windows.\n"
|
|
"-z\n"
|
|
" Zero the part of the shadow's mask behind the window (experimental).\n"
|
|
"-f\n"
|
|
" Fade windows in/out when opening/closing and when opacity\n"
|
|
" changes, unless --no-fading-openclose is used.\n"
|
|
"-F\n"
|
|
" Equals -f. Deprecated.\n"
|
|
"-i opacity\n"
|
|
" Opacity of inactive windows. (0.1 - 1.0)\n"
|
|
"-e opacity\n"
|
|
" Opacity of window titlebars and borders. (0.1 - 1.0)\n"
|
|
"-G\n"
|
|
" Don't draw shadows on DND windows\n"
|
|
"-b\n"
|
|
" Daemonize process.\n"
|
|
"-S\n"
|
|
" Enable synchronous operation (for debugging).\n"
|
|
"--show-all-xerrors\n"
|
|
" Show all X errors (for debugging).\n"
|
|
"-v\n"
|
|
" Print version Number and exit\\n"
|
|
"--config path\n"
|
|
" Look for configuration file at the path.\n"
|
|
"--write-pid-path path\n"
|
|
" Write process ID to a file.\n"
|
|
"--shadow-red value\n"
|
|
" Red color value of shadow (0.0 - 1.0, defaults to 0).\n"
|
|
"--shadow-green value\n"
|
|
" Green color value of shadow (0.0 - 1.0, defaults to 0).\n"
|
|
"--shadow-blue value\n"
|
|
" Blue color value of shadow (0.0 - 1.0, defaults to 0).\n"
|
|
"--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"
|
|
"--active-opacity opacity\n"
|
|
" Default opacity for active windows. (0.0 - 1.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"
|
|
"--fade-exclude condition\n"
|
|
" Exclude conditions for fading.\n"
|
|
"--mark-ovredir-focused\n"
|
|
" Mark windows that have no WM frame as active.\n"
|
|
"--no-fading-openclose\n"
|
|
" Do not fade on window open/close.\n"
|
|
"--no-fading-destroyed-argb\n"
|
|
" Do not fade destroyed ARGB windows with WM frame. Workaround of bugs\n"
|
|
" in Openbox, Fluxbox, etc.\n"
|
|
"--no-fading-opacitychange\n"
|
|
" Do not fade on window opacity change.\n"
|
|
"--shadow-ignore-shaped\n"
|
|
" Do not paint shadows on shaped windows. (Deprecated, use\n"
|
|
" --shadow-exclude \'bounding_shaped\' or\n"
|
|
" --shadow-exclude \'bounding_shaped && !rounded_corners\' instead.)\n"
|
|
"--detect-rounded-corners\n"
|
|
" Try to detect windows with rounded corners and don't consider\n"
|
|
" them shaped windows. Affects --shadow-ignore-shaped,\n"
|
|
" --unredir-if-possible, and possibly others. You need to turn this\n"
|
|
" on manually if you want to match against rounded_corners in\n"
|
|
" conditions.\n"
|
|
"--detect-client-opacity\n"
|
|
" Detect _NET_WM_OPACITY on client windows, useful for window\n"
|
|
" managers not passing _NET_WM_OPACITY of client windows to frame\n"
|
|
" windows.\n"
|
|
#ifndef CONFIG_XRANDR
|
|
#define WARNING " (AUTODETECTION DISABLED AT COMPILE TIME)"
|
|
#else
|
|
#define WARNING
|
|
#endif
|
|
"--refresh-rate val\n"
|
|
" Specify refresh rate of the screen. If not specified or 0, compton\n"
|
|
" will try detecting this with X RandR extension."WARNING"\n"
|
|
"--vsync vsync-method\n"
|
|
" Set VSync method. There are up to 4 VSync methods currently available.\n"
|
|
" none = No VSync\n"
|
|
#undef WARNING
|
|
#ifndef CONFIG_VSYNC_DRM
|
|
#define WARNING WARNING_DISABLED
|
|
#else
|
|
#define WARNING
|
|
#endif
|
|
" drm = VSync with DRM_IOCTL_WAIT_VBLANK. May only work on some\n"
|
|
" drivers." WARNING "\n"
|
|
#undef WARNING
|
|
#ifndef CONFIG_VSYNC_OPENGL
|
|
#define WARNING WARNING_DISABLED
|
|
#else
|
|
#define WARNING
|
|
#endif
|
|
" opengl = Try to VSync with SGI_video_sync OpenGL extension. Only\n"
|
|
" work on some drivers." WARNING"\n"
|
|
" opengl-oml = Try to VSync with OML_sync_control OpenGL extension.\n"
|
|
" Only work on some drivers. Experimental." WARNING"\n"
|
|
" opengl-swc = Try to VSync with SGI_swap_control OpenGL extension.\n"
|
|
" Only work on some drivers. Works only with GLX backend.\n"
|
|
" Does not actually control paint timing, only buffer swap is\n"
|
|
" affected, so it doesn't have the effect of --sw-opti unlike\n"
|
|
" other methods." WARNING "\n"
|
|
" opengl-mswc = Try to VSync with MESA_swap_control OpenGL\n"
|
|
" extension. Basically the same as opengl-swc above, except the\n"
|
|
" extension we use." WARNING "\n"
|
|
"--vsync-aggressive\n"
|
|
" Attempt to send painting request before VBlank and do XFlush()\n"
|
|
" during VBlank. This switch may be lifted out at any moment.\n"
|
|
"--alpha-step val\n"
|
|
" X Render backend: Step for pregenerating alpha pictures. \n"
|
|
" 0.01 - 1.0. Defaults to 0.03.\n"
|
|
"--dbe\n"
|
|
" Enable DBE painting mode, intended to use with VSync to\n"
|
|
" (hopefully) eliminate tearing.\n"
|
|
"--paint-on-overlay\n"
|
|
" Painting on X Composite overlay window.\n"
|
|
"--sw-opti\n"
|
|
" Limit compton to repaint at most once every 1 / refresh_rate\n"
|
|
" second to boost performance.\n"
|
|
"--use-ewmh-active-win\n"
|
|
" Use _NET_WM_ACTIVE_WINDOW on the root window to determine which\n"
|
|
" window is focused instead of using FocusIn/Out events.\n"
|
|
"--respect-prop-shadow\n"
|
|
" Respect _TDE_WM_WINDOW_SHADOW. This a prototype-level feature, which\n"
|
|
" you must not rely on.\n"
|
|
"--unredir-if-possible\n"
|
|
" Unredirect all windows if a full-screen opaque window is\n"
|
|
" detected, to maximize performance for full-screen windows.\n"
|
|
"--unredir-if-possible-delay ms\n"
|
|
" Delay before unredirecting the window, in milliseconds.\n"
|
|
" Defaults to 0.\n"
|
|
"--unredir-if-possible-exclude condition\n"
|
|
" Conditions of windows that shouldn't be considered full-screen\n"
|
|
" for unredirecting screen.\n"
|
|
"--focus-exclude condition\n"
|
|
" Specify a list of conditions of windows that should always be\n"
|
|
" considered focused.\n"
|
|
"--inactive-dim-fixed\n"
|
|
" Use fixed inactive dim value.\n"
|
|
"--detect-transient\n"
|
|
" Use WM_TRANSIENT_FOR to group windows, and consider windows in\n"
|
|
" the same group focused at the same time.\n"
|
|
"--detect-client-leader\n"
|
|
" Use WM_CLIENT_LEADER to group windows, and consider windows in\n"
|
|
" the same group focused at the same time. WM_TRANSIENT_FOR has\n"
|
|
" higher priority if --detect-transient is enabled, too.\n"
|
|
"--blur-background\n"
|
|
" Blur background of semi-transparent / ARGB windows. Bad in\n"
|
|
" performance. The switch name may change without prior\n"
|
|
" notifications.\n"
|
|
"--blur-background-frame\n"
|
|
" Blur background of windows when the window frame is not opaque.\n"
|
|
" Implies --blur-background. Bad in performance. The switch name\n"
|
|
" may change.\n"
|
|
"--blur-background-fixed\n"
|
|
" Use fixed blur strength instead of adjusting according to window\n"
|
|
" opacity.\n"
|
|
"--blur-kern matrix\n"
|
|
" Specify the blur convolution kernel, with the following format:\n"
|
|
" WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5...\n"
|
|
" The element in the center must not be included, it will be forever\n"
|
|
" 1.0 or changing based on opacity, depending on whether you have\n"
|
|
" --blur-background-fixed.\n"
|
|
" A 7x7 Gaussian blur kernel looks like:\n"
|
|
" --blur-kern '7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003'\n"
|
|
" Up to 4 blur kernels may be specified, separated with semicolon, for\n"
|
|
" multi-pass blur.\n"
|
|
" May also be one the predefined kernels: 3x3box (default), 5x5box,\n"
|
|
" 7x7box, 3x3gaussian, 5x5gaussian, 7x7gaussian, 9x9gaussian,\n"
|
|
" 11x11gaussian.\n"
|
|
"--blur-background-exclude condition\n"
|
|
" Exclude conditions for background blur.\n"
|
|
"--greyscale-background\n"
|
|
" Set background of semi-transparent / ARGB windows to greyscale.\n"
|
|
" The switch name may change without prior notifications.\n"
|
|
"--greyscale-background-exclude condition\n"
|
|
" Exclude conditions for greyscale background.\n"
|
|
"--resize-damage integer\n"
|
|
" Resize damaged region by a specific number of pixels. A positive\n"
|
|
" value enlarges it while a negative one shrinks it. Useful for\n"
|
|
" fixing the line corruption issues of blur. May or may not\n"
|
|
" work with --glx-no-stencil. Shrinking doesn't function correctly.\n"
|
|
"--invert-color-include condition\n"
|
|
" Specify a list of conditions of windows that should be painted with\n"
|
|
" inverted color. Resource-hogging, and is not well tested.\n"
|
|
"--opacity-rule opacity:condition\n"
|
|
" Specify a list of opacity rules, in the format \"PERCENT:PATTERN\",\n"
|
|
" like \'50:name *= \"Firefox\"'. compton-trans is recommended over\n"
|
|
" this. Note we do not distinguish 100% and unset, and we don't make\n"
|
|
" any guarantee about possible conflicts with other programs that set\n"
|
|
" _NET_WM_WINDOW_OPACITY on frame or client windows.\n"
|
|
"--shadow-exclude-reg geometry\n"
|
|
" Specify a X geometry that describes the region in which shadow\n"
|
|
" should not be painted in, such as a dock window region.\n"
|
|
" Use --shadow-exclude-reg \'x10+0-0\', for example, if the 10 pixels\n"
|
|
" on the bottom of the screen should not have shadows painted on.\n"
|
|
#undef WARNING
|
|
#ifndef CONFIG_XINERAMA
|
|
#define WARNING WARNING_DISABLED
|
|
#else
|
|
#define WARNING
|
|
#endif
|
|
"--xinerama-shadow-crop\n"
|
|
" Crop shadow of a window fully on a particular Xinerama screen to the\n"
|
|
" screen." WARNING "\n"
|
|
"--backend backend\n"
|
|
" Choose backend. Possible choices are xrender, glx, and\n"
|
|
" xr_glx_hybrid" WARNING ".\n"
|
|
"--glx-no-stencil\n"
|
|
" GLX backend: Avoid using stencil buffer. Might cause issues\n"
|
|
" when rendering transparent content. My tests show a 15% performance\n"
|
|
" boost.\n"
|
|
"--glx-copy-from-front\n"
|
|
" GLX backend: Copy unmodified regions from front buffer instead of\n"
|
|
" redrawing them all. My tests with nvidia-drivers show a 5% decrease\n"
|
|
" in performance when the whole screen is modified, but a 30% increase\n"
|
|
" when only 1/4 is. My tests on nouveau show terrible slowdown. Could\n"
|
|
" work with --glx-swap-method but not --glx-use-copysubbuffermesa.\n"
|
|
"--glx-use-copysubbuffermesa\n"
|
|
" GLX backend: Use MESA_copy_sub_buffer to do partial screen update.\n"
|
|
" My tests on nouveau shows a 200% performance boost when only 1/4 of\n"
|
|
" the screen is updated. May break VSync and is not available on some\n"
|
|
" drivers. Overrides --glx-copy-from-front.\n"
|
|
"--glx-no-rebind-pixmap\n"
|
|
" GLX backend: Avoid rebinding pixmap on window damage. Probably\n"
|
|
" could improve performance on rapid window content changes, but is\n"
|
|
" known to break things on some drivers (LLVMpipe, xf86-video-intel,\n"
|
|
" etc.).\n"
|
|
"--glx-swap-method undefined/copy/exchange/3/4/5/6/buffer-age\n"
|
|
" GLX backend: GLX buffer swap method we assume. Could be\n"
|
|
" undefined (0), copy (1), exchange (2), 3-6, or buffer-age (-1).\n"
|
|
" \"undefined\" is the slowest and the safest, and the default value.\n"
|
|
" 1 is fastest, but may fail on some drivers, 2-6 are gradually slower\n"
|
|
" but safer (6 is still faster than 0). -1 means auto-detect using\n"
|
|
" GLX_EXT_buffer_age, supported by some drivers. Useless with\n"
|
|
" --glx-use-copysubbuffermesa.\n"
|
|
"--glx-use-gpushader4\n"
|
|
" GLX backend: Use GL_EXT_gpu_shader4 for some optimization on blur\n"
|
|
" GLSL code. My tests on GTX 670 show no noticeable effect.\n"
|
|
"--xrender-sync\n"
|
|
" Attempt to synchronize client applications' draw calls with XSync(),\n"
|
|
" used on GLX backend to ensure up-to-date window content is painted.\n"
|
|
#undef WARNING
|
|
#ifndef CONFIG_XSYNC
|
|
#define WARNING WARNING_DISABLED
|
|
#else
|
|
#define WARNING
|
|
#endif
|
|
"--xrender-sync-fence\n"
|
|
" Additionally use X Sync fence to sync clients' draw calls. Needed\n"
|
|
" on nvidia-drivers with GLX backend for some users." WARNING "\n"
|
|
#undef WARNING
|
|
#ifndef CONFIG_DBUS
|
|
#define WARNING WARNING_DISABLED
|
|
#else
|
|
#define WARNING
|
|
#endif
|
|
"--glx-fshader-win shader\n"
|
|
" GLX backend: Use specified GLSL fragment shader for rendering window\n"
|
|
" contents.\n"
|
|
"--force-win-blend\n"
|
|
" Force all windows to be painted with blending. Useful if you have a\n"
|
|
" --glx-fshader-win that could turn opaque pixels transparent.\n"
|
|
"--dbus\n"
|
|
" Enable remote control via D-Bus. See the D-BUS API section in the\n"
|
|
" man page for more details." WARNING "\n"
|
|
"--benchmark cycles\n"
|
|
" Benchmark mode. Repeatedly paint until reaching the specified cycles.\n"
|
|
"--benchmark-wid window-id\n"
|
|
" Specify window ID to repaint in benchmark mode. If omitted or is 0,\n"
|
|
" the whole screen is repainted.\n"
|
|
;
|
|
FILE *f = (ret ? stderr: stdout);
|
|
fputs(usage_text, f);
|
|
#undef WARNING
|
|
#undef WARNING_DISABLED
|
|
|
|
exit(ret);
|
|
}
|
|
|
|
/**
|
|
* Register a window as symbol, and initialize GLX context if wanted.
|
|
*/
|
|
static bool
|
|
register_cm(session_t *ps) {
|
|
assert(!ps->reg_win);
|
|
|
|
ps->reg_win = XCreateSimpleWindow(ps->dpy, ps->root, 0, 0, 1, 1, 0,
|
|
None, None);
|
|
|
|
if (!ps->reg_win) {
|
|
printf_errf("(): Failed to create window.");
|
|
return false;
|
|
}
|
|
|
|
// Unredirect the window if it's redirected, just in case
|
|
if (ps->redirected)
|
|
XCompositeUnredirectWindow(ps->dpy, ps->reg_win, CompositeRedirectManual);
|
|
|
|
{
|
|
XClassHint *h = XAllocClassHint();
|
|
if (h) {
|
|
h->res_name = "compton";
|
|
h->res_class = "xcompmgr";
|
|
}
|
|
Xutf8SetWMProperties(ps->dpy, ps->reg_win, "xcompmgr", "xcompmgr",
|
|
NULL, 0, NULL, NULL, h);
|
|
cxfree(h);
|
|
}
|
|
|
|
// Set _NET_WM_PID
|
|
{
|
|
long pid = getpid();
|
|
if (!XChangeProperty(ps->dpy, ps->reg_win,
|
|
get_atom(ps, "_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace,
|
|
(unsigned char *) &pid, 1)) {
|
|
printf_errf("(): Failed to set _NET_WM_PID.");
|
|
}
|
|
}
|
|
|
|
// Set COMPTON_VERSION
|
|
if (!wid_set_text_prop(ps, ps->reg_win, get_atom(ps, "COMPTON_VERSION"), COMPTON_VERSION)) {
|
|
printf_errf("(): Failed to set COMPTON_VERSION.");
|
|
}
|
|
|
|
// Acquire X Selection _NET_WM_CM_S?
|
|
if (!ps->o.no_x_selection) {
|
|
unsigned len = strlen(REGISTER_PROP) + 2;
|
|
int s = ps->scr;
|
|
|
|
while (s >= 10) {
|
|
++len;
|
|
s /= 10;
|
|
}
|
|
|
|
Window w;
|
|
Atom a;
|
|
static char net_wm_cm[] = "_NET_WM_CM_Sxx";
|
|
|
|
snprintf (net_wm_cm, sizeof (net_wm_cm), "_NET_WM_CM_S%d", ps->scr);
|
|
a = XInternAtom (ps->dpy, net_wm_cm, False);
|
|
|
|
char *buf = malloc(len);
|
|
snprintf(buf, len, REGISTER_PROP "%d", ps->scr);
|
|
buf[len - 1] = '\0';
|
|
// setting this causes compton to abort on TDE login
|
|
// XSetSelectionOwner(ps->dpy, get_atom(ps, buf), ps->reg_win, 0);
|
|
free(buf);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Reopen streams for logging.
|
|
*/
|
|
static bool
|
|
ostream_reopen(session_t *ps, const char *path) {
|
|
if (!path)
|
|
path = ps->o.logpath;
|
|
if (!path)
|
|
path = "/dev/null";
|
|
|
|
bool success = freopen(path, "a", stdout);
|
|
success = freopen(path, "a", stderr) && success;
|
|
if (!success)
|
|
printf_errfq(1, "(%s): freopen() failed.", path);
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* Fork program to background and disable all I/O streams.
|
|
*/
|
|
static inline bool
|
|
fork_after(session_t *ps) {
|
|
if (getppid() == 1)
|
|
return true;
|
|
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
// GLX context must be released and reattached on fork
|
|
if (glx_has_context(ps) && !glXMakeCurrent(ps->dpy, None, NULL)) {
|
|
printf_errf("(): Failed to detach GLx context.");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
int pid = fork();
|
|
|
|
if (-1 == pid) {
|
|
printf_errf("(): fork() failed.");
|
|
return false;
|
|
}
|
|
|
|
if (pid > 0) _exit(0);
|
|
|
|
setsid();
|
|
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (glx_has_context(ps)
|
|
&& !glXMakeCurrent(ps->dpy, get_tgt_window(ps), ps->psglx->context)) {
|
|
printf_errf("(): Failed to make GLX context current.");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Mainly to suppress the _FORTIFY_SOURCE warning
|
|
bool success = freopen("/dev/null", "r", stdin);
|
|
if (!success) {
|
|
printf_errf("(): freopen() failed.");
|
|
return false;
|
|
}
|
|
success = ostream_reopen(ps, NULL);
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* Write PID to a file.
|
|
*/
|
|
static inline bool
|
|
write_pid(session_t *ps) {
|
|
if (!ps->o.write_pid_path)
|
|
return true;
|
|
|
|
FILE *f = fopen(ps->o.write_pid_path, "w");
|
|
if (unlikely(!f)) {
|
|
printf_errf("(): Failed to write PID to \"%s\".", ps->o.write_pid_path);
|
|
return false;
|
|
}
|
|
|
|
fprintf(f, "%ld\n", (long) getpid());
|
|
fclose(f);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parse a long number.
|
|
*/
|
|
static inline bool
|
|
parse_long(const char *s, long *dest) {
|
|
const char *endptr = NULL;
|
|
long val = strtol(s, (char **) &endptr, 0);
|
|
if (!endptr || endptr == s) {
|
|
printf_errf("(\"%s\"): Invalid number.", s);
|
|
return false;
|
|
}
|
|
while (isspace(*endptr))
|
|
++endptr;
|
|
if (*endptr) {
|
|
printf_errf("(\"%s\"): Trailing characters.", s);
|
|
return false;
|
|
}
|
|
*dest = val;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parse a floating-point number in matrix.
|
|
*/
|
|
static inline const char *
|
|
parse_matrix_readnum(const char *src, double *dest) {
|
|
char *pc = NULL;
|
|
double val = strtod(src, &pc);
|
|
if (!pc || pc == src) {
|
|
printf_errf("(\"%s\"): No number found.", src);
|
|
return src;
|
|
}
|
|
|
|
while (*pc && (isspace(*pc) || ',' == *pc))
|
|
++pc;
|
|
|
|
*dest = val;
|
|
|
|
return pc;
|
|
}
|
|
|
|
/**
|
|
* Parse a matrix.
|
|
*/
|
|
static inline XFixed *
|
|
parse_matrix(session_t *ps, const char *src, const char **endptr) {
|
|
int wid = 0, hei = 0;
|
|
const char *pc = NULL;
|
|
XFixed *matrix = NULL;
|
|
|
|
// Get matrix width and height
|
|
{
|
|
double val = 0.0;
|
|
if (src == (pc = parse_matrix_readnum(src, &val)))
|
|
goto parse_matrix_err;
|
|
src = pc;
|
|
wid = val;
|
|
if (src == (pc = parse_matrix_readnum(src, &val)))
|
|
goto parse_matrix_err;
|
|
src = pc;
|
|
hei = val;
|
|
}
|
|
|
|
// Validate matrix width and height
|
|
if (wid <= 0 || hei <= 0) {
|
|
printf_errf("(): Invalid matrix width/height.");
|
|
goto parse_matrix_err;
|
|
}
|
|
if (!(wid % 2 && hei % 2)) {
|
|
printf_errf("(): Width/height not odd.");
|
|
goto parse_matrix_err;
|
|
}
|
|
if (wid > 16 || hei > 16) {
|
|
printf_errf("(): Matrix width/height too large.");
|
|
goto parse_matrix_err;
|
|
}
|
|
|
|
// Allocate memory
|
|
matrix = calloc(wid * hei + 2, sizeof(XFixed));
|
|
if (!matrix) {
|
|
printf_errf("(): Failed to allocate memory for matrix.");
|
|
goto parse_matrix_err;
|
|
}
|
|
|
|
// Read elements
|
|
{
|
|
int skip = hei / 2 * wid + wid / 2;
|
|
bool hasneg = false;
|
|
for (int i = 0; i < wid * hei; ++i) {
|
|
// Ignore the center element
|
|
if (i == skip) {
|
|
matrix[2 + i] = XDoubleToFixed(0);
|
|
continue;
|
|
}
|
|
double val = 0;
|
|
if (src == (pc = parse_matrix_readnum(src, &val)))
|
|
goto parse_matrix_err;
|
|
src = pc;
|
|
if (val < 0) hasneg = true;
|
|
matrix[2 + i] = XDoubleToFixed(val);
|
|
}
|
|
if (BKEND_XRENDER == ps->o.backend && hasneg)
|
|
printf_errf("(): A convolution kernel with negative values "
|
|
"may not work properly under X Render backend.");
|
|
}
|
|
|
|
// Detect trailing characters
|
|
for ( ;*pc && ';' != *pc; ++pc)
|
|
if (!isspace(*pc) && ',' != *pc) {
|
|
printf_errf("(): Trailing characters in matrix string.");
|
|
goto parse_matrix_err;
|
|
}
|
|
|
|
// Jump over spaces after ';'
|
|
if (';' == *pc) {
|
|
++pc;
|
|
while (*pc && isspace(*pc))
|
|
++pc;
|
|
}
|
|
|
|
// Require an end of string if endptr is not provided, otherwise
|
|
// copy end pointer to endptr
|
|
if (endptr)
|
|
*endptr = pc;
|
|
else if (*pc) {
|
|
printf_errf("(): Only one matrix expected.");
|
|
goto parse_matrix_err;
|
|
}
|
|
|
|
// Fill in width and height
|
|
matrix[0] = XDoubleToFixed(wid);
|
|
matrix[1] = XDoubleToFixed(hei);
|
|
|
|
return matrix;
|
|
|
|
parse_matrix_err:
|
|
free(matrix);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Parse a convolution kernel.
|
|
*/
|
|
static inline XFixed *
|
|
parse_conv_kern(session_t *ps, const char *src, const char **endptr) {
|
|
return parse_matrix(ps, src, endptr);
|
|
}
|
|
|
|
/**
|
|
* Parse a list of convolution kernels.
|
|
*/
|
|
static bool
|
|
parse_conv_kern_lst(session_t *ps, const char *src, XFixed **dest, int max) {
|
|
static const struct {
|
|
const char *name;
|
|
const char *kern_str;
|
|
} CONV_KERN_PREDEF[] = {
|
|
{ "3x3box", "3,3,1,1,1,1,1,1,1,1," },
|
|
{ "5x5box", "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1," },
|
|
{ "7x7box", "7,7,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1," },
|
|
{ "3x3gaussian", "3,3,0.243117,0.493069,0.243117,0.493069,0.493069,0.243117,0.493069,0.243117," },
|
|
{ "5x5gaussian", "5,5,0.003493,0.029143,0.059106,0.029143,0.003493,0.029143,0.243117,0.493069,0.243117,0.029143,0.059106,0.493069,0.493069,0.059106,0.029143,0.243117,0.493069,0.243117,0.029143,0.003493,0.029143,0.059106,0.029143,0.003493," },
|
|
{ "7x7gaussian", "7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003," },
|
|
{ "9x9gaussian", "9,9,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000006,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000012,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000012,0.000006,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000001,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0.000000," },
|
|
{ "11x11gaussian", "11,11,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0.000000,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0.000000,0.000006,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000000,0.000000,0.000012,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000012,0.000000,0.000000,0.000006,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000000,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0.000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000," },
|
|
};
|
|
for (int i = 0;
|
|
i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i)
|
|
if (!strcmp(CONV_KERN_PREDEF[i].name, src))
|
|
return parse_conv_kern_lst(ps, CONV_KERN_PREDEF[i].kern_str, dest, max);
|
|
|
|
int i = 0;
|
|
const char *pc = src;
|
|
|
|
// Free old kernels
|
|
for (i = 0; i < max; ++i) {
|
|
free(dest[i]);
|
|
dest[i] = NULL;
|
|
}
|
|
|
|
// Continue parsing until the end of source string
|
|
i = 0;
|
|
while (pc && *pc && i < max - 1) {
|
|
if (!(dest[i++] = parse_conv_kern(ps, pc, &pc)))
|
|
return false;
|
|
}
|
|
|
|
if (*pc) {
|
|
printf_errf("(): Too many blur kernels!");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parse a X geometry.
|
|
*/
|
|
static inline bool
|
|
parse_geometry(session_t *ps, const char *src, geometry_t *dest) {
|
|
geometry_t geom = { .wid = -1, .hei = -1, .x = -1, .y = -1 };
|
|
long val = 0L;
|
|
char *endptr = NULL;
|
|
|
|
#define T_STRIPSPACE() do { \
|
|
while (*src && isspace(*src)) ++src; \
|
|
if (!*src) goto parse_geometry_end; \
|
|
} while(0)
|
|
|
|
T_STRIPSPACE();
|
|
|
|
// Parse width
|
|
// Must be base 10, because "0x0..." may appear
|
|
if (!('+' == *src || '-' == *src)) {
|
|
val = strtol(src, &endptr, 10);
|
|
if (endptr && src != endptr) {
|
|
geom.wid = val;
|
|
assert(geom.wid >= 0);
|
|
src = endptr;
|
|
}
|
|
T_STRIPSPACE();
|
|
}
|
|
|
|
// Parse height
|
|
if ('x' == *src) {
|
|
++src;
|
|
val = strtol(src, &endptr, 10);
|
|
if (endptr && src != endptr) {
|
|
geom.hei = val;
|
|
if (geom.hei < 0) {
|
|
printf_errf("(\"%s\"): Invalid height.", src);
|
|
return false;
|
|
}
|
|
src = endptr;
|
|
}
|
|
T_STRIPSPACE();
|
|
}
|
|
|
|
// Parse x
|
|
if ('+' == *src || '-' == *src) {
|
|
val = strtol(src, &endptr, 10);
|
|
if (endptr && src != endptr) {
|
|
geom.x = val;
|
|
if ('-' == *src && geom.x <= 0)
|
|
geom.x -= 2;
|
|
src = endptr;
|
|
}
|
|
T_STRIPSPACE();
|
|
}
|
|
|
|
// Parse y
|
|
if ('+' == *src || '-' == *src) {
|
|
val = strtol(src, &endptr, 10);
|
|
if (endptr && src != endptr) {
|
|
geom.y = val;
|
|
if ('-' == *src && geom.y <= 0)
|
|
geom.y -= 2;
|
|
src = endptr;
|
|
}
|
|
T_STRIPSPACE();
|
|
}
|
|
|
|
if (*src) {
|
|
printf_errf("(\"%s\"): Trailing characters.", src);
|
|
return false;
|
|
}
|
|
|
|
parse_geometry_end:
|
|
*dest = geom;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parse a list of opacity rules.
|
|
*/
|
|
static inline bool
|
|
parse_rule_opacity(session_t *ps, const char *src) {
|
|
#ifdef CONFIG_C2
|
|
// Find opacity value
|
|
char *endptr = NULL;
|
|
long val = strtol(src, &endptr, 0);
|
|
if (!endptr || endptr == src) {
|
|
printf_errf("(\"%s\"): No opacity specified?", src);
|
|
return false;
|
|
}
|
|
if (val > 100 || val < 0) {
|
|
printf_errf("(\"%s\"): Opacity %ld invalid.", src, val);
|
|
return false;
|
|
}
|
|
|
|
// Skip over spaces
|
|
while (*endptr && isspace(*endptr))
|
|
++endptr;
|
|
if (':' != *endptr) {
|
|
printf_errf("(\"%s\"): Opacity terminator not found.", src);
|
|
return false;
|
|
}
|
|
++endptr;
|
|
|
|
// Parse pattern
|
|
// I hope 1-100 is acceptable for (void *)
|
|
return c2_parsed(ps, &ps->o.opacity_rules, endptr, (void *) val);
|
|
#else
|
|
printf_errf("(\"%s\"): Condition support not compiled in.", src);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#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-tde.conf";
|
|
const static char *config_filename_legacy = "/.compton-tde.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;
|
|
|
|
uid_t uid = getuid();
|
|
struct passwd *pw = getpwuid(uid);
|
|
|
|
if (path) {
|
|
f = fopen(path, "r");
|
|
if (f && ppath)
|
|
*ppath = 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;
|
|
}
|
|
|
|
// Then check user configuration files in the system-defined home directory
|
|
if (pw != NULL) {
|
|
path = mstrjoin(pw->pw_dir, config_filename);
|
|
f = fopen(path, "r");
|
|
if (f && ppath)
|
|
*ppath = path;
|
|
else
|
|
free(path);
|
|
if (f)
|
|
return f;
|
|
|
|
path = mstrjoin(pw->pw_dir, 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 condition list in configuration file.
|
|
*/
|
|
static inline void
|
|
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) {
|
|
// Parse an array of options
|
|
if (config_setting_is_array(setting)) {
|
|
int i = config_setting_length(setting);
|
|
while (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(ps, pcondlst, config_setting_get_string(setting));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse an opacity rule list in configuration file.
|
|
*/
|
|
static inline void
|
|
parse_cfg_condlst_opct(session_t *ps, const config_t *pcfg, const char *name) {
|
|
config_setting_t *setting = config_lookup(pcfg, name);
|
|
if (setting) {
|
|
// Parse an array of options
|
|
if (config_setting_is_array(setting)) {
|
|
int i = config_setting_length(setting);
|
|
while (i--)
|
|
if (!parse_rule_opacity(ps, config_setting_get_string_elem(setting,
|
|
i)))
|
|
exit(1);
|
|
}
|
|
// Treat it as a single pattern if it's a string
|
|
else if (CONFIG_TYPE_STRING == config_setting_type(setting)) {
|
|
parse_rule_opacity(ps, config_setting_get_string(setting));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a configuration file from default location.
|
|
*/
|
|
static void
|
|
parse_config(session_t *ps, struct options_tmp *pcfgtmp) {
|
|
char *path = NULL;
|
|
FILE *f;
|
|
config_t cfg;
|
|
int ival = 0;
|
|
double dval = 0.0;
|
|
// libconfig manages string memory itself, so no need to manually free
|
|
// anything
|
|
const char *sval = NULL;
|
|
|
|
f = open_config_file(ps->o.config_file, &path);
|
|
if (!f) {
|
|
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;
|
|
}
|
|
|
|
config_init(&cfg);
|
|
#ifndef CONFIG_LIBCONFIG_LEGACY
|
|
{
|
|
// dirname() could modify the original string, thus we must pass a
|
|
// copy
|
|
char *path2 = mstrcpy(path);
|
|
char *parent = dirname(path2);
|
|
|
|
if (parent)
|
|
config_set_include_dir(&cfg, parent);
|
|
|
|
free(path2);
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
|
|
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
|
|
|
|
// -D (fade_delta)
|
|
if (lcfg_lookup_int(&cfg, "fade-delta", &ival))
|
|
ps->o.fade_delta = ival;
|
|
// -I (fade_in_step)
|
|
if (config_lookup_float(&cfg, "fade-in-step", &dval))
|
|
ps->o.fade_in_step = normalize_d(dval) * OPAQUE;
|
|
// -O (fade_out_step)
|
|
if (config_lookup_float(&cfg, "fade-out-step", &dval))
|
|
ps->o.fade_out_step = normalize_d(dval) * OPAQUE;
|
|
// -r (shadow_radius)
|
|
lcfg_lookup_int(&cfg, "shadow-radius", &ps->o.shadow_radius);
|
|
// -o (shadow_opacity)
|
|
config_lookup_float(&cfg, "shadow-opacity", &ps->o.shadow_opacity);
|
|
// -l (shadow_offset_x)
|
|
lcfg_lookup_int(&cfg, "shadow-offset-x", &ps->o.shadow_offset_x);
|
|
// -t (shadow_offset_y)
|
|
lcfg_lookup_int(&cfg, "shadow-offset-y", &ps->o.shadow_offset_y);
|
|
// -i (inactive_opacity)
|
|
if (config_lookup_float(&cfg, "inactive-opacity", &dval))
|
|
ps->o.inactive_opacity = normalize_d(dval) * OPAQUE;
|
|
// --active_opacity
|
|
if (config_lookup_float(&cfg, "active-opacity", &dval))
|
|
ps->o.active_opacity = normalize_d(dval) * OPAQUE;
|
|
// -e (frame_opacity)
|
|
config_lookup_float(&cfg, "frame-opacity", &ps->o.frame_opacity);
|
|
// -z (clear_shadow)
|
|
lcfg_lookup_bool(&cfg, "clear-shadow", &ps->o.clear_shadow);
|
|
// -c (shadow_enable)
|
|
if (config_lookup_bool(&cfg, "shadow", &ival) && ival)
|
|
wintype_arr_enable(ps->o.wintype_shadow);
|
|
// -C (no_dock_shadow)
|
|
lcfg_lookup_bool(&cfg, "no-dock-shadow", &pcfgtmp->no_dock_shadow);
|
|
// -G (no_dnd_shadow)
|
|
lcfg_lookup_bool(&cfg, "no-dnd-shadow", &pcfgtmp->no_dnd_shadow);
|
|
// -m (menu_opacity)
|
|
config_lookup_float(&cfg, "menu-opacity", &pcfgtmp->menu_opacity);
|
|
// -f (fading_enable)
|
|
if (config_lookup_bool(&cfg, "fading", &ival) && ival)
|
|
wintype_arr_enable(ps->o.wintype_fade);
|
|
// --no-fading-open-close
|
|
lcfg_lookup_bool(&cfg, "no-fading-openclose", &ps->o.no_fading_openclose);
|
|
// --no-fading-destroyed-argb
|
|
lcfg_lookup_bool(&cfg, "no-fading-destroyed-argb",
|
|
&ps->o.no_fading_destroyed_argb);
|
|
// --no-fading-opacitychange
|
|
lcfg_lookup_bool(&cfg, "no-fading-opacitychange", &ps->o.no_fading_opacitychange);
|
|
// --shadow-red
|
|
config_lookup_float(&cfg, "shadow-red", &ps->o.shadow_red);
|
|
// --shadow-green
|
|
config_lookup_float(&cfg, "shadow-green", &ps->o.shadow_green);
|
|
// --shadow-blue
|
|
config_lookup_float(&cfg, "shadow-blue", &ps->o.shadow_blue);
|
|
// --shadow-exclude-reg
|
|
if (config_lookup_string(&cfg, "shadow-exclude-reg", &sval)
|
|
&& !parse_geometry(ps, sval, &ps->o.shadow_exclude_reg_geom))
|
|
exit(1);
|
|
// --inactive-opacity-override
|
|
lcfg_lookup_bool(&cfg, "inactive-opacity-override",
|
|
&ps->o.inactive_opacity_override);
|
|
// --inactive-dim
|
|
config_lookup_float(&cfg, "inactive-dim", &ps->o.inactive_dim);
|
|
// --mark-wmwin-focused
|
|
lcfg_lookup_bool(&cfg, "mark-wmwin-focused", &ps->o.mark_wmwin_focused);
|
|
// --mark-ovredir-focused
|
|
lcfg_lookup_bool(&cfg, "mark-ovredir-focused",
|
|
&ps->o.mark_ovredir_focused);
|
|
// --shadow-ignore-shaped
|
|
lcfg_lookup_bool(&cfg, "shadow-ignore-shaped",
|
|
&ps->o.shadow_ignore_shaped);
|
|
// --detect-rounded-corners
|
|
lcfg_lookup_bool(&cfg, "detect-rounded-corners",
|
|
&ps->o.detect_rounded_corners);
|
|
// --xinerama-shadow-crop
|
|
lcfg_lookup_bool(&cfg, "xinerama-shadow-crop",
|
|
&ps->o.xinerama_shadow_crop);
|
|
// --detect-client-opacity
|
|
lcfg_lookup_bool(&cfg, "detect-client-opacity",
|
|
&ps->o.detect_client_opacity);
|
|
// --refresh-rate
|
|
lcfg_lookup_int(&cfg, "refresh-rate", &ps->o.refresh_rate);
|
|
// --vsync
|
|
if (config_lookup_string(&cfg, "vsync", &sval) && !parse_vsync(ps, sval))
|
|
exit(1);
|
|
// --backend
|
|
if (config_lookup_string(&cfg, "backend", &sval) && !parse_backend(ps, sval))
|
|
exit(1);
|
|
// --alpha-step
|
|
config_lookup_float(&cfg, "alpha-step", &ps->o.alpha_step);
|
|
// --dbe
|
|
lcfg_lookup_bool(&cfg, "dbe", &ps->o.dbe);
|
|
// --paint-on-overlay
|
|
lcfg_lookup_bool(&cfg, "paint-on-overlay", &ps->o.paint_on_overlay);
|
|
// --sw-opti
|
|
lcfg_lookup_bool(&cfg, "sw-opti", &ps->o.sw_opti);
|
|
// --use-ewmh-active-win
|
|
lcfg_lookup_bool(&cfg, "use-ewmh-active-win",
|
|
&ps->o.use_ewmh_active_win);
|
|
// --unredir-if-possible
|
|
lcfg_lookup_bool(&cfg, "unredir-if-possible",
|
|
&ps->o.unredir_if_possible);
|
|
// --unredir-if-possible-delay
|
|
if (lcfg_lookup_int(&cfg, "unredir-if-possible-delay", &ival))
|
|
ps->o.unredir_if_possible_delay = ival;
|
|
// --inactive-dim-fixed
|
|
lcfg_lookup_bool(&cfg, "inactive-dim-fixed", &ps->o.inactive_dim_fixed);
|
|
// --detect-transient
|
|
lcfg_lookup_bool(&cfg, "detect-transient", &ps->o.detect_transient);
|
|
// --detect-client-leader
|
|
lcfg_lookup_bool(&cfg, "detect-client-leader",
|
|
&ps->o.detect_client_leader);
|
|
// --shadow-exclude
|
|
parse_cfg_condlst(ps, &cfg, &ps->o.shadow_blacklist, "shadow-exclude");
|
|
// --fade-exclude
|
|
parse_cfg_condlst(ps, &cfg, &ps->o.fade_blacklist, "fade-exclude");
|
|
// --focus-exclude
|
|
parse_cfg_condlst(ps, &cfg, &ps->o.focus_blacklist, "focus-exclude");
|
|
// --invert-color-include
|
|
parse_cfg_condlst(ps, &cfg, &ps->o.invert_color_list, "invert-color-include");
|
|
// --blur-background-exclude
|
|
parse_cfg_condlst(ps, &cfg, &ps->o.blur_background_blacklist, "blur-background-exclude");
|
|
// --greyscale-background-exclude
|
|
parse_cfg_condlst(ps, &cfg, &ps->o.greyscale_background_blacklist, "greyscale-background-exclude");
|
|
// --opacity-rule
|
|
parse_cfg_condlst_opct(ps, &cfg, "opacity-rule");
|
|
// --unredir-if-possible-exclude
|
|
parse_cfg_condlst(ps, &cfg, &ps->o.unredir_if_possible_blacklist, "unredir-if-possible-exclude");
|
|
// --blur-background
|
|
lcfg_lookup_bool(&cfg, "blur-background", &ps->o.blur_background);
|
|
// --greyscale-background
|
|
lcfg_lookup_bool(&cfg, "greyscale-background", &ps->o.greyscale_background);
|
|
// --blur-background-frame
|
|
lcfg_lookup_bool(&cfg, "blur-background-frame",
|
|
&ps->o.blur_background_frame);
|
|
// --blur-background-fixed
|
|
lcfg_lookup_bool(&cfg, "blur-background-fixed",
|
|
&ps->o.blur_background_fixed);
|
|
// --blur-kern
|
|
if (config_lookup_string(&cfg, "blur-kern", &sval)
|
|
&& !parse_conv_kern_lst(ps, sval, ps->o.blur_kerns, MAX_BLUR_PASS))
|
|
exit(1);
|
|
// --resize-damage
|
|
lcfg_lookup_int(&cfg, "resize-damage", &ps->o.resize_damage);
|
|
// --glx-no-stencil
|
|
lcfg_lookup_bool(&cfg, "glx-no-stencil", &ps->o.glx_no_stencil);
|
|
// --glx-copy-from-front
|
|
lcfg_lookup_bool(&cfg, "glx-copy-from-front", &ps->o.glx_copy_from_front);
|
|
// --glx-use-copysubbuffermesa
|
|
lcfg_lookup_bool(&cfg, "glx-use-copysubbuffermesa", &ps->o.glx_use_copysubbuffermesa);
|
|
// --glx-no-rebind-pixmap
|
|
lcfg_lookup_bool(&cfg, "glx-no-rebind-pixmap", &ps->o.glx_no_rebind_pixmap);
|
|
// --glx-swap-method
|
|
if (config_lookup_string(&cfg, "glx-swap-method", &sval)
|
|
&& !parse_glx_swap_method(ps, sval))
|
|
exit(1);
|
|
// --glx-use-gpushader4
|
|
lcfg_lookup_bool(&cfg, "glx-use-gpushader4", &ps->o.glx_use_gpushader4);
|
|
// --xrender-sync
|
|
lcfg_lookup_bool(&cfg, "xrender-sync", &ps->o.xrender_sync);
|
|
// --xrender-sync-fence
|
|
lcfg_lookup_bool(&cfg, "xrender-sync-fence", &ps->o.xrender_sync_fence);
|
|
// Wintype settings
|
|
{
|
|
wintype_t i;
|
|
|
|
for (i = 0; i < NUM_WINTYPES; ++i) {
|
|
char *str = mstrjoin("wintypes.", WINTYPES[i]);
|
|
config_setting_t *setting = config_lookup(&cfg, str);
|
|
free(str);
|
|
if (setting) {
|
|
if (config_setting_lookup_bool(setting, "shadow", &ival))
|
|
ps->o.wintype_shadow[i] = (bool) ival;
|
|
if (config_setting_lookup_bool(setting, "fade", &ival))
|
|
ps->o.wintype_fade[i] = (bool) ival;
|
|
if (config_setting_lookup_bool(setting, "focus", &ival))
|
|
ps->o.wintype_focus[i] = (bool) ival;
|
|
config_setting_lookup_float(setting, "opacity",
|
|
&ps->o.wintype_opacity[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
config_destroy(&cfg);
|
|
|
|
// Adjust shadow offsets
|
|
ps->o.shadow_offset_x = ((-ps->o.shadow_radius * 7 / 5) - ps->o.shadow_offset_x * ps->o.shadow_radius / 100);
|
|
ps->o.shadow_offset_y = ((-ps->o.shadow_radius * 7 / 5) - ps->o.shadow_offset_y * ps->o.shadow_radius / 100);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Process arguments and configuration files.
|
|
*/
|
|
static void
|
|
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:hvscnfFCaSzGb";
|
|
const static struct option longopts[] = {
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "config", required_argument, NULL, 256 },
|
|
{ "shadow-radius", required_argument, NULL, 'r' },
|
|
{ "shadow-opacity", required_argument, NULL, 'o' },
|
|
{ "shadow-offset-x", required_argument, NULL, 'l' },
|
|
{ "shadow-offset-y", required_argument, NULL, 't' },
|
|
{ "fade-in-step", required_argument, NULL, 'I' },
|
|
{ "fade-out-step", required_argument, NULL, 'O' },
|
|
{ "fade-delta", required_argument, NULL, 'D' },
|
|
{ "menu-opacity", required_argument, NULL, 'm' },
|
|
{ "shadow", no_argument, NULL, 'c' },
|
|
{ "no-dock-shadow", no_argument, NULL, 'C' },
|
|
{ "clear-shadow", no_argument, NULL, 'z' },
|
|
{ "fading", no_argument, NULL, 'f' },
|
|
{ "inactive-opacity", required_argument, NULL, 'i' },
|
|
{ "frame-opacity", required_argument, NULL, 'e' },
|
|
{ "daemon", no_argument, NULL, 'b' },
|
|
{ "no-dnd-shadow", no_argument, NULL, 'G' },
|
|
{ "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 },
|
|
{ "mark-ovredir-focused", no_argument, NULL, 264 },
|
|
{ "no-fading-openclose", no_argument, NULL, 265 },
|
|
{ "shadow-ignore-shaped", no_argument, NULL, 266 },
|
|
{ "detect-rounded-corners", no_argument, NULL, 267 },
|
|
{ "detect-client-opacity", no_argument, NULL, 268 },
|
|
{ "refresh-rate", required_argument, NULL, 269 },
|
|
{ "vsync", required_argument, NULL, 270 },
|
|
{ "alpha-step", required_argument, NULL, 271 },
|
|
{ "dbe", no_argument, NULL, 272 },
|
|
{ "paint-on-overlay", no_argument, NULL, 273 },
|
|
{ "sw-opti", no_argument, NULL, 274 },
|
|
{ "vsync-aggressive", no_argument, NULL, 275 },
|
|
{ "use-ewmh-active-win", no_argument, NULL, 276 },
|
|
{ "respect-prop-shadow", no_argument, NULL, 277 },
|
|
{ "unredir-if-possible", no_argument, NULL, 278 },
|
|
{ "focus-exclude", required_argument, NULL, 279 },
|
|
{ "inactive-dim-fixed", no_argument, NULL, 280 },
|
|
{ "detect-transient", no_argument, NULL, 281 },
|
|
{ "detect-client-leader", no_argument, NULL, 282 },
|
|
{ "blur-background", no_argument, NULL, 283 },
|
|
{ "blur-background-frame", no_argument, NULL, 284 },
|
|
{ "blur-background-fixed", no_argument, NULL, 285 },
|
|
{ "dbus", no_argument, NULL, 286 },
|
|
{ "logpath", required_argument, NULL, 287 },
|
|
{ "invert-color-include", required_argument, NULL, 288 },
|
|
{ "opengl", no_argument, NULL, 289 },
|
|
{ "backend", required_argument, NULL, 290 },
|
|
{ "glx-no-stencil", no_argument, NULL, 291 },
|
|
{ "glx-copy-from-front", no_argument, NULL, 292 },
|
|
{ "benchmark", required_argument, NULL, 293 },
|
|
{ "benchmark-wid", required_argument, NULL, 294 },
|
|
{ "glx-use-copysubbuffermesa", no_argument, NULL, 295 },
|
|
{ "blur-background-exclude", required_argument, NULL, 296 },
|
|
{ "active-opacity", required_argument, NULL, 297 },
|
|
{ "glx-no-rebind-pixmap", no_argument, NULL, 298 },
|
|
{ "glx-swap-method", required_argument, NULL, 299 },
|
|
{ "fade-exclude", required_argument, NULL, 300 },
|
|
{ "blur-kern", required_argument, NULL, 301 },
|
|
{ "resize-damage", required_argument, NULL, 302 },
|
|
{ "glx-use-gpushader4", no_argument, NULL, 303 },
|
|
{ "opacity-rule", required_argument, NULL, 304 },
|
|
{ "shadow-exclude-reg", required_argument, NULL, 305 },
|
|
{ "paint-exclude", required_argument, NULL, 306 },
|
|
{ "xinerama-shadow-crop", no_argument, NULL, 307 },
|
|
{ "unredir-if-possible-exclude", required_argument, NULL, 308 },
|
|
{ "unredir-if-possible-delay", required_argument, NULL, 309 },
|
|
{ "write-pid-path", required_argument, NULL, 310 },
|
|
{ "vsync-use-glfinish", no_argument, NULL, 311 },
|
|
{ "xrender-sync", no_argument, NULL, 312 },
|
|
{ "xrender-sync-fence", no_argument, NULL, 313 },
|
|
{ "show-all-xerrors", no_argument, NULL, 314 },
|
|
{ "no-fading-destroyed-argb", no_argument, NULL, 315 },
|
|
{ "force-win-blend", no_argument, NULL, 316 },
|
|
{ "glx-fshader-win", required_argument, NULL, 317 },
|
|
{ "version", no_argument, NULL, 318 },
|
|
{ "no-x-selection", no_argument, NULL, 319 },
|
|
{ "no-name-pixmap", no_argument, NULL, 320 },
|
|
{ "no-fading-opacitychange", no_argument, NULL, 321 },
|
|
{ "reredir-on-root-change", no_argument, NULL, 731 },
|
|
{ "glx-reinit-on-root-change", no_argument, NULL, 732 },
|
|
{ "greyscale-background", no_argument, NULL, 733 },
|
|
{ "greyscale-background-exclude", required_argument, NULL, 734 },
|
|
// Must terminate with a NULL entry
|
|
{ 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 ('S' == o)
|
|
ps->o.synchronize = true;
|
|
else if (314 == o)
|
|
ps->o.show_all_xerrors = true;
|
|
else if (318 == o) {
|
|
printf("%s\n", COMPTON_VERSION);
|
|
exit(0);
|
|
}
|
|
else if (320 == o)
|
|
ps->o.no_name_pixmap = true;
|
|
else if ('?' == o || ':' == o)
|
|
usage(1);
|
|
}
|
|
|
|
// Check for abundant positional arguments
|
|
if (optind < argc)
|
|
printf_errfq(1, "(): compton doesn't accept positional arguments.");
|
|
|
|
return;
|
|
}
|
|
|
|
struct options_tmp cfgtmp = {
|
|
.no_dock_shadow = false,
|
|
.no_dnd_shadow = false,
|
|
.menu_opacity = 1.0,
|
|
};
|
|
bool shadow_enable = false, fading_enable = false;
|
|
char *lc_numeric_old = mstrcpy(setlocale(LC_NUMERIC, NULL));
|
|
|
|
for (i = 0; i < NUM_WINTYPES; ++i) {
|
|
ps->o.wintype_fade[i] = false;
|
|
ps->o.wintype_shadow[i] = false;
|
|
ps->o.wintype_opacity[i] = 1.0;
|
|
}
|
|
|
|
// Enforce LC_NUMERIC locale "C" here to make sure dots are recognized
|
|
// instead of commas in atof().
|
|
setlocale(LC_NUMERIC, "C");
|
|
|
|
#ifdef CONFIG_LIBCONFIG
|
|
parse_config(ps, &cfgtmp);
|
|
#endif
|
|
|
|
// Parse commandline arguments. Range checking will be done later.
|
|
|
|
optind = 1;
|
|
while (-1 !=
|
|
(o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) {
|
|
long val = 0;
|
|
switch (o) {
|
|
#define P_CASEBOOL(idx, option) case idx: ps->o.option = true; break
|
|
#define P_CASELONG(idx, option) \
|
|
case idx: \
|
|
if (!parse_long(optarg, &val)) exit(1); \
|
|
ps->o.option = val; \
|
|
break
|
|
|
|
// Short options
|
|
case 'h':
|
|
usage(0);
|
|
break;
|
|
case 'v': fprintf (stderr, "%s v%-3.2f\n", argv[0], _TDE_COMP_MGR_VERSION_); my_exit_code=0; exit (0);
|
|
case 'd':
|
|
case 'S':
|
|
case 314:
|
|
case 318:
|
|
case 320:
|
|
break;
|
|
P_CASELONG('D', fade_delta);
|
|
case 'I':
|
|
ps->o.fade_in_step = normalize_d(atof(optarg)) * OPAQUE;
|
|
break;
|
|
case 'O':
|
|
ps->o.fade_out_step = normalize_d(atof(optarg)) * OPAQUE;
|
|
break;
|
|
case 'c':
|
|
shadow_enable = true;
|
|
break;
|
|
case 'C':
|
|
cfgtmp.no_dock_shadow = true;
|
|
break;
|
|
case 'G':
|
|
cfgtmp.no_dnd_shadow = true;
|
|
break;
|
|
case 'm':
|
|
cfgtmp.menu_opacity = atof(optarg);
|
|
break;
|
|
case 'f':
|
|
case 'F':
|
|
fading_enable = true;
|
|
break;
|
|
P_CASELONG('r', shadow_radius);
|
|
case 'o':
|
|
ps->o.shadow_opacity = atof(optarg);
|
|
break;
|
|
P_CASELONG('l', shadow_offset_x);
|
|
P_CASELONG('t', shadow_offset_y);
|
|
case 'i':
|
|
ps->o.inactive_opacity = (normalize_d(atof(optarg)) * OPAQUE);
|
|
break;
|
|
case 'e':
|
|
ps->o.frame_opacity = atof(optarg);
|
|
break;
|
|
P_CASEBOOL('z', clear_shadow);
|
|
case 'n':
|
|
case 'a':
|
|
case 's':
|
|
printf_errfq(1, "(): -n, -a, and -s have been removed.");
|
|
break;
|
|
P_CASEBOOL('b', fork_after_register);
|
|
// Long options
|
|
case 256:
|
|
// --config
|
|
break;
|
|
case 257:
|
|
// --shadow-red
|
|
ps->o.shadow_red = atof(optarg);
|
|
break;
|
|
case 258:
|
|
// --shadow-green
|
|
ps->o.shadow_green = atof(optarg);
|
|
break;
|
|
case 259:
|
|
// --shadow-blue
|
|
ps->o.shadow_blue = atof(optarg);
|
|
break;
|
|
P_CASEBOOL(260, inactive_opacity_override);
|
|
case 261:
|
|
// --inactive-dim
|
|
ps->o.inactive_dim = atof(optarg);
|
|
break;
|
|
P_CASEBOOL(262, mark_wmwin_focused);
|
|
case 263:
|
|
// --shadow-exclude
|
|
condlst_add(ps, &ps->o.shadow_blacklist, optarg);
|
|
break;
|
|
P_CASEBOOL(264, mark_ovredir_focused);
|
|
P_CASEBOOL(265, no_fading_openclose);
|
|
P_CASEBOOL(266, shadow_ignore_shaped);
|
|
P_CASEBOOL(267, detect_rounded_corners);
|
|
P_CASEBOOL(268, detect_client_opacity);
|
|
P_CASELONG(269, refresh_rate);
|
|
case 270:
|
|
// --vsync
|
|
if (!parse_vsync(ps, optarg))
|
|
exit(1);
|
|
break;
|
|
case 271:
|
|
// --alpha-step
|
|
ps->o.alpha_step = atof(optarg);
|
|
break;
|
|
P_CASEBOOL(272, dbe);
|
|
P_CASEBOOL(273, paint_on_overlay);
|
|
P_CASEBOOL(274, sw_opti);
|
|
P_CASEBOOL(275, vsync_aggressive);
|
|
P_CASEBOOL(276, use_ewmh_active_win);
|
|
P_CASEBOOL(277, respect_prop_shadow);
|
|
P_CASEBOOL(278, unredir_if_possible);
|
|
case 279:
|
|
// --focus-exclude
|
|
condlst_add(ps, &ps->o.focus_blacklist, optarg);
|
|
break;
|
|
P_CASEBOOL(280, inactive_dim_fixed);
|
|
P_CASEBOOL(281, detect_transient);
|
|
P_CASEBOOL(282, detect_client_leader);
|
|
P_CASEBOOL(283, blur_background);
|
|
P_CASEBOOL(284, blur_background_frame);
|
|
P_CASEBOOL(285, blur_background_fixed);
|
|
P_CASEBOOL(286, dbus);
|
|
case 287:
|
|
// --logpath
|
|
ps->o.logpath = mstrcpy(optarg);
|
|
break;
|
|
case 288:
|
|
// --invert-color-include
|
|
condlst_add(ps, &ps->o.invert_color_list, optarg);
|
|
break;
|
|
case 289:
|
|
// --opengl
|
|
ps->o.backend = BKEND_GLX;
|
|
break;
|
|
case 290:
|
|
// --backend
|
|
if (!parse_backend(ps, optarg))
|
|
exit(1);
|
|
break;
|
|
P_CASEBOOL(291, glx_no_stencil);
|
|
P_CASEBOOL(292, glx_copy_from_front);
|
|
P_CASELONG(293, benchmark);
|
|
case 294:
|
|
// --benchmark-wid
|
|
ps->o.benchmark_wid = strtol(optarg, NULL, 0);
|
|
break;
|
|
P_CASEBOOL(295, glx_use_copysubbuffermesa);
|
|
case 296:
|
|
// --blur-background-exclude
|
|
condlst_add(ps, &ps->o.blur_background_blacklist, optarg);
|
|
break;
|
|
case 297:
|
|
// --active-opacity
|
|
ps->o.active_opacity = (normalize_d(atof(optarg)) * OPAQUE);
|
|
break;
|
|
P_CASEBOOL(298, glx_no_rebind_pixmap);
|
|
case 299:
|
|
// --glx-swap-method
|
|
if (!parse_glx_swap_method(ps, optarg))
|
|
exit(1);
|
|
break;
|
|
case 300:
|
|
// --fade-exclude
|
|
condlst_add(ps, &ps->o.fade_blacklist, optarg);
|
|
break;
|
|
case 301:
|
|
// --blur-kern
|
|
if (!parse_conv_kern_lst(ps, optarg, ps->o.blur_kerns, MAX_BLUR_PASS))
|
|
exit(1);
|
|
break;
|
|
P_CASELONG(302, resize_damage);
|
|
P_CASEBOOL(303, glx_use_gpushader4);
|
|
case 304:
|
|
// --opacity-rule
|
|
if (!parse_rule_opacity(ps, optarg))
|
|
exit(1);
|
|
break;
|
|
case 305:
|
|
// --shadow-exclude-reg
|
|
if (!parse_geometry(ps, optarg, &ps->o.shadow_exclude_reg_geom))
|
|
exit(1);
|
|
break;
|
|
case 306:
|
|
// --paint-exclude
|
|
condlst_add(ps, &ps->o.paint_blacklist, optarg);
|
|
break;
|
|
P_CASEBOOL(307, xinerama_shadow_crop);
|
|
case 308:
|
|
// --unredir-if-possible-exclude
|
|
condlst_add(ps, &ps->o.unredir_if_possible_blacklist, optarg);
|
|
break;
|
|
P_CASELONG(309, unredir_if_possible_delay);
|
|
case 310:
|
|
// --write-pid-path
|
|
ps->o.write_pid_path = mstrcpy(optarg);
|
|
break;
|
|
P_CASEBOOL(311, vsync_use_glfinish);
|
|
P_CASEBOOL(312, xrender_sync);
|
|
P_CASEBOOL(313, xrender_sync_fence);
|
|
P_CASEBOOL(315, no_fading_destroyed_argb);
|
|
P_CASEBOOL(316, force_win_blend);
|
|
case 317:
|
|
ps->o.glx_fshader_win_str = mstrcpy(optarg);
|
|
break;
|
|
P_CASEBOOL(319, no_x_selection);
|
|
P_CASEBOOL(321, no_fading_opacitychange);
|
|
P_CASEBOOL(731, reredir_on_root_change);
|
|
P_CASEBOOL(732, glx_reinit_on_root_change);
|
|
P_CASEBOOL(733, greyscale_background);
|
|
case 734:
|
|
// --greyscale-background-exclude
|
|
condlst_add(ps, &ps->o.greyscale_background_blacklist, optarg);
|
|
break;
|
|
default:
|
|
usage(1);
|
|
break;
|
|
#undef P_CASEBOOL
|
|
}
|
|
}
|
|
|
|
// Restore LC_NUMERIC
|
|
setlocale(LC_NUMERIC, lc_numeric_old);
|
|
free(lc_numeric_old);
|
|
|
|
// Range checking and option assignments
|
|
ps->o.fade_delta = max_i(ps->o.fade_delta, 1);
|
|
ps->o.shadow_radius = max_i(ps->o.shadow_radius, 1);
|
|
ps->o.shadow_red = normalize_d(ps->o.shadow_red);
|
|
ps->o.shadow_green = normalize_d(ps->o.shadow_green);
|
|
ps->o.shadow_blue = normalize_d(ps->o.shadow_blue);
|
|
ps->o.inactive_dim = normalize_d(ps->o.inactive_dim);
|
|
ps->o.frame_opacity = normalize_d(ps->o.frame_opacity);
|
|
ps->o.shadow_opacity = normalize_d(ps->o.shadow_opacity);
|
|
cfgtmp.menu_opacity = normalize_d(cfgtmp.menu_opacity);
|
|
ps->o.refresh_rate = normalize_i_range(ps->o.refresh_rate, 0, 300);
|
|
ps->o.alpha_step = normalize_d_range(ps->o.alpha_step, 0.01, 1.0);
|
|
if (OPAQUE == ps->o.inactive_opacity) {
|
|
ps->o.inactive_opacity = 0;
|
|
}
|
|
if (OPAQUE == ps->o.active_opacity) {
|
|
ps->o.active_opacity = 0;
|
|
}
|
|
if (shadow_enable)
|
|
wintype_arr_enable(ps->o.wintype_shadow);
|
|
ps->o.wintype_shadow[WINTYPE_DESKTOP] = false;
|
|
if (cfgtmp.no_dock_shadow)
|
|
ps->o.wintype_shadow[WINTYPE_DOCK] = false;
|
|
if (cfgtmp.no_dnd_shadow)
|
|
ps->o.wintype_shadow[WINTYPE_DND] = false;
|
|
if (fading_enable)
|
|
wintype_arr_enable(ps->o.wintype_fade);
|
|
if (1.0 != cfgtmp.menu_opacity) {
|
|
ps->o.wintype_opacity[WINTYPE_DROPDOWN_MENU] = cfgtmp.menu_opacity;
|
|
ps->o.wintype_opacity[WINTYPE_POPUP_MENU] = cfgtmp.menu_opacity;
|
|
}
|
|
|
|
// --blur-background-frame implies --blur-background
|
|
if (ps->o.blur_background_frame)
|
|
ps->o.blur_background = true;
|
|
|
|
if (ps->o.xrender_sync_fence)
|
|
ps->o.xrender_sync = true;
|
|
|
|
// Other variables determined by options
|
|
|
|
// Determine whether we need to track focus changes
|
|
if (ps->o.inactive_opacity || ps->o.active_opacity || ps->o.inactive_dim) {
|
|
ps->o.track_focus = true;
|
|
}
|
|
|
|
// Determine whether we track window grouping
|
|
if (ps->o.detect_transient || ps->o.detect_client_leader) {
|
|
ps->o.track_leader = true;
|
|
}
|
|
|
|
// Fill default blur kernel
|
|
if (ps->o.blur_background && !ps->o.blur_kerns[0]) {
|
|
// Convolution filter parameter (box blur)
|
|
// gaussian or binomial filters are definitely superior, yet looks
|
|
// like they aren't supported as of xorg-server-1.13.0
|
|
const static XFixed convolution_blur[] = {
|
|
// Must convert to XFixed with XDoubleToFixed()
|
|
// Matrix size
|
|
XDoubleToFixed(3), XDoubleToFixed(3),
|
|
// Matrix
|
|
XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1),
|
|
XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1),
|
|
XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1),
|
|
};
|
|
ps->o.blur_kerns[0] = malloc(sizeof(convolution_blur));
|
|
if (!ps->o.blur_kerns[0]) {
|
|
printf_errf("(): Failed to allocate memory for convolution kernel.");
|
|
exit(1);
|
|
}
|
|
memcpy(ps->o.blur_kerns[0], &convolution_blur, sizeof(convolution_blur));
|
|
}
|
|
|
|
rebuild_shadow_exclude_reg(ps);
|
|
|
|
if (ps->o.resize_damage < 0)
|
|
printf_errf("(): Negative --resize-damage does not work correctly.");
|
|
}
|
|
|
|
/**
|
|
* Fetch all required atoms and save them to a session.
|
|
*/
|
|
static void
|
|
init_atoms(session_t *ps) {
|
|
ps->atom_opacity = get_atom(ps, "_NET_WM_WINDOW_OPACITY");
|
|
ps->atom_frame_extents = get_atom(ps, "_NET_FRAME_EXTENTS");
|
|
ps->atom_client = get_atom(ps, "WM_STATE");
|
|
ps->atom_name = XA_WM_NAME;
|
|
ps->atom_name_ewmh = get_atom(ps, "_NET_WM_NAME");
|
|
ps->atom_class = XA_WM_CLASS;
|
|
ps->atom_role = get_atom(ps, "WM_WINDOW_ROLE");
|
|
ps->atom_transient = XA_WM_TRANSIENT_FOR;
|
|
ps->atom_client_leader = get_atom(ps, "WM_CLIENT_LEADER");
|
|
ps->atom_ewmh_active_win = get_atom(ps, "_NET_ACTIVE_WINDOW");
|
|
ps->atom_compton_shadow = get_atom(ps, "_TDE_WM_WINDOW_SHADOW");
|
|
|
|
ps->atom_win_type = get_atom(ps, "_NET_WM_WINDOW_TYPE");
|
|
ps->atoms_wintypes[WINTYPE_UNKNOWN] = 0;
|
|
ps->atoms_wintypes[WINTYPE_DESKTOP] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_DESKTOP");
|
|
ps->atoms_wintypes[WINTYPE_DOCK] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_DOCK");
|
|
ps->atoms_wintypes[WINTYPE_TOOLBAR] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_TOOLBAR");
|
|
ps->atoms_wintypes[WINTYPE_MENU] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_MENU");
|
|
ps->atoms_wintypes[WINTYPE_UTILITY] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_UTILITY");
|
|
ps->atoms_wintypes[WINTYPE_SPLASH] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_SPLASH");
|
|
ps->atoms_wintypes[WINTYPE_DIALOG] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_DIALOG");
|
|
ps->atoms_wintypes[WINTYPE_NORMAL] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_NORMAL");
|
|
ps->atoms_wintypes[WINTYPE_DROPDOWN_MENU] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU");
|
|
ps->atoms_wintypes[WINTYPE_POPUP_MENU] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_POPUP_MENU");
|
|
ps->atoms_wintypes[WINTYPE_TOOLTIP] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_TOOLTIP");
|
|
ps->atoms_wintypes[WINTYPE_NOTIFY] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_NOTIFICATION");
|
|
ps->atoms_wintypes[WINTYPE_COMBO] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_COMBO");
|
|
ps->atoms_wintypes[WINTYPE_DND] = get_atom(ps,
|
|
"_NET_WM_WINDOW_TYPE_DND");
|
|
|
|
ps->atom_win_type_tde_transparent_to_black = get_atom(ps, "_TDE_TRANSPARENT_TO_BLACK");
|
|
ps->atom_win_type_tde_transparent_to_desktop = get_atom(ps, "_TDE_TRANSPARENT_TO_DESKTOP");
|
|
ps->atom_win_type_tde_transparency_filter_greyscale = get_atom(ps, "_TDE_TRANSPARENCY_FILTER_GREYSCALE");
|
|
ps->atom_win_type_tde_transparency_filter_greyscale_blend = get_atom(ps, "_TDE_TRANSPARENCY_FILTER_GREYSCALE_BLEND");
|
|
}
|
|
|
|
#ifdef CONFIG_XRANDR
|
|
/**
|
|
* Update refresh rate info with X Randr extension.
|
|
*/
|
|
static void
|
|
update_refresh_rate(session_t *ps) {
|
|
XRRScreenConfiguration* randr_info;
|
|
|
|
if (!(randr_info = XRRGetScreenInfo(ps->dpy, ps->root)))
|
|
return;
|
|
ps->refresh_rate = XRRConfigCurrentRate(randr_info);
|
|
|
|
XRRFreeScreenConfigInfo(randr_info);
|
|
|
|
if (ps->refresh_rate)
|
|
ps->refresh_intv = US_PER_SEC / ps->refresh_rate;
|
|
else
|
|
ps->refresh_intv = 0;
|
|
}
|
|
#endif // CONFIG_XRANDR
|
|
|
|
/**
|
|
* Initialize refresh-rated based software optimization.
|
|
*
|
|
* @return true for success, false otherwise
|
|
*/
|
|
static bool
|
|
swopti_init(session_t *ps) {
|
|
// Prepare refresh rate
|
|
// Check if user provides one
|
|
ps->refresh_rate = ps->o.refresh_rate;
|
|
if (ps->refresh_rate)
|
|
ps->refresh_intv = US_PER_SEC / ps->refresh_rate;
|
|
|
|
#ifdef CONFIG_XRANDR
|
|
// Auto-detect refresh rate otherwise
|
|
if (!ps->refresh_rate && ps->randr_exists) {
|
|
update_refresh_rate(ps);
|
|
}
|
|
#endif // CONFIG_XRANDR
|
|
|
|
// Turn off vsync_sw if we can't get the refresh rate
|
|
if (!ps->refresh_rate)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Modify a struct timeval timeout value to render at a fixed pace.
|
|
*
|
|
* @param ps current session
|
|
* @param[in,out] ptv pointer to the timeout
|
|
*/
|
|
static void
|
|
swopti_handle_timeout(session_t *ps, struct timeval *ptv) {
|
|
if (!ptv)
|
|
return;
|
|
|
|
// Get the microsecond offset of the time when the we reach the timeout
|
|
// I don't think a 32-bit long could overflow here.
|
|
long offset = (ptv->tv_usec + get_time_timeval().tv_usec - ps->paint_tm_offset) % ps->refresh_intv;
|
|
if (offset < 0)
|
|
offset += ps->refresh_intv;
|
|
|
|
assert(offset >= 0 && offset < ps->refresh_intv);
|
|
|
|
// If the target time is sufficiently close to a refresh time, don't add
|
|
// an offset, to avoid certain blocking conditions.
|
|
if (offset < SWOPTI_TOLERANCE
|
|
|| offset > ps->refresh_intv - SWOPTI_TOLERANCE)
|
|
return;
|
|
|
|
// Add an offset so we wait until the next refresh after timeout
|
|
ptv->tv_usec += ps->refresh_intv - offset;
|
|
if (ptv->tv_usec > US_PER_SEC) {
|
|
ptv->tv_usec -= US_PER_SEC;
|
|
++ptv->tv_sec;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize DRM VSync.
|
|
*
|
|
* @return true for success, false otherwise
|
|
*/
|
|
static bool
|
|
vsync_drm_init(session_t *ps) {
|
|
#ifdef CONFIG_VSYNC_DRM
|
|
// Should we always open card0?
|
|
if (ps->drm_fd < 0 && (ps->drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) {
|
|
printf_errf("(): Failed to open device.");
|
|
return false;
|
|
}
|
|
|
|
if (vsync_drm_wait(ps))
|
|
return false;
|
|
|
|
return true;
|
|
#else
|
|
printf_errf("(): Program not compiled with DRM VSync support.");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_VSYNC_DRM
|
|
/**
|
|
* Wait for next VSync, DRM method.
|
|
*
|
|
* Stolen from: https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp
|
|
*/
|
|
static int
|
|
vsync_drm_wait(session_t *ps) {
|
|
int ret = -1;
|
|
drm_wait_vblank_t vbl;
|
|
|
|
vbl.request.type = _DRM_VBLANK_RELATIVE,
|
|
vbl.request.sequence = 1;
|
|
|
|
do {
|
|
ret = ioctl(ps->drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl);
|
|
vbl.request.type &= ~_DRM_VBLANK_RELATIVE;
|
|
} while (ret && errno == EINTR);
|
|
|
|
if (ret)
|
|
fprintf(stderr, "vsync_drm_wait(): VBlank ioctl did not work, "
|
|
"unimplemented in this drmver?\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Initialize OpenGL VSync.
|
|
*
|
|
* Stolen from: http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e
|
|
* Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html
|
|
*
|
|
* @return true for success, false otherwise
|
|
*/
|
|
static bool
|
|
vsync_opengl_init(session_t *ps) {
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (!ensure_glx_context(ps))
|
|
return false;
|
|
|
|
// Get video sync functions
|
|
if (!ps->psglx->glXGetVideoSyncSGI)
|
|
ps->psglx->glXGetVideoSyncSGI = (f_GetVideoSync)
|
|
glXGetProcAddress((const GLubyte *) "glXGetVideoSyncSGI");
|
|
if (!ps->psglx->glXWaitVideoSyncSGI)
|
|
ps->psglx->glXWaitVideoSyncSGI = (f_WaitVideoSync)
|
|
glXGetProcAddress((const GLubyte *) "glXWaitVideoSyncSGI");
|
|
if (!ps->psglx->glXWaitVideoSyncSGI || !ps->psglx->glXGetVideoSyncSGI) {
|
|
printf_errf("(): Failed to get glXWait/GetVideoSyncSGI function.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
printf_errf("(): Program not compiled with OpenGL VSync support.");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
vsync_opengl_oml_init(session_t *ps) {
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (!ensure_glx_context(ps))
|
|
return false;
|
|
|
|
// Get video sync functions
|
|
if (!ps->psglx->glXGetSyncValuesOML)
|
|
ps->psglx->glXGetSyncValuesOML = (f_GetSyncValuesOML)
|
|
glXGetProcAddress ((const GLubyte *) "glXGetSyncValuesOML");
|
|
if (!ps->psglx->glXWaitForMscOML)
|
|
ps->psglx->glXWaitForMscOML = (f_WaitForMscOML)
|
|
glXGetProcAddress ((const GLubyte *) "glXWaitForMscOML");
|
|
if (!ps->psglx->glXGetSyncValuesOML || !ps->psglx->glXWaitForMscOML) {
|
|
printf_errf("(): Failed to get OML_sync_control functions.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
printf_errf("(): Program not compiled with OpenGL VSync support.");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
vsync_opengl_swc_init(session_t *ps) {
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (!ensure_glx_context(ps))
|
|
return false;
|
|
|
|
if (!bkend_use_glx(ps)) {
|
|
printf_errf("(): I'm afraid glXSwapIntervalSGI wouldn't help if you are "
|
|
"not using GLX backend. You could try, nonetheless.");
|
|
}
|
|
|
|
// Get video sync functions
|
|
if (!ps->psglx->glXSwapIntervalProc)
|
|
ps->psglx->glXSwapIntervalProc = (f_SwapIntervalSGI)
|
|
glXGetProcAddress ((const GLubyte *) "glXSwapIntervalSGI");
|
|
if (!ps->psglx->glXSwapIntervalProc) {
|
|
printf_errf("(): Failed to get SGI_swap_control function.");
|
|
return false;
|
|
}
|
|
ps->psglx->glXSwapIntervalProc(1);
|
|
|
|
return true;
|
|
#else
|
|
printf_errf("(): Program not compiled with OpenGL VSync support.");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
vsync_opengl_mswc_init(session_t *ps) {
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (!ensure_glx_context(ps))
|
|
return false;
|
|
|
|
if (!bkend_use_glx(ps)) {
|
|
printf_errf("(): I'm afraid glXSwapIntervalMESA wouldn't help if you are "
|
|
"not using GLX backend. You could try, nonetheless.");
|
|
}
|
|
|
|
// Get video sync functions
|
|
if (!ps->psglx->glXSwapIntervalMESAProc)
|
|
ps->psglx->glXSwapIntervalMESAProc = (f_SwapIntervalMESA)
|
|
glXGetProcAddress ((const GLubyte *) "glXSwapIntervalMESA");
|
|
if (!ps->psglx->glXSwapIntervalMESAProc) {
|
|
printf_errf("(): Failed to get MESA_swap_control function.");
|
|
return false;
|
|
}
|
|
ps->psglx->glXSwapIntervalMESAProc(1);
|
|
|
|
return true;
|
|
#else
|
|
printf_errf("(): Program not compiled with OpenGL VSync support.");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
/**
|
|
* Wait for next VSync, OpenGL method.
|
|
*/
|
|
static int
|
|
vsync_opengl_wait(session_t *ps) {
|
|
unsigned vblank_count = 0;
|
|
|
|
ps->psglx->glXGetVideoSyncSGI(&vblank_count);
|
|
ps->psglx->glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count);
|
|
// I see some code calling glXSwapIntervalSGI(1) afterwards, is it required?
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Wait for next VSync, OpenGL OML method.
|
|
*
|
|
* https://mail.gnome.org/archives/clutter-list/2012-November/msg00031.html
|
|
*/
|
|
static int
|
|
vsync_opengl_oml_wait(session_t *ps) {
|
|
int64_t ust = 0, msc = 0, sbc = 0;
|
|
|
|
ps->psglx->glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc);
|
|
ps->psglx->glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2,
|
|
&ust, &msc, &sbc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
vsync_opengl_swc_deinit(session_t *ps) {
|
|
// The standard says it doesn't accept 0, but in fact it probably does
|
|
if (glx_has_context(ps) && ps->psglx->glXSwapIntervalProc)
|
|
ps->psglx->glXSwapIntervalProc(0);
|
|
}
|
|
|
|
static void
|
|
vsync_opengl_mswc_deinit(session_t *ps) {
|
|
if (glx_has_context(ps) && ps->psglx->glXSwapIntervalMESAProc)
|
|
ps->psglx->glXSwapIntervalMESAProc(0);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Initialize current VSync method.
|
|
*/
|
|
bool
|
|
vsync_init(session_t *ps) {
|
|
if (ps->o.vsync && VSYNC_FUNCS_INIT[ps->o.vsync]
|
|
&& !VSYNC_FUNCS_INIT[ps->o.vsync](ps)) {
|
|
ps->o.vsync = VSYNC_NONE;
|
|
return false;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Wait for next VSync.
|
|
*/
|
|
static void
|
|
vsync_wait(session_t *ps) {
|
|
if (!ps->o.vsync)
|
|
return;
|
|
|
|
if (VSYNC_FUNCS_WAIT[ps->o.vsync])
|
|
VSYNC_FUNCS_WAIT[ps->o.vsync](ps);
|
|
}
|
|
|
|
/**
|
|
* Deinitialize current VSync method.
|
|
*/
|
|
void
|
|
vsync_deinit(session_t *ps) {
|
|
if (ps->o.vsync && VSYNC_FUNCS_DEINIT[ps->o.vsync])
|
|
VSYNC_FUNCS_DEINIT[ps->o.vsync](ps);
|
|
}
|
|
|
|
/**
|
|
* Pregenerate alpha pictures.
|
|
*/
|
|
static void
|
|
init_alpha_picts(session_t *ps) {
|
|
int i;
|
|
int num = round(1.0 / ps->o.alpha_step) + 1;
|
|
|
|
ps->alpha_picts = malloc(sizeof(Picture) * num);
|
|
|
|
for (i = 0; i < num; ++i) {
|
|
double o = i * ps->o.alpha_step;
|
|
if ((1.0 - o) > ps->o.alpha_step)
|
|
ps->alpha_picts[i] = solid_picture(ps, false, o, 0, 0, 0);
|
|
else
|
|
ps->alpha_picts[i] = None;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize double buffer.
|
|
*/
|
|
static bool
|
|
init_dbe(session_t *ps) {
|
|
if (!(ps->root_dbe = XdbeAllocateBackBufferName(ps->dpy,
|
|
(ps->o.paint_on_overlay ? ps->overlay: ps->root), XdbeCopied))) {
|
|
printf_errf("(): Failed to create double buffer. Double buffering "
|
|
"cannot work.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Initialize X composite overlay window.
|
|
*/
|
|
static bool
|
|
init_overlay(session_t *ps) {
|
|
ps->overlay = XCompositeGetOverlayWindow(ps->dpy, ps->root);
|
|
if (ps->overlay) {
|
|
// Set window region of the overlay window, code stolen from
|
|
// compiz-0.8.8
|
|
XserverRegion region = XFixesCreateRegion(ps->dpy, NULL, 0);
|
|
XFixesSetWindowShapeRegion(ps->dpy, ps->overlay, ShapeBounding, 0, 0, 0);
|
|
XFixesSetWindowShapeRegion(ps->dpy, ps->overlay, ShapeInput, 0, 0, region);
|
|
XFixesDestroyRegion(ps->dpy, region);
|
|
|
|
// Listen to Expose events on the overlay
|
|
XSelectInput(ps->dpy, ps->overlay, ExposureMask);
|
|
|
|
// Retrieve DamageNotify on root window if we are painting on an
|
|
// overlay
|
|
// root_damage = XDamageCreate(ps->dpy, root, XDamageReportNonEmpty);
|
|
|
|
// Unmap overlay, firstly. But this typically does not work because
|
|
// the window isn't created yet.
|
|
// XUnmapWindow(ps->dpy, ps->overlay);
|
|
// XFlush(ps->dpy);
|
|
}
|
|
else {
|
|
fprintf(stderr, "Cannot get X Composite overlay window. Falling "
|
|
"back to painting on root window.\n");
|
|
ps->o.paint_on_overlay = false;
|
|
}
|
|
#ifdef DEBUG_REDIR
|
|
printf_dbgf("(): overlay = %#010lx\n", ps->overlay);
|
|
#endif
|
|
|
|
return ps->overlay;
|
|
}
|
|
|
|
/**
|
|
* Query needed X Render / OpenGL filters to check for their existence.
|
|
*/
|
|
static bool
|
|
init_filters(session_t *ps) {
|
|
// Blur filter
|
|
if (ps->o.blur_background || ps->o.blur_background_frame) {
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
case BKEND_XR_GLX_HYBRID:
|
|
{
|
|
// Query filters
|
|
XFilters *pf = XRenderQueryFilters(ps->dpy, get_tgt_window(ps));
|
|
if (pf) {
|
|
for (int i = 0; i < pf->nfilter; ++i) {
|
|
// Convolution filter
|
|
if (!strcmp(pf->filter[i], XRFILTER_CONVOLUTION))
|
|
ps->xrfilter_convolution_exists = true;
|
|
}
|
|
}
|
|
cxfree(pf);
|
|
|
|
// Turn features off if any required filter is not present
|
|
if (!ps->xrfilter_convolution_exists) {
|
|
printf_errf("(): X Render convolution filter unsupported by your X server. Background blur is not possible.");
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
case BKEND_GLX:
|
|
{
|
|
if (!glx_init_blur(ps))
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Greyscale filter
|
|
switch (ps->o.backend) {
|
|
case BKEND_XRENDER:
|
|
case BKEND_XR_GLX_HYBRID:
|
|
{
|
|
break;
|
|
}
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
case BKEND_GLX:
|
|
{
|
|
if (!glx_init_greyscale(ps))
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Redirect all windows.
|
|
*/
|
|
static void
|
|
redir_start(session_t *ps) {
|
|
if (!ps->redirected) {
|
|
#ifdef DEBUG_REDIR
|
|
print_timestamp(ps);
|
|
printf_dbgf("(): Screen redirected.\n");
|
|
#endif
|
|
|
|
// Map overlay window. Done firstly according to this:
|
|
// https://bugzilla.gnome.org/show_bug.cgi?id=597014
|
|
if (ps->overlay)
|
|
XMapWindow(ps->dpy, ps->overlay);
|
|
|
|
XCompositeRedirectSubwindows(ps->dpy, ps->root, CompositeRedirectManual);
|
|
|
|
/*
|
|
// Unredirect GL context window as this may have an effect on VSync:
|
|
// < http://dri.freedesktop.org/wiki/CompositeSwap >
|
|
XCompositeUnredirectWindow(ps->dpy, ps->reg_win, CompositeRedirectManual);
|
|
if (ps->o.paint_on_overlay && ps->overlay) {
|
|
XCompositeUnredirectWindow(ps->dpy, ps->overlay,
|
|
CompositeRedirectManual);
|
|
} */
|
|
|
|
// Must call XSync() here
|
|
XSync(ps->dpy, False);
|
|
|
|
ps->redirected = true;
|
|
|
|
// Repaint the whole screen
|
|
force_repaint(ps);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the poll time.
|
|
*/
|
|
static time_ms_t
|
|
timeout_get_poll_time(session_t *ps) {
|
|
const time_ms_t now = get_time_ms();
|
|
time_ms_t wait = TIME_MS_MAX;
|
|
|
|
// Traverse throught the timeout linked list
|
|
for (timeout_t *ptmout = ps->tmout_lst; ptmout; ptmout = ptmout->next) {
|
|
if (ptmout->enabled) {
|
|
time_ms_t newrun = timeout_get_newrun(ptmout);
|
|
if (newrun <= now) {
|
|
wait = 0;
|
|
break;
|
|
}
|
|
else {
|
|
time_ms_t newwait = newrun - now;
|
|
if (newwait < wait)
|
|
wait = newwait;
|
|
}
|
|
}
|
|
}
|
|
|
|
return wait;
|
|
}
|
|
|
|
/**
|
|
* Insert a new timeout.
|
|
*/
|
|
timeout_t *
|
|
timeout_insert(session_t *ps, time_ms_t interval,
|
|
bool (*callback)(session_t *ps, timeout_t *ptmout), void *data) {
|
|
const static timeout_t tmout_def = {
|
|
.enabled = true,
|
|
.data = NULL,
|
|
.callback = NULL,
|
|
.firstrun = 0L,
|
|
.lastrun = 0L,
|
|
.interval = 0L,
|
|
};
|
|
|
|
const time_ms_t now = get_time_ms();
|
|
timeout_t *ptmout = malloc(sizeof(timeout_t));
|
|
if (!ptmout)
|
|
printf_errfq(1, "(): Failed to allocate memory for timeout.");
|
|
memcpy(ptmout, &tmout_def, sizeof(timeout_t));
|
|
|
|
ptmout->interval = interval;
|
|
ptmout->firstrun = now;
|
|
ptmout->lastrun = now;
|
|
ptmout->data = data;
|
|
ptmout->callback = callback;
|
|
ptmout->next = ps->tmout_lst;
|
|
ps->tmout_lst = ptmout;
|
|
|
|
return ptmout;
|
|
}
|
|
|
|
/**
|
|
* Drop a timeout.
|
|
*
|
|
* @return true if we have found the timeout and removed it, false
|
|
* otherwise
|
|
*/
|
|
bool
|
|
timeout_drop(session_t *ps, timeout_t *prm) {
|
|
timeout_t **pplast = &ps->tmout_lst;
|
|
|
|
for (timeout_t *ptmout = ps->tmout_lst; ptmout;
|
|
pplast = &ptmout->next, ptmout = ptmout->next) {
|
|
if (prm == ptmout) {
|
|
*pplast = ptmout->next;
|
|
free(ptmout);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Clear all timeouts.
|
|
*/
|
|
static void
|
|
timeout_clear(session_t *ps) {
|
|
timeout_t *ptmout = ps->tmout_lst;
|
|
timeout_t *next = NULL;
|
|
while (ptmout) {
|
|
next = ptmout->next;
|
|
free(ptmout);
|
|
ptmout = next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run timeouts.
|
|
*
|
|
* @return true if we have ran a timeout, false otherwise
|
|
*/
|
|
static bool
|
|
timeout_run(session_t *ps) {
|
|
const time_ms_t now = get_time_ms();
|
|
bool ret = false;
|
|
timeout_t *pnext = NULL;
|
|
|
|
for (timeout_t *ptmout = ps->tmout_lst; ptmout; ptmout = pnext) {
|
|
pnext = ptmout->next;
|
|
if (ptmout->enabled) {
|
|
const time_ms_t max = now +
|
|
(time_ms_t) (ptmout->interval * TIMEOUT_RUN_TOLERANCE);
|
|
time_ms_t newrun = timeout_get_newrun(ptmout);
|
|
if (newrun <= max) {
|
|
ret = true;
|
|
timeout_invoke(ps, ptmout);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Invoke a timeout.
|
|
*/
|
|
void
|
|
timeout_invoke(session_t *ps, timeout_t *ptmout) {
|
|
const time_ms_t now = get_time_ms();
|
|
ptmout->lastrun = now;
|
|
// Avoid modifying the timeout structure after running timeout, to
|
|
// make it possible to remove timeout in callback
|
|
if (ptmout->callback)
|
|
ptmout->callback(ps, ptmout);
|
|
}
|
|
|
|
/**
|
|
* Reset a timeout to initial state.
|
|
*/
|
|
void
|
|
timeout_reset(session_t *ps, timeout_t *ptmout) {
|
|
ptmout->firstrun = ptmout->lastrun = get_time_ms();
|
|
}
|
|
|
|
/**
|
|
* Unredirect all windows.
|
|
*/
|
|
static void
|
|
redir_stop(session_t *ps) {
|
|
if (ps->redirected) {
|
|
#ifdef DEBUG_REDIR
|
|
print_timestamp(ps);
|
|
printf_dbgf("(): Screen unredirected.\n");
|
|
#endif
|
|
// Destroy all Pictures as they expire once windows are unredirected
|
|
// If we don't destroy them here, looks like the resources are just
|
|
// kept inaccessible somehow
|
|
for (win *w = ps->list; w; w = w->next)
|
|
free_wpaint(ps, w);
|
|
|
|
XCompositeUnredirectSubwindows(ps->dpy, ps->root, CompositeRedirectManual);
|
|
// Unmap overlay window
|
|
if (ps->overlay)
|
|
XUnmapWindow(ps->dpy, ps->overlay);
|
|
|
|
// Must call XSync() here
|
|
XSync(ps->dpy, False);
|
|
|
|
ps->redirected = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unredirection timeout callback.
|
|
*/
|
|
static bool
|
|
tmout_unredir_callback(session_t *ps, timeout_t *tmout) {
|
|
ps->tmout_unredir_hit = true;
|
|
tmout->enabled = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Main loop.
|
|
*/
|
|
static bool
|
|
mainloop(session_t *ps) {
|
|
// Don't miss timeouts even when we have a LOT of other events!
|
|
timeout_run(ps);
|
|
|
|
// Process existing events
|
|
// Sometimes poll() returns 1 but no events are actually read,
|
|
// causing XNextEvent() to block, I have no idea what's wrong, so we
|
|
// check for the number of events here.
|
|
if (XEventsQueued(ps->dpy, QueuedAfterReading)) {
|
|
XEvent ev = { };
|
|
|
|
XNextEvent(ps->dpy, &ev);
|
|
ev_handle(ps, &ev);
|
|
ps->ev_received = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef CONFIG_DBUS
|
|
if (ps->o.dbus) {
|
|
cdbus_loop(ps);
|
|
}
|
|
#endif
|
|
|
|
if (ps->reset)
|
|
return false;
|
|
|
|
// Calculate timeout
|
|
struct timeval *ptv = NULL;
|
|
{
|
|
// Consider ev_received firstly
|
|
if (ps->ev_received || ps->o.benchmark) {
|
|
ptv = malloc(sizeof(struct timeval));
|
|
ptv->tv_sec = 0L;
|
|
ptv->tv_usec = 0L;
|
|
}
|
|
// Then consider fading timeout
|
|
else if (!ps->idling) {
|
|
ptv = malloc(sizeof(struct timeval));
|
|
*ptv = ms_to_tv(fade_timeout(ps));
|
|
}
|
|
|
|
// Software optimization is to be applied on timeouts that require
|
|
// immediate painting only
|
|
if (ptv && ps->o.sw_opti)
|
|
swopti_handle_timeout(ps, ptv);
|
|
|
|
// Don't continue looping for 0 timeout
|
|
if (ptv && timeval_isempty(ptv)) {
|
|
free(ptv);
|
|
return false;
|
|
}
|
|
|
|
// Now consider the waiting time of other timeouts
|
|
time_ms_t tmout_ms = timeout_get_poll_time(ps);
|
|
if (tmout_ms < TIME_MS_MAX) {
|
|
if (!ptv) {
|
|
ptv = malloc(sizeof(struct timeval));
|
|
*ptv = ms_to_tv(tmout_ms);
|
|
}
|
|
else if (timeval_ms_cmp(ptv, tmout_ms) > 0) {
|
|
*ptv = ms_to_tv(tmout_ms);
|
|
}
|
|
}
|
|
|
|
// Don't continue looping for 0 timeout
|
|
if (ptv && timeval_isempty(ptv)) {
|
|
free(ptv);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Polling
|
|
fds_poll(ps, ptv);
|
|
free(ptv);
|
|
ptv = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
cxinerama_upd_scrs(session_t *ps) {
|
|
#ifdef CONFIG_XINERAMA
|
|
free_xinerama_info(ps);
|
|
|
|
if (!ps->o.xinerama_shadow_crop || !ps->xinerama_exists) return;
|
|
|
|
if (!XineramaIsActive(ps->dpy)) return;
|
|
|
|
ps->xinerama_scrs = XineramaQueryScreens(ps->dpy, &ps->xinerama_nscrs);
|
|
|
|
// Just in case the shit hits the fan...
|
|
if (!ps->xinerama_nscrs) {
|
|
cxfree(ps->xinerama_scrs);
|
|
ps->xinerama_scrs = NULL;
|
|
return;
|
|
}
|
|
|
|
ps->xinerama_scr_regs = allocchk(malloc(sizeof(XserverRegion *)
|
|
* ps->xinerama_nscrs));
|
|
for (int i = 0; i < ps->xinerama_nscrs; ++i) {
|
|
const XineramaScreenInfo * const s = &ps->xinerama_scrs[i];
|
|
XRectangle r = { .x = s->x_org, .y = s->y_org,
|
|
.width = s->width, .height = s->height };
|
|
ps->xinerama_scr_regs[i] = XFixesCreateRegion(ps->dpy, &r, 1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Initialize a session.
|
|
*
|
|
* @param ps_old old session, from which the function will take the X
|
|
* connection, then free it
|
|
* @param argc number of commandline arguments
|
|
* @param argv commandline arguments
|
|
*/
|
|
static session_t *
|
|
session_init(session_t *ps_old, int argc, char **argv) {
|
|
const static session_t s_def = {
|
|
.dpy = NULL,
|
|
.scr = 0,
|
|
.vis = NULL,
|
|
.depth = 0,
|
|
.root = None,
|
|
.root_height = 0,
|
|
.root_width = 0,
|
|
// .root_damage = None,
|
|
.overlay = None,
|
|
.root_tile_fill = false,
|
|
.root_tile_paint = PAINT_INIT,
|
|
.screen_reg = None,
|
|
.tgt_picture = None,
|
|
.tgt_buffer = PAINT_INIT,
|
|
.root_dbe = None,
|
|
.reg_win = None,
|
|
.o = {
|
|
.config_file = NULL,
|
|
.display = NULL,
|
|
.backend = BKEND_XRENDER,
|
|
.glx_no_stencil = false,
|
|
.glx_copy_from_front = false,
|
|
#ifdef CONFIG_VSYNC_OPENGL_GLSL
|
|
.glx_prog_win = GLX_PROG_MAIN_INIT,
|
|
#endif
|
|
.mark_wmwin_focused = false,
|
|
.mark_ovredir_focused = false,
|
|
.fork_after_register = false,
|
|
.synchronize = false,
|
|
.detect_rounded_corners = false,
|
|
.paint_on_overlay = false,
|
|
.resize_damage = 0,
|
|
.unredir_if_possible = false,
|
|
.unredir_if_possible_blacklist = NULL,
|
|
.unredir_if_possible_delay = 0,
|
|
.redirected_force = UNSET,
|
|
.stoppaint_force = UNSET,
|
|
.dbus = false,
|
|
.benchmark = 0,
|
|
.benchmark_wid = None,
|
|
.logpath = NULL,
|
|
|
|
.refresh_rate = 0,
|
|
.sw_opti = false,
|
|
.vsync = VSYNC_NONE,
|
|
.dbe = false,
|
|
.vsync_aggressive = false,
|
|
|
|
.wintype_shadow = { false },
|
|
.shadow_red = 0.0,
|
|
.shadow_green = 0.0,
|
|
.shadow_blue = 0.0,
|
|
.shadow_radius = 12,
|
|
.shadow_offset_x = 0,
|
|
.shadow_offset_y = 0,
|
|
.shadow_opacity = .75,
|
|
.clear_shadow = false,
|
|
.shadow_blacklist = NULL,
|
|
.shadow_ignore_shaped = false,
|
|
.respect_prop_shadow = false,
|
|
.xinerama_shadow_crop = false,
|
|
|
|
.wintype_fade = { false },
|
|
.fade_in_step = 0.028 * OPAQUE,
|
|
.fade_out_step = 0.03 * OPAQUE,
|
|
.fade_delta = 10,
|
|
.no_fading_openclose = false,
|
|
.no_fading_destroyed_argb = false,
|
|
.no_fading_opacitychange = false,
|
|
.fade_blacklist = NULL,
|
|
|
|
.wintype_opacity = { 0.0 },
|
|
.inactive_opacity = 0,
|
|
.inactive_opacity_override = false,
|
|
.active_opacity = 0,
|
|
.frame_opacity = 0.0,
|
|
.detect_client_opacity = false,
|
|
.alpha_step = 0.03,
|
|
|
|
.blur_background = false,
|
|
.blur_background_frame = false,
|
|
.blur_background_fixed = false,
|
|
.blur_background_blacklist = NULL,
|
|
.blur_kerns = { NULL },
|
|
.greyscale_background = false,
|
|
.inactive_dim = 0.0,
|
|
.inactive_dim_fixed = false,
|
|
.invert_color_list = NULL,
|
|
.opacity_rules = NULL,
|
|
|
|
.wintype_focus = { false },
|
|
.use_ewmh_active_win = false,
|
|
.focus_blacklist = NULL,
|
|
.detect_transient = false,
|
|
.detect_client_leader = false,
|
|
|
|
.track_focus = false,
|
|
.track_wdata = false,
|
|
.track_leader = false,
|
|
},
|
|
|
|
.pfds_read = NULL,
|
|
.pfds_write = NULL,
|
|
.pfds_except = NULL,
|
|
.nfds_max = 0,
|
|
.tmout_lst = NULL,
|
|
|
|
.all_damage = None,
|
|
.all_damage_last = { None },
|
|
.time_start = { 0, 0 },
|
|
.redirected = false,
|
|
.alpha_picts = NULL,
|
|
.reg_ignore_expire = false,
|
|
.idling = false,
|
|
.fade_time = 0L,
|
|
.ignore_head = NULL,
|
|
.ignore_tail = NULL,
|
|
.reset = false,
|
|
|
|
.expose_rects = NULL,
|
|
.size_expose = 0,
|
|
.n_expose = 0,
|
|
|
|
.list = NULL,
|
|
.active_win = NULL,
|
|
.active_leader = None,
|
|
|
|
.black_picture = None,
|
|
.cshadow_picture = None,
|
|
.white_picture = None,
|
|
.gaussian_map = NULL,
|
|
.cgsize = 0,
|
|
.shadow_corner = NULL,
|
|
.shadow_top = NULL,
|
|
|
|
.refresh_rate = 0,
|
|
.refresh_intv = 0UL,
|
|
.paint_tm_offset = 0L,
|
|
|
|
#ifdef CONFIG_VSYNC_DRM
|
|
.drm_fd = -1,
|
|
#endif
|
|
|
|
.xfixes_event = 0,
|
|
.xfixes_error = 0,
|
|
.damage_event = 0,
|
|
.damage_error = 0,
|
|
.render_event = 0,
|
|
.render_error = 0,
|
|
.composite_event = 0,
|
|
.composite_error = 0,
|
|
.composite_opcode = 0,
|
|
.has_name_pixmap = false,
|
|
.shape_exists = false,
|
|
.shape_event = 0,
|
|
.shape_error = 0,
|
|
#ifdef CONFIG_XRANDR
|
|
.randr_exists = 0,
|
|
.randr_event = 0,
|
|
.randr_error = 0,
|
|
#endif // CONFIG_XRANDR
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
.glx_exists = false,
|
|
.glx_event = 0,
|
|
.glx_error = 0,
|
|
#endif
|
|
.dbe_exists = false,
|
|
.xrfilter_convolution_exists = false,
|
|
|
|
.atom_opacity = None,
|
|
.atom_frame_extents = None,
|
|
.atom_client = None,
|
|
.atom_name = None,
|
|
.atom_name_ewmh = None,
|
|
.atom_class = None,
|
|
.atom_role = None,
|
|
.atom_transient = None,
|
|
.atom_ewmh_active_win = None,
|
|
.atom_compton_shadow = None,
|
|
.atom_win_type = None,
|
|
.atom_win_type_tde_transparent_to_black = None,
|
|
.atom_win_type_tde_transparent_to_desktop = None,
|
|
.atom_win_type_tde_transparency_filter_greyscale = None,
|
|
.atom_win_type_tde_transparency_filter_greyscale_blend = None,
|
|
.atoms_wintypes = { 0 },
|
|
.track_atom_lst = NULL,
|
|
|
|
#ifdef CONFIG_DBUS
|
|
.dbus_conn = NULL,
|
|
.dbus_service = NULL,
|
|
#endif
|
|
};
|
|
|
|
// Allocate a session and copy default values into it
|
|
session_t *ps = malloc(sizeof(session_t));
|
|
memcpy(ps, &s_def, sizeof(session_t));
|
|
ps_g = ps;
|
|
ps->ignore_tail = &ps->ignore_head;
|
|
gettimeofday(&ps->time_start, NULL);
|
|
|
|
wintype_arr_enable(ps->o.wintype_focus);
|
|
ps->o.wintype_focus[WINTYPE_UNKNOWN] = false;
|
|
ps->o.wintype_focus[WINTYPE_NORMAL] = false;
|
|
ps->o.wintype_focus[WINTYPE_UTILITY] = false;
|
|
|
|
// First pass
|
|
get_cfg(ps, argc, argv, true);
|
|
|
|
// Inherit old Display if possible, primarily for resource leak checking
|
|
if (ps_old && ps_old->dpy)
|
|
ps->dpy = ps_old->dpy;
|
|
|
|
// Open Display
|
|
if (!ps->dpy) {
|
|
ps->dpy = XOpenDisplay(ps->o.display);
|
|
if (!ps->dpy) {
|
|
printf_errfq(1, "(): Can't open display.");
|
|
}
|
|
}
|
|
|
|
XSetErrorHandler(xerror);
|
|
if (ps->o.synchronize) {
|
|
XSynchronize(ps->dpy, 1);
|
|
}
|
|
|
|
ps->scr = DefaultScreen(ps->dpy);
|
|
ps->root = RootWindow(ps->dpy, ps->scr);
|
|
|
|
ps->vis = DefaultVisual(ps->dpy, ps->scr);
|
|
ps->depth = DefaultDepth(ps->dpy, ps->scr);
|
|
|
|
// Start listening to events on root earlier to catch all possible
|
|
// root geometry changes
|
|
XSelectInput(ps->dpy, ps->root,
|
|
SubstructureNotifyMask
|
|
| ExposureMask
|
|
| StructureNotifyMask
|
|
| PropertyChangeMask);
|
|
XFlush(ps->dpy);
|
|
|
|
ps->root_width = DisplayWidth(ps->dpy, ps->scr);
|
|
ps->root_height = DisplayHeight(ps->dpy, ps->scr);
|
|
|
|
if (!XRenderQueryExtension(ps->dpy,
|
|
&ps->render_event, &ps->render_error)) {
|
|
fprintf(stderr, "No render extension\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (!XQueryExtension(ps->dpy, COMPOSITE_NAME, &ps->composite_opcode,
|
|
&ps->composite_event, &ps->composite_error)) {
|
|
fprintf(stderr, "No composite extension\n");
|
|
exit(1);
|
|
}
|
|
|
|
{
|
|
int composite_major = 0, composite_minor = 0;
|
|
|
|
XCompositeQueryVersion(ps->dpy, &composite_major, &composite_minor);
|
|
|
|
if (!ps->o.no_name_pixmap
|
|
&& (composite_major > 0 || composite_minor >= 2)) {
|
|
ps->has_name_pixmap = true;
|
|
}
|
|
}
|
|
|
|
if (!XDamageQueryExtension(ps->dpy, &ps->damage_event, &ps->damage_error)) {
|
|
fprintf(stderr, "No damage extension\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (!XFixesQueryExtension(ps->dpy, &ps->xfixes_event, &ps->xfixes_error)) {
|
|
fprintf(stderr, "No XFixes extension\n");
|
|
exit(1);
|
|
}
|
|
|
|
// Build a safe representation of display name
|
|
{
|
|
char *display_repr = DisplayString(ps->dpy);
|
|
if (!display_repr)
|
|
display_repr = "unknown";
|
|
display_repr = mstrcpy(display_repr);
|
|
|
|
// Convert all special characters in display_repr name to underscore
|
|
{
|
|
char *pdisp = display_repr;
|
|
|
|
while (*pdisp) {
|
|
if (!isalnum(*pdisp))
|
|
*pdisp = '_';
|
|
++pdisp;
|
|
}
|
|
}
|
|
|
|
ps->o.display_repr = display_repr;
|
|
}
|
|
|
|
// Second pass
|
|
get_cfg(ps, argc, argv, false);
|
|
|
|
// Query X Shape
|
|
if (XShapeQueryExtension(ps->dpy, &ps->shape_event, &ps->shape_error)) {
|
|
ps->shape_exists = true;
|
|
}
|
|
|
|
if (ps->o.xrender_sync_fence) {
|
|
#ifdef CONFIG_XSYNC
|
|
// Query X Sync
|
|
if (XSyncQueryExtension(ps->dpy, &ps->xsync_event, &ps->xsync_error)) {
|
|
// TODO: Fencing may require version >= 3.0?
|
|
int major_version_return = 0, minor_version_return = 0;
|
|
if (XSyncInitialize(ps->dpy, &major_version_return, &minor_version_return))
|
|
ps->xsync_exists = true;
|
|
}
|
|
if (!ps->xsync_exists) {
|
|
printf_errf("(): X Sync extension not found. No X Sync fence sync is "
|
|
"possible.");
|
|
exit(1);
|
|
}
|
|
#else
|
|
printf_errf("(): X Sync support not compiled in. --xrender-sync-fence "
|
|
"can't work.");
|
|
exit(1);
|
|
#endif
|
|
}
|
|
|
|
// Query X RandR
|
|
if ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop) {
|
|
#ifdef CONFIG_XRANDR
|
|
if (XRRQueryExtension(ps->dpy, &ps->randr_event, &ps->randr_error))
|
|
ps->randr_exists = true;
|
|
else
|
|
printf_errf("(): No XRandR extension, automatic screen change "
|
|
"detection impossible.");
|
|
#else // CONFIG_XRANDR
|
|
printf_errf("(): Xrandr support not compiled in.");
|
|
#endif // CONFIG_XRANDR
|
|
}
|
|
|
|
// Query X DBE extension
|
|
if (ps->o.dbe) {
|
|
int dbe_ver_major = 0, dbe_ver_minor = 0;
|
|
if (XdbeQueryExtension(ps->dpy, &dbe_ver_major, &dbe_ver_minor))
|
|
if (dbe_ver_major >= 1)
|
|
ps->dbe_exists = true;
|
|
else
|
|
fprintf(stderr, "DBE extension version too low. Double buffering "
|
|
"impossible.\n");
|
|
else {
|
|
fprintf(stderr, "No DBE extension. Double buffering impossible.\n");
|
|
}
|
|
if (!ps->dbe_exists)
|
|
ps->o.dbe = false;
|
|
}
|
|
|
|
// Query X Xinerama extension
|
|
if (ps->o.xinerama_shadow_crop) {
|
|
#ifdef CONFIG_XINERAMA
|
|
int xinerama_event = 0, xinerama_error = 0;
|
|
if (XineramaQueryExtension(ps->dpy, &xinerama_event, &xinerama_error))
|
|
ps->xinerama_exists = true;
|
|
#else
|
|
printf_errf("(): Xinerama support not compiled in.");
|
|
#endif
|
|
}
|
|
|
|
rebuild_screen_reg(ps);
|
|
|
|
// Overlay must be initialized before double buffer, and before creation
|
|
// of OpenGL context.
|
|
if (ps->o.paint_on_overlay)
|
|
init_overlay(ps);
|
|
|
|
// Initialize DBE
|
|
if (ps->o.dbe && BKEND_XRENDER != ps->o.backend) {
|
|
printf_errf("(): DBE couldn't be used on GLX backend.");
|
|
ps->o.dbe = false;
|
|
}
|
|
|
|
if (ps->o.dbe && !init_dbe(ps))
|
|
exit(1);
|
|
|
|
// Initialize OpenGL as early as possible
|
|
if (bkend_use_glx(ps)) {
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
if (!glx_init(ps, true))
|
|
exit(1);
|
|
#else
|
|
printf_errfq(1, "(): GLX backend support not compiled in.");
|
|
#endif
|
|
}
|
|
|
|
// Initialize window GL shader
|
|
if (BKEND_GLX == ps->o.backend && ps->o.glx_fshader_win_str) {
|
|
#ifdef CONFIG_VSYNC_OPENGL_GLSL
|
|
if (!glx_load_prog_main(ps, NULL, ps->o.glx_fshader_win_str, &ps->o.glx_prog_win))
|
|
exit(1);
|
|
#else
|
|
printf_errf("(): GLSL supported not compiled in, can't load shader.");
|
|
exit(1);
|
|
#endif
|
|
}
|
|
|
|
// Initialize software optimization
|
|
if (ps->o.sw_opti)
|
|
ps->o.sw_opti = swopti_init(ps);
|
|
|
|
#ifdef CONFIG_XRANDR
|
|
// Monitor screen changes if vsync_sw is enabled and we are using
|
|
// an auto-detected refresh rate, or when Xinerama features are enabled
|
|
if (ps->randr_exists && ((ps->o.sw_opti && !ps->o.refresh_rate)
|
|
|| ps->o.xinerama_shadow_crop))
|
|
XRRSelectInput(ps->dpy, ps->root, RRScreenChangeNotifyMask);
|
|
#else
|
|
if( (ps->o.sw_opti && !ps->o.refresh_rate) ) {
|
|
printf_errf("(): Xrandr support not compiled in. Plese specify --refrash-rate manually");
|
|
}
|
|
if (ps->o.xinerama_shadow_crop) {
|
|
printf_errf("(): due to Xrandr support not compiled in."
|
|
" Xinerama features may malfunction on changing screen size.");
|
|
}
|
|
#endif // CONFIG_XRANDR
|
|
|
|
// Initialize VSync
|
|
if (!vsync_init(ps))
|
|
exit(1);
|
|
|
|
cxinerama_upd_scrs(ps);
|
|
|
|
// Create registration window
|
|
if (!ps->reg_win && !register_cm(ps))
|
|
exit(1);
|
|
|
|
init_atoms(ps);
|
|
init_alpha_picts(ps);
|
|
|
|
ps->gaussian_map = make_gaussian_map(ps->o.shadow_radius);
|
|
presum_gaussian(ps, ps->gaussian_map);
|
|
|
|
{
|
|
XRenderPictureAttributes pa;
|
|
pa.subwindow_mode = IncludeInferiors;
|
|
|
|
ps->root_picture = XRenderCreatePicture(ps->dpy, ps->root,
|
|
XRenderFindVisualFormat(ps->dpy, ps->vis),
|
|
CPSubwindowMode, &pa);
|
|
if (ps->o.paint_on_overlay) {
|
|
ps->tgt_picture = XRenderCreatePicture(ps->dpy, ps->overlay,
|
|
XRenderFindVisualFormat(ps->dpy, ps->vis),
|
|
CPSubwindowMode, &pa);
|
|
}
|
|
else {
|
|
ps->tgt_picture = ps->root_picture;
|
|
}
|
|
}
|
|
|
|
// Initialize filters, must be preceded by OpenGL context creation
|
|
if (!init_filters(ps))
|
|
exit(1);
|
|
|
|
ps->black_picture = solid_picture(ps, true, 1, 0, 0, 0);
|
|
ps->white_picture = solid_picture(ps, true, 1, 1, 1, 1);
|
|
|
|
// Generates another Picture for shadows if the color is modified by
|
|
// user
|
|
if (!ps->o.shadow_red && !ps->o.shadow_green && !ps->o.shadow_blue) {
|
|
ps->cshadow_picture = ps->black_picture;
|
|
} else {
|
|
ps->cshadow_picture = solid_picture(ps, true, 1,
|
|
ps->o.shadow_red, ps->o.shadow_green, ps->o.shadow_blue);
|
|
}
|
|
|
|
fds_insert(ps, ConnectionNumber(ps->dpy), POLLIN);
|
|
ps->tmout_unredir = timeout_insert(ps, ps->o.unredir_if_possible_delay,
|
|
tmout_unredir_callback, NULL);
|
|
ps->tmout_unredir->enabled = false;
|
|
|
|
XGrabServer(ps->dpy);
|
|
|
|
{
|
|
Window root_return, parent_return;
|
|
Window *children;
|
|
unsigned int nchildren;
|
|
|
|
XQueryTree(ps->dpy, ps->root, &root_return,
|
|
&parent_return, &children, &nchildren);
|
|
|
|
for (unsigned i = 0; i < nchildren; i++) {
|
|
add_win(ps, children[i], i ? children[i-1] : None);
|
|
}
|
|
|
|
cxfree(children);
|
|
}
|
|
|
|
if (ps->o.track_focus) {
|
|
recheck_focus(ps);
|
|
}
|
|
|
|
XUngrabServer(ps->dpy);
|
|
// ALWAYS flush after XUngrabServer()!
|
|
XFlush(ps->dpy);
|
|
|
|
// Initialize DBus
|
|
if (ps->o.dbus) {
|
|
#ifdef CONFIG_DBUS
|
|
cdbus_init(ps);
|
|
if (!ps->dbus_conn) {
|
|
cdbus_destroy(ps);
|
|
ps->o.dbus = false;
|
|
}
|
|
#else
|
|
printf_errfq(1, "(): DBus support not compiled in!");
|
|
#endif
|
|
}
|
|
|
|
// Fork to background, if asked
|
|
if (ps->o.fork_after_register) {
|
|
if (!fork_after(ps)) {
|
|
session_destroy(ps);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
write_pid(ps);
|
|
|
|
// Free the old session
|
|
if (ps_old)
|
|
free(ps_old);
|
|
|
|
return ps;
|
|
}
|
|
|
|
/**
|
|
* Destroy a session.
|
|
*
|
|
* Does not close the X connection or free the <code>session_t</code>
|
|
* structure, though.
|
|
*
|
|
* @param ps session to destroy
|
|
*/
|
|
static void
|
|
session_destroy(session_t *ps) {
|
|
redir_stop(ps);
|
|
|
|
// Stop listening to events on root window
|
|
XSelectInput(ps->dpy, ps->root, 0);
|
|
|
|
#ifdef CONFIG_DBUS
|
|
// Kill DBus connection
|
|
if (ps->o.dbus)
|
|
cdbus_destroy(ps);
|
|
|
|
free(ps->dbus_service);
|
|
#endif
|
|
|
|
// Free window linked list
|
|
{
|
|
win *next = NULL;
|
|
for (win *w = ps->list; w; w = next) {
|
|
// Must be put here to avoid segfault
|
|
next = w->next;
|
|
|
|
if (IsViewable == w->a.map_state && !w->destroyed)
|
|
win_ev_stop(ps, w);
|
|
|
|
free_win_res(ps, w);
|
|
free(w);
|
|
}
|
|
|
|
ps->list = NULL;
|
|
}
|
|
|
|
// Free alpha_picts
|
|
{
|
|
const int max = round(1.0 / ps->o.alpha_step) + 1;
|
|
for (int i = 0; i < max; ++i)
|
|
free_picture(ps, &ps->alpha_picts[i]);
|
|
free(ps->alpha_picts);
|
|
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);
|
|
free_wincondlst(&ps->o.blur_background_blacklist);
|
|
free_wincondlst(&ps->o.greyscale_background_blacklist);
|
|
free_wincondlst(&ps->o.opacity_rules);
|
|
free_wincondlst(&ps->o.paint_blacklist);
|
|
free_wincondlst(&ps->o.unredir_if_possible_blacklist);
|
|
#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
|
|
{
|
|
ignore_t *next = NULL;
|
|
for (ignore_t *ign = ps->ignore_head; ign; ign = next) {
|
|
next = ign->next;
|
|
|
|
free(ign);
|
|
}
|
|
|
|
// Reset head and tail
|
|
ps->ignore_head = NULL;
|
|
ps->ignore_tail = &ps->ignore_head;
|
|
}
|
|
|
|
// Free cshadow_picture and black_picture
|
|
if (ps->cshadow_picture == ps->black_picture)
|
|
ps->cshadow_picture = None;
|
|
else
|
|
free_picture(ps, &ps->cshadow_picture);
|
|
|
|
free_picture(ps, &ps->black_picture);
|
|
free_picture(ps, &ps->white_picture);
|
|
|
|
// Free tgt_{buffer,picture} and root_picture
|
|
if (ps->tgt_buffer.pict == ps->tgt_picture)
|
|
ps->tgt_buffer.pict = None;
|
|
|
|
if (ps->tgt_picture == ps->root_picture)
|
|
ps->tgt_picture = None;
|
|
else
|
|
free_picture(ps, &ps->tgt_picture);
|
|
free_fence(ps, &ps->tgt_buffer_fence);
|
|
|
|
free_picture(ps, &ps->root_picture);
|
|
free_paint(ps, &ps->tgt_buffer);
|
|
|
|
// Free other X resources
|
|
free_root_tile(ps);
|
|
free_region(ps, &ps->screen_reg);
|
|
free_region(ps, &ps->all_damage);
|
|
for (int i = 0; i < CGLX_MAX_BUFFER_AGE; ++i)
|
|
free_region(ps, &ps->all_damage_last[i]);
|
|
free(ps->expose_rects);
|
|
free(ps->shadow_corner);
|
|
free(ps->shadow_top);
|
|
free(ps->gaussian_map);
|
|
|
|
free(ps->o.config_file);
|
|
free(ps->o.write_pid_path);
|
|
free(ps->o.display);
|
|
free(ps->o.display_repr);
|
|
free(ps->o.logpath);
|
|
for (int i = 0; i < MAX_BLUR_PASS; ++i) {
|
|
free(ps->o.blur_kerns[i]);
|
|
free(ps->blur_kerns_cache[i]);
|
|
}
|
|
free(ps->pfds_read);
|
|
free(ps->pfds_write);
|
|
free(ps->pfds_except);
|
|
free(ps->o.glx_fshader_win_str);
|
|
free_xinerama_info(ps);
|
|
|
|
#ifdef CONFIG_VSYNC_OPENGL
|
|
glx_destroy(ps);
|
|
#endif
|
|
|
|
// Free double buffer
|
|
if (ps->root_dbe) {
|
|
XdbeDeallocateBackBufferName(ps->dpy, ps->root_dbe);
|
|
ps->root_dbe = None;
|
|
}
|
|
|
|
#ifdef CONFIG_VSYNC_DRM
|
|
// Close file opened for DRM VSync
|
|
if (ps->drm_fd >= 0) {
|
|
close(ps->drm_fd);
|
|
ps->drm_fd = -1;
|
|
}
|
|
#endif
|
|
|
|
// Release overlay window
|
|
if (ps->overlay) {
|
|
XCompositeReleaseOverlayWindow(ps->dpy, ps->overlay);
|
|
ps->overlay = None;
|
|
}
|
|
|
|
// Free reg_win
|
|
if (ps->reg_win) {
|
|
XDestroyWindow(ps->dpy, ps->reg_win);
|
|
ps->reg_win = None;
|
|
}
|
|
|
|
// Flush all events
|
|
XSync(ps->dpy, True);
|
|
|
|
#ifdef DEBUG_XRC
|
|
// Report about resource leakage
|
|
xrc_report_xid();
|
|
#endif
|
|
|
|
// Free timeouts
|
|
ps->tmout_unredir = NULL;
|
|
timeout_clear(ps);
|
|
|
|
if (ps == ps_g)
|
|
ps_g = NULL;
|
|
}
|
|
|
|
/*
|
|
static inline void
|
|
dump_img(session_t *ps) {
|
|
int len = 0;
|
|
unsigned char *d = glx_take_screenshot(ps, &len);
|
|
write_binary_data("/tmp/dump.raw", d, len);
|
|
free(d);
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Do the actual work.
|
|
*
|
|
* @param ps current session
|
|
*/
|
|
static void
|
|
session_run(session_t *ps) {
|
|
win *t;
|
|
|
|
if (ps->o.sw_opti)
|
|
ps->paint_tm_offset = get_time_timeval().tv_usec;
|
|
|
|
ps->reg_ignore_expire = true;
|
|
|
|
t = paint_preprocess(ps, ps->list);
|
|
|
|
if (ps->redirected)
|
|
paint_all(ps, None, None, t);
|
|
|
|
// Initialize idling
|
|
ps->idling = false;
|
|
|
|
// Main loop
|
|
while (!ps->reset) {
|
|
ps->ev_received = false;
|
|
|
|
while (mainloop(ps))
|
|
continue;
|
|
|
|
if (ps->o.benchmark) {
|
|
if (ps->o.benchmark_wid) {
|
|
win *w = find_win(ps, ps->o.benchmark_wid);
|
|
if (!w) {
|
|
printf_errf("(): Couldn't find specified benchmark window.");
|
|
session_destroy(ps);
|
|
exit(1);
|
|
}
|
|
add_damage_win(ps, w);
|
|
}
|
|
else {
|
|
force_repaint(ps);
|
|
}
|
|
}
|
|
|
|
// idling will be turned off during paint_preprocess() if needed
|
|
ps->idling = true;
|
|
|
|
t = paint_preprocess(ps, ps->list);
|
|
ps->tmout_unredir_hit = false;
|
|
|
|
// If the screen is unredirected, free all_damage to stop painting
|
|
if (!ps->redirected || ON == ps->o.stoppaint_force)
|
|
free_region(ps, &ps->all_damage);
|
|
|
|
XserverRegion all_damage_orig = None;
|
|
if (ps->o.resize_damage > 0)
|
|
all_damage_orig = copy_region(ps, ps->all_damage);
|
|
resize_region(ps, ps->all_damage, ps->o.resize_damage);
|
|
if (ps->all_damage && !is_region_empty(ps, ps->all_damage, NULL)) {
|
|
static int paint = 0;
|
|
paint_all(ps, ps->all_damage, all_damage_orig, t);
|
|
ps->reg_ignore_expire = false;
|
|
paint++;
|
|
if (ps->o.benchmark && paint >= ps->o.benchmark)
|
|
exit(0);
|
|
XSync(ps->dpy, False);
|
|
ps->all_damage = None;
|
|
}
|
|
free_region(ps, &all_damage_orig);
|
|
|
|
if (ps->idling)
|
|
ps->fade_time = 0L;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Turn on the program reset flag.
|
|
*
|
|
* This will result in compton resetting itself after next paint.
|
|
*/
|
|
static void
|
|
reset_enable(int __attribute__((unused)) signum) {
|
|
session_t * const ps = ps_g;
|
|
|
|
ps->reset = true;
|
|
}
|
|
|
|
/**
|
|
* The function that everybody knows.
|
|
*/
|
|
int
|
|
main(int argc, char **argv) {
|
|
// Set locale so window names with special characters are interpreted
|
|
// correctly
|
|
setlocale(LC_ALL, "");
|
|
|
|
// Initialize signal handlers
|
|
sigfillset(&block_mask);
|
|
usr_action.sa_handler = handle_siguser;
|
|
usr_action.sa_mask = block_mask;
|
|
usr_action.sa_flags = 0;
|
|
sigaction(SIGUSR1, &usr_action, NULL);
|
|
sigaction(SIGUSR2, &usr_action, NULL);
|
|
sigaction(SIGTERM, &usr_action, NULL);
|
|
|
|
// Main loop
|
|
session_t *ps_old = ps_g;
|
|
while (1) {
|
|
ps_g = session_init(ps_old, argc, argv);
|
|
if (!ps_g) {
|
|
printf_errf("(): Failed to create new session.");
|
|
return 1;
|
|
}
|
|
|
|
/* Under no circumstances should these two lines EVER be moved earlier in main() than this point */
|
|
atexit(delete_pid_file);
|
|
write_pid_file(getpid());
|
|
|
|
session_run(ps_g);
|
|
ps_old = ps_g;
|
|
session_destroy(ps_g);
|
|
}
|
|
|
|
free(ps_g);
|
|
|
|
return 0;
|
|
}
|