diff --git a/compton.sample.conf b/compton.sample.conf index 599de2d5..0f2e4fbb 100644 --- a/compton.sample.conf +++ b/compton.sample.conf @@ -22,6 +22,8 @@ inactive-opacity-override = false; alpha-step = 0.06; # inactive-dim = 0.2; # inactive-dim-fixed = true; +# blur-background = true; +# blur-background-frame = true; # Fading fading = true; diff --git a/src/compton.c b/src/compton.c index a9053811..ceba178f 100644 --- a/src/compton.c +++ b/src/compton.c @@ -1422,6 +1422,66 @@ win_paint_shadow(session_t *ps, win *w, Picture tgt_buffer) { w->shadow_width, w->shadow_height); } +/** + * Blur the background of a window. + */ +static inline void +win_blur_background(session_t *ps, win *w, Picture tgt_buffer, + XserverRegion reg_paint) { + // 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), + }; + // Extra pixels we have to get for the blur to work correctly. + const static int expand = 1; + + Pixmap tmp_pixmap = None; + Picture tmp_picture = None; + + int x = w->a.x; + int y = w->a.y; + int wid = w->widthb; + int hei = w->heightb; + + int xe = x - expand; + int ye = y - expand; + int wide = wid + expand * 2; + int heie = hei + expand * 2; + + // Directly copying from tgt_buffer does not work, so we create a + // Picture in the middle. We expand the region slightly, to make sure + // the blur on border pixels work correctly. + tmp_pixmap = XCreatePixmap(ps->dpy, ps->root, wide, heie, ps->depth); + if (!tmp_pixmap) + goto win_blur_background_err; + + tmp_picture = XRenderCreatePicture(ps->dpy, tmp_pixmap, + XRenderFindVisualFormat(ps->dpy, ps->vis), 0, 0); + if (!tmp_picture) + goto win_blur_background_err; + + // Copy the content to tmp_picture, then copy back. + // We lift the PictureClipRegion here, to get the expanded pixels. + XFixesSetPictureClipRegion(ps->dpy, tgt_buffer, 0, 0, None); + XRenderComposite(ps->dpy, PictOpSrc, tgt_buffer, None, tmp_picture, xe, ye, 0, 0, 0, 0, wide, heie); + XFixesSetPictureClipRegion(ps->dpy, tgt_buffer, 0, 0, reg_paint); + XRenderSetPictureFilter(ps->dpy, tmp_picture, XRFILTER_CONVOLUTION, (XFixed *) convolution_blur, sizeof(convolution_blur) / sizeof(XFixed)); + XRenderComposite(ps->dpy, PictOpSrc, tmp_picture, None, tgt_buffer, expand, expand, 0, 0, x, y, wid, hei); + xrfilter_reset(ps, tmp_picture); + +win_blur_background_err: + free_pixmap(ps, &tmp_pixmap); + free_picture(ps, &tmp_picture); +} + /** * Paint a window itself and dim it if asked. */ @@ -1610,7 +1670,8 @@ paint_all(session_t *ps, XserverRegion region, win *t) { // Detect if the region is empty before painting if (region == reg_paint || !is_region_empty(ps, reg_paint)) { - XFixesSetPictureClipRegion(ps->dpy, ps->tgt_buffer, 0, 0, reg_paint); + XFixesSetPictureClipRegion(ps->dpy, ps->tgt_buffer, 0, 0, + reg_paint); win_paint_shadow(ps, w, ps->tgt_buffer); } @@ -1638,6 +1699,11 @@ paint_all(session_t *ps, XserverRegion region, win *t) { if (!is_region_empty(ps, reg_paint)) { XFixesSetPictureClipRegion(ps->dpy, ps->tgt_buffer, 0, 0, reg_paint); + // Blur window background + if ((ps->o.blur_background && WINDOW_SOLID != w->mode) + || (ps->o.blur_background_frame && w->frame_opacity)) { + win_blur_background(ps, w, ps->tgt_buffer, reg_paint); + } // Painting the window win_paint_win(ps, w, ps->tgt_buffer); @@ -1670,7 +1736,7 @@ paint_all(session_t *ps, XserverRegion region, win *t) { // DBE painting mode, only need to swap the buffer if (ps->o.dbe) { XdbeSwapInfo swap_info = { - .swap_window = (ps->o.paint_on_overlay ? ps->overlay: ps->root), + .swap_window = get_tgt_window(ps), // Is it safe to use XdbeUndefined? .swap_action = XdbeCopied }; @@ -3689,6 +3755,14 @@ usage(void) { " 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" "\n" "Format of a condition:\n" "\n" @@ -4095,6 +4169,11 @@ parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { parse_cfg_condlst(&cfg, &ps->o.shadow_blacklist, "shadow-exclude"); // --focus-exclude parse_cfg_condlst(&cfg, &ps->o.focus_blacklist, "focus-exclude"); + // --blur-background + lcfg_lookup_bool(&cfg, "blur-background", &ps->o.blur_background); + // --blur-background-frame + lcfg_lookup_bool(&cfg, "blur-background-frame", + &ps->o.blur_background_frame); // Wintype settings { wintype_t i; @@ -4154,6 +4233,8 @@ get_cfg(session_t *ps, int argc, char *const *argv) { { "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 }, // Must terminate with a NULL entry { NULL, 0, NULL, 0 }, }; @@ -4371,6 +4452,14 @@ get_cfg(session_t *ps, int argc, char *const *argv) { // --detect-client-leader ps->o.detect_client_leader = true; break; + case 283: + // --blur-background + ps->o.blur_background = true; + break; + case 284: + // --blur-background-frame + ps->o.blur_background_frame = true; + break; default: usage(); } @@ -4409,6 +4498,10 @@ get_cfg(session_t *ps, int argc, char *const *argv) { 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; + // Other variables determined by options // Determine whether we need to track focus changes @@ -4785,6 +4878,32 @@ init_overlay(session_t *ps) { } } +/** + * Query needed X Render filters to check for their existence. + */ +static void +init_filters(session_t *ps) { + if (ps->o.blur_background || ps->o.blur_background_frame) { + // 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; + } + } + XFree(pf); + + // Turn features off if any required filter is not present + if (!ps->xrfilter_convolution_exists) { + fprintf(stderr, "X Render convolution filter unsupported by your X server. Background blur disabled.\n"); + ps->o.blur_background = false; + ps->o.blur_background_frame = false; + } + } +} + /** * Redirect all windows. */ @@ -4908,6 +5027,8 @@ session_init(session_t *ps_old, int argc, char **argv) { .inactive_dim = 0.0, .inactive_dim_fixed = false, .alpha_step = 0.03, + .blur_background = false, + .blur_background_frame = false, .wintype_focus = { false }, .use_ewmh_active_win = false, @@ -4977,6 +5098,7 @@ session_init(session_t *ps_old, int argc, char **argv) { .glx_event = 0, .glx_error = 0, .dbe_exists = false, + .xrfilter_convolution_exists = false, .atom_opacity = None, .atom_frame_extents = None, @@ -5152,6 +5274,8 @@ session_init(session_t *ps_old, int argc, char **argv) { } } + init_filters(ps); + ps->black_picture = solid_picture(ps, true, 1, 0, 0, 0); // Generates another Picture for shadows if the color is modified by diff --git a/src/compton.h b/src/compton.h index 16272e6f..8aecaa76 100644 --- a/src/compton.h +++ b/src/compton.h @@ -116,6 +116,10 @@ #define US_PER_SEC 1000000L #define MS_PER_SEC 1000 +#define XRFILTER_CONVOLUTION "convolution" +#define XRFILTER_GUASSIAN "gaussian" +#define XRFILTER_BINOMIAL "binomial" + // Window flags // Window size is changed @@ -326,6 +330,11 @@ typedef struct { bool inactive_dim_fixed; /// Step for pregenerating alpha pictures. 0.01 - 1.0. double alpha_step; + /// Whether to blur background of semi-transparent / ARGB windows. + bool blur_background; + /// Whether to blur background when the window frame is not opaque. + /// Implies blur_background. + bool blur_background_frame; // === Focus related === /// Consider windows of specific types to be always focused. @@ -519,6 +528,8 @@ typedef struct { #endif /// Whether X DBE extension exists. bool dbe_exists; + /// Whether X Render convolution filter exists. + bool xrfilter_convolution_exists; // === Atoms === /// Atom of property _NET_WM_OPACITY. @@ -778,6 +789,22 @@ set_ignore_next(session_t *ps) { static int should_ignore(session_t *ps, unsigned long sequence); +/** + * Return the painting target window. + */ +static inline Window +get_tgt_window(session_t *ps) { + return ps->o.paint_on_overlay ? ps->overlay: ps->root; +} + +/** + * Reset filter on a Picture. + */ +static inline void +xrfilter_reset(session_t *ps, Picture p) { + XRenderSetPictureFilter(ps->dpy, p, "Nearest", NULL, 0); +} + /** * Subtract two unsigned long values. *