diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 2612eceb..070f1e1c 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -103,6 +103,12 @@ OPTIONS *--inactive-dim* 'VALUE':: Dim inactive windows. (0.0 - 1.0, defaults to 0.0) +*--corner-radius* 'VALUE':: + Sets the radius of rounded window corners. When > 0, the compositor will round the corners of windows. Does not interact well with *--transparent-clipping*. (defaults to 0). + +*--rounded-corners-exclude* 'CONDITION':: + Exclude conditions for rounded corners. + *--mark-wmwin-focused*:: Try to detect WM windows (a non-override-redirect window with no child that has 'WM_STATE') and mark them as active. diff --git a/picom.sample.conf b/picom.sample.conf index 83493d45..097fed51 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -133,6 +133,22 @@ focus-exclude = [ "class_g = 'Cairo-clock'" ]; # opacity-rule = [] +################################# +# Corners # +################################# + +# Sets the radius of rounded window corners. When > 0, the compositor will +# round the corners of windows. Does not interact well with +# `transparent-clipping`. +corner-radius = 0 + +# Exclude conditions for rounded corners. +rounded-corners-exclude = [ + "window_type = 'dock'", + "window_type = 'desktop'" +]; + + ################################# # Background-Blurring # ################################# diff --git a/src/config.c b/src/config.c index e10ec341..a2963247 100644 --- a/src/config.c +++ b/src/config.c @@ -505,6 +505,7 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) { + // clang-format off *opt = (struct options){ .backend = BKEND_XRENDER, .glx_no_stencil = false, @@ -537,6 +538,8 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .shadow_ignore_shaped = false, .xinerama_shadow_crop = false, + .corner_radius = 0, + .fade_in_step = 0.028, .fade_out_step = 0.03, .fade_delta = 10, @@ -572,7 +575,10 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .no_ewmh_fullscreen = false, .track_leader = false, + + .rounded_corners_blacklist = NULL }; + // clang-format on char *ret = NULL; #ifdef CONFIG_LIBCONFIG diff --git a/src/config.h b/src/config.h index d8741a5e..bd3e08bf 100644 --- a/src/config.h +++ b/src/config.h @@ -217,6 +217,10 @@ typedef struct options { c2_lptr_t *opacity_rules; /// Limit window brightness double max_brightness; + // Radius of rounded window corners + int corner_radius; + /// Rounded corners blacklist. A linked list of conditions. + c2_lptr_t *rounded_corners_blacklist; // === Focus related === /// Whether to try to detect WM windows and mark them as focused. diff --git a/src/config_libconfig.c b/src/config_libconfig.c index b488b617..1c0c6117 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -374,6 +374,10 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // --active_opacity if (config_lookup_float(&cfg, "active-opacity", &dval)) opt->active_opacity = normalize_d(dval); + // --corner-radius + config_lookup_int(&cfg, "corner-radius", &opt->corner_radius); + // --rounded-corners-exclude + parse_cfg_condlst(&cfg, &opt->rounded_corners_blacklist, "rounded-corners-exclude"); // -e (frame_opacity) config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity); // -c (shadow_enable) diff --git a/src/options.c b/src/options.c index 976b0a0f..6632e7d2 100644 --- a/src/options.c +++ b/src/options.c @@ -117,6 +117,13 @@ static void usage(const char *argv0, int ret) { "--active-opacity opacity\n" " Default opacity for active windows. (0.0 - 1.0)\n" "\n" + "--corner-radius value\n" + " Sets the radius of rounded window corners. When > 0, the compositor\n" + " will round the corners of windows. (defaults to 0).\n" + "\n" + "--rounded-corners-exclude condition\n" + " Exclude conditions for rounded corners.\n" + "\n" "--mark-wmwin-focused\n" " Try to detect WM windows and mark them as active.\n" "\n" @@ -440,6 +447,8 @@ static const struct option longopts[] = { {"blur-deviation", required_argument, NULL, 330}, {"blur-strength", required_argument, NULL, 331}, {"shadow-color", required_argument, NULL, 332}, + {"corner-radius", required_argument, NULL, 333}, + {"rounded-corners-exclude", required_argument, NULL, 334}, {"experimental-backends", no_argument, NULL, 733}, {"monitor-repaint", no_argument, NULL, 800}, {"diagnostics", no_argument, NULL, 801}, @@ -859,7 +868,14 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // --blur-strength opt->blur_strength = atoi(optarg); break; - + case 333: + // --cornor-radius + opt->corner_radius = atoi(optarg); + break; + case 334: + // --rounded-corners-exclude + condlst_add(&opt->rounded_corners_blacklist, optarg); + break; P_CASEBOOL(733, experimental_backends); P_CASEBOOL(800, monitor_repaint); case 801: opt->print_diagnostics = true; break; @@ -986,6 +1002,13 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, "properly under X Render backend."); } + if (opt->corner_radius > 0 && + (opt->backend != BKEND_XRENDER || opt->experimental_backends)) { + log_warn("Rounded corner is only supported on legacy xrender backend, it " + "will be disabled"); + opt->corner_radius = 0; + } + return true; } diff --git a/src/picom.c b/src/picom.c index 17005c38..9234253b 100644 --- a/src/picom.c +++ b/src/picom.c @@ -751,10 +751,11 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { // w->mode == WMODE_SOLID or WMODE_FRAME_TRANS region_t *tmp = rc_region_new(); if (w->mode == WMODE_SOLID) { - *tmp = win_get_bounding_shape_global_by_val(w); + *tmp = + win_get_bounding_shape_global_without_corners_by_val(w); } else { // w->mode == WMODE_FRAME_TRANS - win_get_region_noframe_local(w, tmp); + win_get_region_noframe_local_without_corners(w, tmp); pixman_region32_intersect(tmp, tmp, &w->bounding_shape); pixman_region32_translate(tmp, w->g.x, w->g.y); } @@ -1886,6 +1887,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, c2_list_postprocess(ps, ps->o.blur_background_blacklist) && c2_list_postprocess(ps, ps->o.invert_color_list) && c2_list_postprocess(ps, ps->o.opacity_rules) && + c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) && c2_list_postprocess(ps, ps->o.focus_blacklist))) { log_error("Post-processing of conditionals failed, some of your rules " "might not work"); @@ -2263,6 +2265,7 @@ static void session_destroy(session_t *ps) { free_wincondlst(&ps->o.opacity_rules); free_wincondlst(&ps->o.paint_blacklist); free_wincondlst(&ps->o.unredir_if_possible_blacklist); + free_wincondlst(&ps->o.rounded_corners_blacklist); // Free tracked atom list { diff --git a/src/render.c b/src/render.c index deb2fa48..58713aa5 100644 --- a/src/render.c +++ b/src/render.c @@ -229,21 +229,104 @@ uint32_t make_rectangle(int x, int y, int wid, int hei, xcb_render_trapezoid_t t return 1; } -void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, double opacity, - bool argb, bool neg, xcb_render_picture_t pict, glx_texture_t *ptex, - const region_t *reg_paint, const glx_prog_main_t *pprogram) { +uint32_t make_rounded_window_shape(xcb_render_trapezoid_t traps[], uint32_t max_ntraps, + int cr, int wid, int hei) { + uint32_t n = make_circle(cr, cr, cr, max_ntraps, traps); + n += make_circle(wid - cr, cr, cr, max_ntraps, traps + n); + n += make_circle(wid - cr, hei - cr, cr, max_ntraps, traps + n); + n += make_circle(cr, hei - cr, cr, max_ntraps, traps + n); + n += make_rectangle(0, cr, wid, hei - 2 * cr, traps + n); + n += make_rectangle(cr, 0, wid - 2 * cr, cr, traps + n); + n += make_rectangle(cr, hei - cr, wid - 2 * cr, cr, traps + n); + return n; +} + +void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, int fullwid, + int fullhei, double opacity, bool argb, bool neg, int cr, + xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint, + const glx_prog_main_t *pprogram, clip_t *clip) { switch (ps->o.backend) { case BKEND_XRENDER: case BKEND_XR_GLX_HYBRID: { auto alpha_step = (int)(opacity * MAX_ALPHA); xcb_render_picture_t alpha_pict = ps->alpha_picts[alpha_step]; if (alpha_step != 0) { - uint8_t op = ((!argb && !alpha_pict) ? XCB_RENDER_PICT_OP_SRC - : XCB_RENDER_PICT_OP_OVER); - xcb_render_composite( - ps->c, op, pict, alpha_pict, ps->tgt_buffer.pict, - to_i16_checked(x), to_i16_checked(y), 0, 0, to_i16_checked(dx), - to_i16_checked(dy), to_u16_checked(wid), to_u16_checked(hei)); + if (cr) { + xcb_render_picture_t p_tmp = x_create_picture_with_standard( + ps->c, ps->root, fullwid, fullhei, + XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = { + .red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = { + .x = 0, + .y = 0, + .width = to_u16_checked(fullwid), + .height = to_u16_checked(fullhei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + p_tmp, trans, 1, &rect); + + uint32_t max_ntraps = to_u32_checked(cr); + xcb_render_trapezoid_t traps[4 * max_ntraps + 3]; + + uint32_t n = make_rounded_window_shape( + traps, max_ntraps, cr, fullwid, fullhei); + + xcb_render_trapezoids( + ps->c, XCB_RENDER_PICT_OP_OVER, alpha_pict, p_tmp, + x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), + 0, 0, n, traps); + + xcb_render_composite( + ps->c, XCB_RENDER_PICT_OP_OVER, pict, p_tmp, + ps->tgt_buffer.pict, to_i16_checked(x), + to_i16_checked(y), to_i16_checked(x), to_i16_checked(y), + to_i16_checked(dx), to_i16_checked(dy), + to_u16_checked(wid), to_u16_checked(hei)); + + xcb_render_free_picture(ps->c, p_tmp); + + } else { + xcb_render_picture_t p_tmp = alpha_pict; + if (clip) { + p_tmp = x_create_picture_with_standard( + ps->c, ps->root, wid, hei, + XCB_PICT_STANDARD_ARGB_32, 0, 0); + + xcb_render_color_t black = { + .red = 255, .blue = 255, .green = 255, .alpha = 255}; + const xcb_rectangle_t rect = { + .x = 0, + .y = 0, + .width = to_u16_checked(wid), + .height = to_u16_checked(hei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + p_tmp, black, 1, &rect); + if (alpha_pict) { + xcb_render_composite( + ps->c, XCB_RENDER_PICT_OP_SRC, + alpha_pict, XCB_NONE, p_tmp, 0, 0, 0, + 0, 0, 0, to_u16_checked(wid), + to_u16_checked(hei)); + } + xcb_render_composite( + ps->c, XCB_RENDER_PICT_OP_OUT_REVERSE, + clip->pict, XCB_NONE, p_tmp, 0, 0, 0, 0, + to_i16_checked(clip->x), to_i16_checked(clip->y), + to_u16_checked(wid), to_u16_checked(hei)); + } + uint8_t op = ((!argb && !alpha_pict && !clip) + ? XCB_RENDER_PICT_OP_SRC + : XCB_RENDER_PICT_OP_OVER); + + xcb_render_composite( + ps->c, op, pict, p_tmp, ps->tgt_buffer.pict, + to_i16_checked(x), to_i16_checked(y), 0, 0, + to_i16_checked(dx), to_i16_checked(dy), + to_u16_checked(wid), to_u16_checked(hei)); + if (clip) { + xcb_render_free_picture(ps->c, p_tmp); + } + } } break; } @@ -269,17 +352,21 @@ paint_region(session_t *ps, const struct managed_win *w, int x, int y, int wid, double opacity, const region_t *reg_paint, xcb_render_picture_t pict) { const int dx = (w ? w->g.x : 0) + x; const int dy = (w ? w->g.y : 0) + y; + const int fullwid = w ? w->widthb : 0; + const int fullhei = w ? w->heightb : 0; const bool argb = (w && (win_has_alpha(w) || ps->o.force_win_blend)); const bool neg = (w && w->invert_color); - render(ps, x, y, dx, dy, wid, hei, opacity, argb, neg, pict, + render(ps, x, y, dx, dy, wid, hei, fullwid, fullhei, opacity, argb, neg, + w ? w->corner_radius : 0, pict, (w ? w->paint.ptex : ps->root_tile_paint.ptex), reg_paint, #ifdef CONFIG_OPENGL w ? &ps->glx_prog_win : NULL #else NULL #endif - ); + , + XCB_NONE); } /** @@ -655,9 +742,43 @@ win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { return; } + xcb_render_picture_t td = XCB_NONE; + bool should_clip = + (w->corner_radius > 0) && (!ps->o.wintype_option[w->window_type].full_shadow); + if (should_clip) { + uint32_t max_ntraps = to_u32_checked(w->corner_radius); + xcb_render_trapezoid_t traps[4 * max_ntraps + 3]; + uint32_t n = make_rounded_window_shape( + traps, max_ntraps, w->corner_radius, w->widthb, w->heightb); + + td = x_create_picture_with_standard(ps->c, ps->root, w->widthb, w->heightb, + XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = {.red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, + .y = 0, + .width = to_u16_checked(w->widthb), + .height = to_u16_checked(w->heightb)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, trans, 1, &rect); + + auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0); + xcb_render_trapezoids(ps->c, XCB_RENDER_PICT_OP_OVER, solid, td, + x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), + 0, 0, n, traps); + xcb_render_free_picture(ps->c, solid); + } + + clip_t clip = { + .pict = td, + .x = -(w->shadow_dx), + .y = -(w->shadow_dy), + }; render(ps, 0, 0, w->g.x + w->shadow_dx, w->g.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, NULL); + w->shadow_height, w->widthb, w->heightb, w->shadow_opacity, true, false, 0, + w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, NULL, + should_clip ? &clip : NULL); + if (td) { + xcb_render_free_picture(ps->c, td); + } } /** @@ -675,9 +796,10 @@ win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { * * @return true if successful, false otherwise */ -static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y, - uint16_t wid, uint16_t hei, struct x_convolution_kernel **blur_kerns, - int nkernels, const region_t *reg_clip) { +static bool +xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y, + uint16_t wid, uint16_t hei, struct x_convolution_kernel **blur_kerns, + int nkernels, const region_t *reg_clip, xcb_render_picture_t rounded) { assert(blur_kerns); assert(blur_kerns[0]); @@ -722,7 +844,7 @@ static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t } if (src_pict != tgt_buffer) - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, rounded, tgt_buffer, 0, 0, 0, 0, x, y, wid, hei); free_picture(ps->c, &tmp_picture); @@ -740,6 +862,7 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t const int16_t y = w->g.y; const auto wid = to_u16_checked(w->widthb); const auto hei = to_u16_checked(w->heightb); + const int cr = w ? w->corner_radius : 0; double factor_center = 1.0; // Adjust blur strength according to window opacity, to make it appear @@ -771,6 +894,33 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t &ps->blur_kerns_cache[i]); } + xcb_render_picture_t td = XCB_NONE; + if (cr) { + uint32_t max_ntraps = to_u32_checked(cr); + xcb_render_trapezoid_t traps[4 * max_ntraps + 3]; + uint32_t n = + make_rounded_window_shape(traps, max_ntraps, cr, wid, hei); + + td = x_create_picture_with_standard( + ps->c, ps->root, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = { + .red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, + .y = 0, + .width = to_u16_checked(wid), + .height = to_u16_checked(hei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, + trans, 1, &rect); + + auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0); + + xcb_render_trapezoids( + ps->c, XCB_RENDER_PICT_OP_OVER, solid, td, + x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0, + 0, n, traps); + xcb_render_free_picture(ps->c, solid); + } + // Minimize the region we try to blur, if the window itself is not // opaque, only the frame is. region_t reg_blur = win_get_bounding_shape_global_by_val(w); @@ -782,10 +932,14 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t pixman_region32_subtract(®_blur, ®_blur, ®_noframe); pixman_region32_fini(®_noframe); } + // Translate global coordinates to local ones pixman_region32_translate(®_blur, -x, -y); xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, - ps->o.blur_kernel_count, ®_blur); + ps->o.blur_kernel_count, ®_blur, td); + if (td) { + xcb_render_free_picture(ps->c, td); + } pixman_region32_clear(®_blur); } break; #ifdef CONFIG_OPENGL @@ -894,7 +1048,9 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // // Whether this is beneficial is to be determined XXX for (auto w = t; w; w = w->prev_trans) { - region_t bshape = win_get_bounding_shape_global_by_val(w); + region_t bshape_no_corners = + win_get_bounding_shape_global_without_corners_by_val(w); + region_t bshape_corners = win_get_bounding_shape_global_by_val(w); // Painting shadow if (w->shadow) { // Lazy shadow building @@ -923,7 +1079,7 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // saving GPU power and handling shaped windows (XXX // unconfirmed) if (!ps->o.wintype_option[w->window_type].full_shadow) - pixman_region32_subtract(®_tmp, ®_tmp, &bshape); + pixman_region32_subtract(®_tmp, ®_tmp, &bshape_no_corners); if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 && w->xinerama_scr < ps->xinerama_nscrs) @@ -950,8 +1106,9 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // Remember, reg_ignore is the union of all windows above the current // window. pixman_region32_subtract(®_tmp, ®ion, w->reg_ignore); - pixman_region32_intersect(®_tmp, ®_tmp, &bshape); - pixman_region32_fini(&bshape); + pixman_region32_intersect(®_tmp, ®_tmp, &bshape_corners); + pixman_region32_fini(&bshape_corners); + pixman_region32_fini(&bshape_no_corners); if (pixman_region32_not_empty(®_tmp)) { set_tgt_clip(ps, ®_tmp); diff --git a/src/render.h b/src/render.h index 92b71c8e..95a46dbe 100644 --- a/src/render.h +++ b/src/render.h @@ -25,9 +25,16 @@ typedef struct paint { #endif } paint_t; -void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, double opacity, - bool argb, bool neg, xcb_render_picture_t pict, glx_texture_t *ptex, - const region_t *reg_paint, const glx_prog_main_t *pprogram); +typedef struct clip { + xcb_render_picture_t pict; + int x; + int y; +} clip_t; + +void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, int fullw, + int fullh, double opacity, bool argb, bool neg, int cr, + xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint, + const glx_prog_main_t *pprogram, clip_t *clip); void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint); void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage); diff --git a/src/win.c b/src/win.c index 5bdaacc8..13cc71d0 100644 --- a/src/win.c +++ b/src/win.c @@ -79,9 +79,19 @@ static void win_update_prop_shadow(session_t *ps, struct managed_win *w); */ static void win_update_leader(session_t *ps, struct managed_win *w); +/// Generate a "no corners" region function, from a function that returns the +/// region via a region_t pointer argument. Corners of the window will be removed from +/// the returned region. +/// Function signature has to be (win *, region_t *) +#define gen_without_corners(fun) \ + void fun##_without_corners(const struct managed_win *w, region_t *res) { \ + fun(w, res); \ + win_region_remove_corners(w, res); \ + } + /// Generate a "return by value" function, from a function that returns the /// region via a region_t pointer argument. -/// Function signature has to be (win *, region_t *) +/// Function signature has to be (win *) #define gen_by_val(fun) \ region_t fun##_by_val(const struct managed_win *w) { \ region_t ret; \ @@ -217,9 +227,13 @@ void win_get_region_noframe_local(const struct managed_win *w, region_t *res) { pixman_region32_fini(res); if (width > 0 && height > 0) { pixman_region32_init_rect(res, x, y, (uint)width, (uint)height); + } else { + pixman_region32_init(res); } } +gen_without_corners(win_get_region_noframe_local); + void win_get_region_frame_local(const struct managed_win *w, region_t *res) { const margin_t extents = win_calc_frame_extents(w); auto outer_width = extents.left + extents.right + w->g.width; @@ -1019,6 +1033,26 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w) win_set_blur_background(ps, w, blur_background_new); } +/** + * Determine if a window should have rounded corners. + */ +static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) { + if (ps->o.corner_radius == 0) { + w->corner_radius = 0; + return; + } + + // Don't round full screen windows & excluded windows + if ((w && win_is_fullscreen(ps, w)) || + c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL)) { + w->corner_radius = 0; + log_debug("Not rounding corners for window %#010x", w->base.id); + } else { + w->corner_radius = ps->o.corner_radius; + log_debug("Rounding corners for window %#010x", w->base.id); + } +} + /** * Update window opacity according to opacity rules. */ @@ -1053,6 +1087,7 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) { win_determine_shadow(ps, w); win_determine_invert_color(ps, w); win_determine_blur_background(ps, w); + win_determine_rounded_corners(ps, w); w->mode = win_calc_mode(w); log_debug("Window mode changed to %d", w->mode); win_update_opacity_rule(ps, w); @@ -1413,6 +1448,8 @@ struct win *fill_win(session_t *ps, struct win *w) { // Initialized during paint .paint = PAINT_INIT, .shadow_paint = PAINT_INIT, + + .corner_radius = 0, }; assert(!w->destroyed); diff --git a/src/win.h b/src/win.h index 83cebec5..12dc0633 100644 --- a/src/win.h +++ b/src/win.h @@ -208,6 +208,9 @@ struct managed_win { /// Last window opacity value set by the rules. double opacity_set; + /// Radius of rounded window corners + int corner_radius; + // Fading-related members /// Override value of window fade state. Set by D-Bus method calls. switch_t fade_force; @@ -348,6 +351,7 @@ void add_damage_from_win(session_t *ps, const struct managed_win *w); * Return region in global coordinates. */ void win_get_region_noframe_local(const struct managed_win *w, region_t *); +void win_get_region_noframe_local_without_corners(const struct managed_win *w, region_t *); /// Get the region for the frame of the window void win_get_region_frame_local(const struct managed_win *w, region_t *res); @@ -438,6 +442,24 @@ static inline attr_unused void win_set_property_stale(struct managed_win *w, xcb /// Free all resources in a struct win void free_win_res(session_t *ps, struct managed_win *w); +static inline void win_region_remove_corners(const struct managed_win *w, region_t *res) { + region_t corners; + pixman_region32_init_rects( + &corners, + (rect_t[]){ + {.x1 = 0, .y1 = 0, .x2 = w->corner_radius, .y2 = w->corner_radius}, + {.x1 = 0, .y1 = w->heightb - w->corner_radius, .x2 = w->corner_radius, .y2 = w->heightb}, + {.x1 = w->widthb - w->corner_radius, .y1 = 0, .x2 = w->widthb, .y2 = w->corner_radius}, + {.x1 = w->widthb - w->corner_radius, + .y1 = w->heightb - w->corner_radius, + .x2 = w->widthb, + .y2 = w->heightb}, + }, + 4); + pixman_region32_subtract(res, res, &corners); + pixman_region32_fini(&corners); +} + static inline region_t attr_unused win_get_bounding_shape_global_by_val(struct managed_win *w) { region_t ret; pixman_region32_init(&ret); @@ -446,6 +468,16 @@ static inline region_t attr_unused win_get_bounding_shape_global_by_val(struct m return ret; } +static inline region_t +win_get_bounding_shape_global_without_corners_by_val(struct managed_win *w) { + region_t ret; + pixman_region32_init(&ret); + pixman_region32_copy(&ret, &w->bounding_shape); + win_region_remove_corners(w, &ret); + pixman_region32_translate(&ret, w->g.x, w->g.y); + return ret; +} + /** * Calculate the extents of the frame of the given window based on EWMH * _NET_FRAME_EXTENTS and the X window border width.