diff options
| author | allusive-dev <[email protected]> | 2023-10-30 15:12:21 +1100 |
|---|---|---|
| committer | allusive-dev <[email protected]> | 2023-10-30 15:12:21 +1100 |
| commit | ac33357e7ce7c474aeaffc92e381020289d767a2 (patch) | |
| tree | 7f05fa79b3ccd7834f85cc65a07fbd4f8030eb94 /src | |
| parent | Create FUNDING.yml (diff) | |
| download | compfy-ac33357e7ce7c474aeaffc92e381020289d767a2.tar.xz compfy-ac33357e7ce7c474aeaffc92e381020289d767a2.zip | |
Version 1.01.0.0
Diffstat (limited to 'src')
44 files changed, 4575 insertions, 2503 deletions
@@ -3,8 +3,8 @@ #include "atom.h" #include "common.h" -#include "utils.h" #include "log.h" +#include "utils.h" static inline void *atom_getter(void *ud, const char *atom_name, int *err) { xcb_connection_t *c = ud; diff --git a/src/backend/backend.c b/src/backend/backend.c index b0e562a..9d4d10c 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -16,6 +16,7 @@ extern struct backend_operations xrender_ops, dummy_ops; #ifdef CONFIG_OPENGL extern struct backend_operations glx_ops; +extern struct backend_operations egl_ops; #endif struct backend_operations *backend_list[NUM_BKEND] = { @@ -23,6 +24,7 @@ struct backend_operations *backend_list[NUM_BKEND] = { [BKEND_DUMMY] = &dummy_ops, #ifdef CONFIG_OPENGL [BKEND_GLX] = &glx_ops, + [BKEND_EGL] = &egl_ops, #endif }; @@ -44,7 +46,7 @@ region_t get_damage(session_t *ps, bool all_damage) { } else { for (int i = 0; i < buffer_age; i++) { auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage; - log_trace("damage index: %d, damage ring offset: %ld", i, curr); + log_trace("damage index: %d, damage ring offset: %td", i, curr); dump_region(&ps->damage_ring[curr]); pixman_region32_union(®ion, ®ion, &ps->damage_ring[curr]); } @@ -53,6 +55,31 @@ region_t get_damage(session_t *ps, bool all_damage) { return region; } +void handle_device_reset(session_t *ps) { + log_error("Device reset detected"); + // Wait for reset to complete + // Although ideally the backend should return DEVICE_STATUS_NORMAL after a reset + // is completed, it's not always possible. + // + // According to ARB_robustness (emphasis mine): + // + // "If a reset status other than NO_ERROR is returned and subsequent + // calls return NO_ERROR, the context reset was encountered and + // completed. If a reset status is repeatedly returned, the context **may** + // be in the process of resetting." + // + // Which means it may also not be in the process of resetting. For example on + // AMDGPU devices, Mesa OpenGL always return CONTEXT_RESET after a reset has + // started, completed or not. + // + // So here we blindly wait 5 seconds and hope ourselves best of the luck. + sleep(5); + + // Reset picom + log_info("Resetting picom after device reset"); + ev_break(ps->loop, EVBREAK_ALL); +} + static void process_window_for_painting(session_t *ps, struct managed_win* w, void* win_image, double additional_alpha, region_t* reg_bound, region_t* reg_visible, @@ -66,6 +93,7 @@ static void process_window_for_painting(session_t *ps, struct managed_win* w, vo // reg_visible as a hint. Since window image data outside of the // damage region won't be painted onto target region_t reg_visible_local; + coord_t window_coord = {.x = w->g.x, .y = w->g.y}; { // The bounding shape, in window local coordinates region_t reg_bound_local; @@ -75,16 +103,15 @@ static void process_window_for_painting(session_t *ps, struct managed_win* w, vo pixman_region32_init(®_visible_local); pixman_region32_intersect(®_visible_local, - reg_visible, reg_paint); + reg_visible, reg_paint); pixman_region32_translate(®_visible_local, -w->g.x, - -w->g.y); + -w->g.y); // Data outside of the bounding shape won't be visible, // but it is not necessary to limit the image operations // to the bounding shape yet. So pass that as the visible // region, not the clip region. pixman_region32_intersect( ®_visible_local, ®_visible_local, ®_bound_local); - pixman_region32_fini(®_bound_local); } auto new_img = ps->backend_data->ops->clone_image( @@ -98,15 +125,18 @@ static void process_window_for_painting(session_t *ps, struct managed_win* w, vo ®_visible_local, (double[]){w->frame_opacity}); pixman_region32_fini(®_frame); ps->backend_data->ops->compose(ps->backend_data, new_img, - w->g.x, w->g.y, - w->g.x + w->widthb, w->g.y + w->heightb, - reg_paint_in_bound, reg_visible); + window_coord, NULL, window_coord, + reg_paint_in_bound, reg_visible); ps->backend_data->ops->release_image(ps->backend_data, new_img); pixman_region32_fini(®_visible_local); } /// paint all windows void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { + if (ps->backend_data->ops->device_status && + ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) { + return handle_device_reset(ps); + } if (ps->o.xrender_sync_fence) { if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) { log_error("x_fence_sync failed, xrender-sync-fence will be " @@ -206,7 +236,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { if (ps->root_image) { ps->backend_data->ops->compose(ps->backend_data, ps->root_image, - 0, 0, ps->root_width, ps->root_height, + (coord_t){0}, NULL, (coord_t){0}, ®_paint, ®_visible); } else { ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1}, @@ -227,6 +257,12 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // The bounding shape of the window, in global/target coordinates // reminder: bounding shape contains the WM frame auto reg_bound = win_get_bounding_shape_global_by_val(w); + auto reg_bound_no_corner = + win_get_bounding_shape_global_without_corners_by_val(w); + + if (!w->mask_image && (w->bounding_shaped || w->corner_radius != 0)) { + win_bind_mask(ps->backend_data, w); + } // The clip region for the current window, in global/target coordinates // reg_paint_in_bound \in reg_paint @@ -250,7 +286,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { * (e.g. with shaders), we should consult the backend whether the window * is transparent or not. for now we will just rely on the force_win_blend * option */ + auto real_win_mode = w->mode; + coord_t window_coord = {.x = w->g.x, .y = w->g.y}; if (w->blur_background && (ps->o.force_win_blend || real_win_mode == WMODE_TRANS || @@ -294,7 +332,8 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // We need to blur the bounding shape of the window // (reg_paint_in_bound = reg_bound \cap reg_paint) ps->backend_data->ops->blur( - ps->backend_data, blur_opacity, ps->backend_blur_context, + ps->backend_data, blur_opacity, + ps->backend_blur_context, w->mask_image, window_coord, ®_paint_in_bound, ®_visible); } else { // Window itself is solid, we only need to blur the frame @@ -313,9 +352,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { pixman_region32_intersect(®_blur, ®_blur, ®_visible); } - ps->backend_data->ops->blur(ps->backend_data, blur_opacity, - ps->backend_blur_context, - ®_blur, ®_visible); + ps->backend_data->ops->blur( + ps->backend_data, blur_opacity, ps->backend_blur_context, + w->mask_image, window_coord, ®_blur, ®_visible); pixman_region32_fini(®_blur); } } @@ -327,9 +366,6 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // reg_shadow \in reg_paint auto reg_shadow = win_extents_by_val(w); pixman_region32_intersect(®_shadow, ®_shadow, ®_paint); - if (!ps->o.wintype_option[w->window_type].full_shadow) { - pixman_region32_subtract(®_shadow, ®_shadow, ®_bound); - } // Mask out the region we don't want shadow on if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) { @@ -365,12 +401,28 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ps->backend_data->ops->set_image_property( ps->backend_data, IMAGE_PROPERTY_OPACITY, w->shadow_image, &w->opacity); + coord_t shadow_coord = {.x = w->g.x + w->shadow_dx, + .y = w->g.y + w->shadow_dy}; + + auto inverted_mask = NULL; + if (!ps->o.wintype_option[w->window_type].full_shadow) { + pixman_region32_subtract(®_shadow, ®_shadow, + ®_bound_no_corner); + if (w->mask_image) { + inverted_mask = w->mask_image; + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_INVERTED, + inverted_mask, (bool[]){true}); + } + } ps->backend_data->ops->compose( - ps->backend_data, w->shadow_image, - w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, - w->g.x + w->shadow_dx + w->shadow_width, - w->g.y + w->shadow_dy + w->shadow_height, - ®_shadow, ®_visible); + ps->backend_data, w->shadow_image, shadow_coord, + inverted_mask, window_coord, ®_shadow, ®_visible); + if (inverted_mask) { + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_INVERTED, + inverted_mask, (bool[]){false}); + } pixman_region32_fini(®_shadow); } @@ -395,6 +447,25 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { &dim_opacity); ps->backend_data->ops->set_image_property( ps->backend_data, IMAGE_PROPERTY_OPACITY, w->win_image, &w->opacity); + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, w->win_image, + (double[]){w->corner_radius}); + if (w->corner_radius) { + int border_width = w->g.border_width; + if (border_width == 0) { + // Some WM has borders implemented as WM frames + border_width = min3(w->frame_extents.left, + w->frame_extents.right, + w->frame_extents.bottom); + } + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH, + w->win_image, &border_width); + } + + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_CUSTOM_SHADER, w->win_image, + w->fg_shader ? (void *)w->fg_shader->backend_shader : NULL); } if (w->opacity * MAX_ALPHA < 1) { @@ -411,13 +482,12 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { pixman_region32_subtract(®_shadow_clip, ®_shadow_clip, ®_bound); } - // Draw window on target bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0; + // Draw window on target if (w->frame_opacity == 1 && !is_animating) { ps->backend_data->ops->compose(ps->backend_data, w->win_image, - w->g.x, w->g.y, - w->g.x + w->widthb, w->g.y + w->heightb, - ®_paint_in_bound, ®_visible); + window_coord, NULL, window_coord, + ®_paint_in_bound, ®_visible); } else { if (is_animating && w->old_win_image) { assert(w->old_win_image); diff --git a/src/backend/backend.h b/src/backend/backend.h index ae107d3..191e814 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -16,6 +16,8 @@ typedef struct session session_t; struct managed_win; +struct backend_shadow_context; + struct ev_loop; struct backend_operations; @@ -30,12 +32,31 @@ typedef struct backend_base { // ... } backend_t; +typedef struct geometry { + int width; + int height; +} geometry_t; + +typedef struct coord { + int x, y; +} coord_t; + typedef void (*backend_ready_callback_t)(void *); +// This mimics OpenGL's ARB_robustness extension, which enables detection of GPU context +// resets. +// See: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_robustness.txt, section +// 2.6 "Graphics Reset Recovery". +enum device_status { + DEVICE_STATUS_NORMAL, + DEVICE_STATUS_RESETTING, +}; + // When image properties are actually applied to the image, they are applied in a // particular order: // -// Color inversion -> Dimming -> Opacity multiply -> Limit maximum brightness +// Corner radius -> Color inversion -> Dimming -> Opacity multiply -> Limit maximum +// brightness enum image_properties { // Whether the color of the image is inverted // 1 boolean, default: false @@ -54,6 +75,15 @@ enum image_properties { // brightness down to the max brightness value. // 1 double, default: 1 IMAGE_PROPERTY_MAX_BRIGHTNESS, + // Gives the image a rounded corner. + // 1 double, default: 0 + IMAGE_PROPERTY_CORNER_RADIUS, + // Border width + // 1 int, default: 0 + IMAGE_PROPERTY_BORDER_WIDTH, + // Custom shader for this window. + // 1 pointer to shader struct, default: NULL + IMAGE_PROPERTY_CUSTOM_SHADER, }; enum image_operations { @@ -61,6 +91,12 @@ enum image_operations { IMAGE_OP_APPLY_ALPHA, }; +enum shader_attributes { + // Whether the shader needs to be render regardless of whether the window is + // updated. + SHADER_ATTRIBUTE_ANIMATED = 1, +}; + struct gaussian_blur_args { int size; double deviation; @@ -133,26 +169,30 @@ struct backend_operations { void (*prepare)(backend_t *backend_data, const region_t *reg_damage); /** - * Paint the content of an image onto the rendering buffer + * Paint the content of an image onto the rendering buffer. * - * @param backend_data the backend data - * @param image_data the image to paint - * @param dst_x1, dst_y1 the top left corner of the image in the target - * @param dst_x2, dst_y2 the top right corner of the image in the target - * @param reg_paint the clip region, in target coordinates - * @param reg_visible the visible region, in target coordinates + * @param backend_data the backend data + * @param image_data the image to paint + * @param dst_x, dst_y the top left corner of the image in the target + * @param mask the mask image, the top left of the mask is aligned with + * the top left of the image + * @param reg_paint the clip region, in target coordinates + * @param reg_visible the visible region, in target coordinates */ - void (*compose)(backend_t *backend_data, void *image_data, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_paint, const region_t *reg_visible); + void (*compose)(backend_t *backend_data, void *image_data, coord_t image_dst, + void *mask, coord_t mask_dst, const region_t *reg_paint, + const region_t *reg_visible); /// Fill rectangle of the rendering buffer, mostly for debug purposes, optional. void (*fill)(backend_t *backend_data, struct color, const region_t *clip); /// Blur a given region of the rendering buffer. - bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, - const region_t *reg_blur, const region_t *reg_visible) - attr_nonnull(1, 3, 4, 5); + /// + /// The blur is limited by `mask`. `mask_dst` specifies the top left corner of the + /// mask is. + bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, void *mask, + coord_t mask_dst, const region_t *reg_blur, + const region_t *reg_visible) attr_nonnull(1, 3, 4, 6, 7); /// Update part of the back buffer with the rendering buffer, then present the /// back buffer onto the target window (if not back buffered, update part of the @@ -175,18 +215,67 @@ struct backend_operations { void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned); - /// Create a shadow image based on the parameters + /// Create a shadow context for rendering shadows with radius `radius`. + /// Default implementation: default_backend_create_shadow_context + struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data, + double radius); + /// Destroy a shadow context + /// Default implementation: default_backend_destroy_shadow_context + void (*destroy_shadow_context)(backend_t *backend_data, + struct backend_shadow_context *ctx); + + /// Create a shadow image based on the parameters. Resulting image should have a + /// size of `width + radisu * 2` x `height + radius * 2`. Radius is set when the + /// shadow context is created. /// Default implementation: default_backend_render_shadow + /// + /// Required. void *(*render_shadow)(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a); + struct backend_shadow_context *ctx, struct color color); + + /// Create a shadow by blurring a mask. `size` is the size of the blur. The + /// backend can use whichever blur method is the fastest. The shadow produced + /// shoule be consistent with `render_shadow`. + /// + /// Optional. + void *(*shadow_from_mask)(backend_t *backend_data, void *mask, + struct backend_shadow_context *ctx, struct color color); + + /// Create a mask image from region `reg`. This region can be used to create + /// shadow, or used as a mask for composing. When used as a mask, it should mask + /// out everything that is not inside the region used to create it. + /// + /// Image properties might be set on masks too, at least the INVERTED and + /// CORNER_RADIUS properties must be supported. Inversion should invert the inside + /// and outside of the mask. Corner radius should exclude the corners from the + /// mask. Corner radius should be applied before the inversion. + /// + /// Required. + void *(*make_mask)(backend_t *backend_data, geometry_t size, const region_t *reg); // ============ Resource management =========== /// Free resources associated with an image data structure void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2); + /// Create a shader object from a shader source. + /// + /// Optional + void *(*create_shader)(backend_t *backend_data, const char *source)attr_nonnull(1, 2); + + /// Free a shader object. + /// + /// Required if create_shader is present. + void (*destroy_shader)(backend_t *backend_data, void *shader) attr_nonnull(1, 2); + // =========== Query =========== + /// Get the attributes of a shader. + /// + /// Optional, Returns a bitmask of attributes, see `shader_attributes`. + uint64_t (*get_shader_attributes)(backend_t *backend_data, void *shader) + attr_nonnull(1, 2); + /// Return if image is not completely opaque. /// /// This function is needed because some backend might change the content of the @@ -243,20 +332,6 @@ struct backend_operations { bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data, const region_t *reg_op, const region_t *reg_visible, void *args); - /** - * Read the color of the pixel at given position of the given image. Image - * properties have no effect. - * - * @param backend_data backend_data - * @param image_data an image data structure previously returned by the - * backend. the image to read pixel from. - * @param x, y coordinate of the pixel to read - * @param[out] color the color of the pixel - * @return whether the operation is successful - */ - bool (*read_pixel)(backend_t *backend_data, void *image_data, int x, int y, - struct color *output); - /// Create another instance of the `image_data`. All `image_op` and /// `set_image_property` calls on the returned image should not affect the /// original image @@ -282,6 +357,8 @@ struct backend_operations { enum driver (*detect_driver)(backend_t *backend_data); void (*diagnostics)(backend_t *backend_data); + + enum device_status (*device_status)(backend_t *backend_data); }; extern struct backend_operations *backend_list[]; diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index c0377d3..d6fcce2 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -92,7 +92,7 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, } unsigned char *data = ximage->data; - long sstride = ximage->stride; + long long sstride = ximage->stride; // If the window body is smaller than the kernel, we do convolution directly if (width < r * 2 && height < r * 2) { @@ -100,7 +100,7 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, for (int x = 0; x < swidth; x++) { double sum = sum_kernel_normalized( kernel, d - x - 1, d - y - 1, width, height); - data[y * sstride + x] = (uint8_t)(sum * 255.0); + data[y * sstride + x] = (uint8_t)(sum * 255.0 * opacity); } } return ximage; @@ -118,14 +118,14 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, for (int x = 0; x < r * 2; x++) { double sum = sum_kernel_normalized(kernel, d - x - 1, d - y - 1, d, height) * - 255.0; + 255.0 * opacity; data[y * sstride + x] = (uint8_t)sum; data[y * sstride + swidth - x - 1] = (uint8_t)sum; } } for (int y = 0; y < sheight; y++) { - double sum = - sum_kernel_normalized(kernel, 0, d - y - 1, d, height) * 255.0; + double sum = sum_kernel_normalized(kernel, 0, d - y - 1, d, height) * + 255.0 * opacity; memset(&data[y * sstride + r * 2], (uint8_t)sum, (size_t)(width - 2 * r)); } @@ -137,14 +137,14 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, for (int x = 0; x < swidth; x++) { double sum = sum_kernel_normalized(kernel, d - x - 1, d - y - 1, width, d) * - 255.0; + 255.0 * opacity; data[y * sstride + x] = (uint8_t)sum; data[(sheight - y - 1) * sstride + x] = (uint8_t)sum; } } for (int x = 0; x < swidth; x++) { - double sum = - sum_kernel_normalized(kernel, d - x - 1, 0, width, d) * 255.0; + double sum = sum_kernel_normalized(kernel, d - x - 1, 0, width, d) * + 255.0 * opacity; for (int y = r * 2; y < height; y++) { data[y * sstride + x] = (uint8_t)sum; } @@ -291,16 +291,16 @@ shadow_picture_err: return false; } -void * -default_backend_render_shadow(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a) { - xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, - true, 1, r, g, b), +void *default_backend_render_shadow(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color) { + const conv *kernel = (void *)sctx; + xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, true, + 1, color.red, color.green, color.blue), shadow = XCB_NONE; xcb_render_picture_t pict = XCB_NONE; - if (!build_shadow(backend_data->c, backend_data->root, a, width, height, kernel, - shadow_pixel, &shadow, &pict)) { + if (!build_shadow(backend_data->c, backend_data->root, color.alpha, width, height, + kernel, shadow_pixel, &shadow, &pict)) { return NULL; } @@ -311,6 +311,34 @@ default_backend_render_shadow(backend_t *backend_data, int width, int height, return ret; } +/// Implement render_shadow with shadow_from_mask +void * +backend_render_shadow_from_mask(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color) { + region_t reg; + pixman_region32_init_rect(®, 0, 0, (unsigned int)width, (unsigned int)height); + void *mask = backend_data->ops->make_mask( + backend_data, (geometry_t){.width = width, .height = height}, ®); + pixman_region32_fini(®); + + void *shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color); + backend_data->ops->release_image(backend_data, mask); + return shadow; +} + +struct backend_shadow_context * +default_create_shadow_context(backend_t *backend_data attr_unused, double radius) { + auto ret = + (struct backend_shadow_context *)gaussian_kernel_autodetect_deviation(radius); + sum_kernel_preprocess((conv *)ret); + return ret; +} + +void default_destroy_shadow_context(backend_t *backend_data attr_unused, + struct backend_shadow_context *sctx) { + free_conv((conv *)sctx); +} + static struct conv **generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) { int r = args->size * 2 + 1; assert(r > 0); @@ -449,7 +477,10 @@ bool default_set_image_property(backend_t *base attr_unused, enum image_properti tex->ewidth = iargs[0]; tex->eheight = iargs[1]; break; + case IMAGE_PROPERTY_CORNER_RADIUS: tex->corner_radius = dargs[0]; break; case IMAGE_PROPERTY_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; break; + case IMAGE_PROPERTY_BORDER_WIDTH: tex->border_width = *(int *)arg; break; + case IMAGE_PROPERTY_CUSTOM_SHADER: break; } return true; @@ -468,6 +499,7 @@ struct backend_image *default_new_backend_image(int w, int h) { ret->eheight = h; ret->ewidth = w; ret->color_inverted = false; + ret->corner_radius = 0; return ret; } diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index 5c9c806..c72a168 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -37,9 +37,11 @@ struct backend_image { double opacity; double dim; double max_brightness; + double corner_radius; // Effective size of the image int ewidth, eheight; bool color_inverted; + int border_width; }; bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width, @@ -60,9 +62,18 @@ bool default_is_win_transparent(void *, win *, void *); /// caveat as `default_is_win_transparent` applies. bool default_is_frame_transparent(void *, win *, void *); +void *default_backend_render_shadow(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color); + +/// Implement `render_shadow` with `shadow_from_mask`. void * -default_backend_render_shadow(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a); +backend_render_shadow_from_mask(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color); +struct backend_shadow_context * +default_create_shadow_context(backend_t *backend_data, double radius); + +void default_destroy_shadow_context(backend_t *backend_data, + struct backend_shadow_context *sctx); void init_backend_base(struct backend_base *base, session_t *ps); @@ -70,8 +81,6 @@ struct conv **generate_blur_kernel(enum blur_method method, void *args, int *ker struct dual_kawase_params *generate_dual_kawase_params(void *args); void *default_clone_image(backend_t *base, const void *image_data, const region_t *reg); -void *default_resize_image(backend_t *base, const void *image_data, uint16_t desired_width, - uint16_t desired_height, const region_t *reg); bool default_is_image_transparent(backend_t *base attr_unused, void *image_data); bool default_set_image_property(backend_t *base attr_unused, enum image_properties op, void *image_data, void *arg); diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index a057b97..7e06fac 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -23,6 +23,8 @@ struct dummy_image { struct dummy_data { struct backend_base base; struct dummy_image *images; + + struct backend_image mask; }; struct backend_base *dummy_init(struct session *ps attr_unused) { @@ -47,6 +49,9 @@ void dummy_deinit(struct backend_base *data) { static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) { auto dummy = (struct dummy_data *)base; + if (img == (struct dummy_image *)&dummy->mask) { + return; + } struct dummy_image *tmp = NULL; HASH_FIND_INT(dummy->images, &img->pixmap, tmp); if (!tmp) { @@ -56,10 +61,13 @@ static void dummy_check_image(struct backend_base *base, const struct dummy_imag assert(*tmp->refcount > 0); } -void dummy_compose(struct backend_base *base, void *image, int dst_x1 attr_unused, - int dst_y1 attr_unused, int dst_x2 attr_unused, int dst_y2 attr_unused, - const region_t *reg_paint attr_unused, const region_t *reg_visible attr_unused) { +void dummy_compose(struct backend_base *base, void *image, coord_t dst attr_unused, + void *mask attr_unused, coord_t mask_dst attr_unused, + const region_t *reg_paint attr_unused, + const region_t *reg_visible attr_unused) { + auto dummy attr_unused = (struct dummy_data *)base; dummy_check_image(base, image); + assert(mask == NULL || mask == &dummy->mask); } void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused, @@ -67,7 +75,8 @@ void dummy_fill(struct backend_base *backend_data attr_unused, struct color c at } bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused, - void *blur_ctx attr_unused, const region_t *reg_blur attr_unused, + void *blur_ctx attr_unused, void *mask attr_unused, + coord_t mask_dst attr_unused, const region_t *reg_blur attr_unused, const region_t *reg_visible attr_unused) { return true; } @@ -94,6 +103,9 @@ void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap, void dummy_release_image(backend_t *base, void *image) { auto dummy = (struct dummy_data *)base; + if (image == &dummy->mask) { + return; + } auto img = (struct dummy_image *)image; assert(*img->refcount > 0); (*img->refcount)--; @@ -121,6 +133,11 @@ bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unu return true; } +void *dummy_make_mask(struct backend_base *base, geometry_t size attr_unused, + const region_t *reg attr_unused) { + return &(((struct dummy_data *)base)->mask); +} + bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused, void *image, void *arg attr_unused) { dummy_check_image(base, image); @@ -158,7 +175,10 @@ struct backend_operations dummy_ops = { .fill = dummy_fill, .blur = dummy_blur, .bind_pixmap = dummy_bind_pixmap, + .create_shadow_context = default_create_shadow_context, + .destroy_shadow_context = default_destroy_shadow_context, .render_shadow = default_backend_render_shadow, + .make_mask = dummy_make_mask, .release_image = dummy_release_image, .is_image_transparent = dummy_is_image_transparent, .buffer_age = dummy_buffer_age, diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c new file mode 100644 index 0000000..41022f5 --- /dev/null +++ b/src/backend/gl/blur.c @@ -0,0 +1,900 @@ +#include <locale.h> +#include <stdbool.h> + +#include <backend/backend.h> +#include <backend/backend_common.h> + +#include "gl_common.h" + +struct gl_blur_context { + enum blur_method method; + gl_blur_shader_t *blur_shader; + + /// Temporary textures used for blurring + GLuint *blur_textures; + int blur_texture_count; + /// Temporary fbos used for blurring + GLuint *blur_fbos; + int blur_fbo_count; + + /// Cached dimensions of each blur_texture. They are the same size as the target, + /// so they are always big enough without resizing. + /// Turns out calling glTexImage to resize is expensive, so we avoid that. + struct texture_size { + int width; + int height; + } *texture_sizes; + + /// Cached dimensions of the offscreen framebuffer. It's the same size as the + /// target but is expanded in either direction by resize_width / resize_height. + int fb_width, fb_height; + + /// How much do we need to resize the damaged region for blurring. + int resize_width, resize_height; + + int npasses; +}; + +/** + * Blur contents in a particular region. + */ +bool gl_kernel_blur(double opacity, struct gl_blur_context *bctx, const rect_t *extent, + struct backend_image *mask, coord_t mask_dst, const GLuint vao[2], + const int vao_nelems[2], GLuint source_texture, + geometry_t source_size, GLuint target_fbo, GLuint default_mask) { + int dst_y_fb_coord = bctx->fb_height - extent->y2; + + int curr = 0; + for (int i = 0; i < bctx->npasses; ++i) { + const gl_blur_shader_t *p = &bctx->blur_shader[i]; + assert(p->prog); + + assert(bctx->blur_textures[curr]); + + // The origin to use when sampling from the source texture + GLint texorig_x = extent->x1, texorig_y = dst_y_fb_coord; + GLint tex_width, tex_height; + GLuint src_texture; + + if (i == 0) { + src_texture = source_texture; + tex_width = source_size.width; + tex_height = source_size.height; + } else { + src_texture = bctx->blur_textures[curr]; + auto src_size = bctx->texture_sizes[curr]; + tex_width = src_size.width; + tex_height = src_size.height; + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, src_texture); + glUseProgram(p->prog); + glUniform2f(p->uniform_pixel_norm, 1.0F / (GLfloat)tex_width, + 1.0F / (GLfloat)tex_height); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, default_mask); + + glUniform1i(p->uniform_mask_tex, 1); + glUniform2f(p->uniform_mask_offset, 0.0F, 0.0F); + glUniform1i(p->uniform_mask_inverted, 0); + glUniform1f(p->uniform_mask_corner_radius, 0.0F); + + // The number of indices in the selected vertex array + GLsizei nelems; + + if (i < bctx->npasses - 1) { + assert(bctx->blur_fbos[0]); + assert(bctx->blur_textures[!curr]); + + // not last pass, draw into framebuffer, with resized regions + glBindVertexArray(vao[1]); + nelems = vao_nelems[1]; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[0]); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, bctx->blur_textures[!curr], 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { + return false; + } + + glUniform1f(p->uniform_opacity, 1.0F); + } else { + // last pass, draw directly into the back buffer, with origin + // regions. And apply mask if requested + if (mask) { + auto inner = (struct gl_texture *)mask->inner; + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, inner->texture); + glUniform1i(p->uniform_mask_inverted, mask->color_inverted); + glUniform1f(p->uniform_mask_corner_radius, + (float)mask->corner_radius); + glUniform2f( + p->uniform_mask_offset, (float)(mask_dst.x), + (float)(bctx->fb_height - mask_dst.y - inner->height)); + } + glBindVertexArray(vao[0]); + nelems = vao_nelems[0]; + glBindFramebuffer(GL_FRAMEBUFFER, target_fbo); + + glUniform1f(p->uniform_opacity, (float)opacity); + } + + glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); + glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); + + // XXX use multiple draw calls is probably going to be slow than + // just simply blur the whole area. + + curr = !curr; + } + + return true; +} + +bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rect_t *extent, + struct backend_image *mask, coord_t mask_dst, const GLuint vao[2], + const int vao_nelems[2], GLuint source_texture, + geometry_t source_size, GLuint target_fbo, GLuint default_mask) { + int dst_y_fb_coord = bctx->fb_height - extent->y2; + + int iterations = bctx->blur_texture_count; + int scale_factor = 1; + + // Kawase downsample pass + const gl_blur_shader_t *down_pass = &bctx->blur_shader[0]; + assert(down_pass->prog); + glUseProgram(down_pass->prog); + + glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); + + for (int i = 0; i < iterations; ++i) { + // Scale output width / height by half in each iteration + scale_factor <<= 1; + + GLuint src_texture; + int tex_width, tex_height; + + if (i == 0) { + // first pass: copy from back buffer + src_texture = source_texture; + tex_width = source_size.width; + tex_height = source_size.height; + } else { + // copy from previous pass + src_texture = bctx->blur_textures[i - 1]; + auto src_size = bctx->texture_sizes[i - 1]; + tex_width = src_size.width; + tex_height = src_size.height; + } + + assert(src_texture); + assert(bctx->blur_fbos[i]); + + glBindTexture(GL_TEXTURE_2D, src_texture); + glBindVertexArray(vao[1]); + auto nelems = vao_nelems[1]; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + + glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor); + + glUniform2f(down_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width, + 1.0F / (GLfloat)tex_height); + + glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); + } + + // Kawase upsample pass + const gl_blur_shader_t *up_pass = &bctx->blur_shader[1]; + assert(up_pass->prog); + glUseProgram(up_pass->prog); + + glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); + + for (int i = iterations - 1; i >= 0; --i) { + // Scale output width / height back by two in each iteration + scale_factor >>= 1; + + const GLuint src_texture = bctx->blur_textures[i]; + assert(src_texture); + + // Calculate normalized half-width/-height of a src pixel + auto src_size = bctx->texture_sizes[i]; + int tex_width = src_size.width; + int tex_height = src_size.height; + + // The number of indices in the selected vertex array + GLsizei nelems; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, src_texture); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, default_mask); + + glUniform1i(up_pass->uniform_mask_tex, 1); + glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F); + glUniform1i(up_pass->uniform_mask_inverted, 0); + glUniform1f(up_pass->uniform_mask_corner_radius, 0.0F); + if (i > 0) { + assert(bctx->blur_fbos[i - 1]); + + // not last pass, draw into next framebuffer + glBindVertexArray(vao[1]); + nelems = vao_nelems[1]; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + + glUniform1f(up_pass->uniform_opacity, (GLfloat)1); + } else { + // last pass, draw directly into the back buffer + if (mask) { + auto inner = (struct gl_texture *)mask->inner; + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, inner->texture); + glUniform1i(up_pass->uniform_mask_inverted, + mask->color_inverted); + glUniform1f(up_pass->uniform_mask_corner_radius, + (float)mask->corner_radius); + glUniform2f( + up_pass->uniform_mask_offset, (float)(mask_dst.x), + (float)(bctx->fb_height - mask_dst.y - inner->height)); + } + glBindVertexArray(vao[0]); + nelems = vao_nelems[0]; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target_fbo); + + glUniform1f(up_pass->uniform_opacity, (GLfloat)opacity); + } + + glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor); + glUniform2f(up_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width, + 1.0F / (GLfloat)tex_height); + + glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); + } + + return true; +} + +bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, + coord_t mask_dst, const region_t *reg_blur, + const region_t *reg_visible attr_unused, GLuint source_texture, + geometry_t source_size, GLuint target_fbo, GLuint default_mask) { + bool ret = false; + + if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) { + // Resize the temporary textures used for blur in case the root + // size changed + bctx->fb_width = source_size.width; + bctx->fb_height = source_size.height; + + for (int i = 0; i < bctx->blur_texture_count; ++i) { + auto tex_size = bctx->texture_sizes + i; + if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { + // Use smaller textures for each iteration (quarter of the + // previous texture) + tex_size->width = 1 + ((bctx->fb_width - 1) >> (i + 1)); + tex_size->height = 1 + ((bctx->fb_height - 1) >> (i + 1)); + } else { + tex_size->width = bctx->fb_width; + tex_size->height = bctx->fb_height; + } + + glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width, + tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + + if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { + // Attach texture to FBO target + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + bctx->blur_textures[i], 0); + if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return false; + } + } + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } + + // Remainder: regions are in Xorg coordinates + auto reg_blur_resized = + resize_region(reg_blur, bctx->resize_width, bctx->resize_height); + const rect_t *extent = pixman_region32_extents((region_t *)reg_blur), + *extent_resized = pixman_region32_extents(®_blur_resized); + int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1; + if (width == 0 || height == 0) { + return true; + } + + int nrects, nrects_resized; + const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects), + *rects_resized = + pixman_region32_rectangles(®_blur_resized, &nrects_resized); + if (!nrects || !nrects_resized) { + return true; + } + + auto coord = ccalloc(nrects * 16, GLint); + auto indices = ccalloc(nrects * 6, GLuint); + auto extent_height = extent_resized->y2 - extent_resized->y1; + x_rect_to_coords( + nrects, rects, (coord_t){.x = extent_resized->x1, .y = extent_resized->y1}, + extent_height, bctx->fb_height, source_size.height, false, coord, indices); + + auto coord_resized = ccalloc(nrects_resized * 16, GLint); + auto indices_resized = ccalloc(nrects_resized * 6, GLuint); + x_rect_to_coords(nrects_resized, rects_resized, + (coord_t){.x = extent_resized->x1, .y = extent_resized->y1}, + extent_height, bctx->fb_height, bctx->fb_height, false, + coord_resized, indices_resized); + pixman_region32_fini(®_blur_resized); + + GLuint vao[2]; + glGenVertexArrays(2, vao); + GLuint bo[4]; + glGenBuffers(4, bo); + + glBindVertexArray(vao[0]); + glBindBuffer(GL_ARRAY_BUFFER, bo[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, + indices, GL_STATIC_DRAW); + glEnableVertexAttribArray(vert_coord_loc); + glEnableVertexAttribArray(vert_in_texcoord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); + glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, + sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); + + glBindVertexArray(vao[1]); + glBindBuffer(GL_ARRAY_BUFFER, bo[2]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16, + coord_resized, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized, + GL_STATIC_DRAW); + glEnableVertexAttribArray(vert_coord_loc); + glEnableVertexAttribArray(vert_in_texcoord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); + glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, + sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); + + int vao_nelems[2] = {nrects * 6, nrects_resized * 6}; + + if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { + ret = gl_dual_kawase_blur(opacity, bctx, extent_resized, mask, mask_dst, + vao, vao_nelems, source_texture, source_size, + target_fbo, default_mask); + } else { + ret = gl_kernel_blur(opacity, bctx, extent_resized, mask, mask_dst, vao, + vao_nelems, source_texture, source_size, target_fbo, + default_mask); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(4, bo); + glBindVertexArray(0); + glDeleteVertexArrays(2, vao); + glUseProgram(0); + + free(indices); + free(coord); + free(indices_resized); + free(coord_resized); + + gl_check_err(); + return ret; +} + +bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, + const region_t *reg_blur, const region_t *reg_visible attr_unused) { + auto gd = (struct gl_data *)base; + auto bctx = (struct gl_blur_context *)ctx; + return gl_blur_impl(opacity, bctx, mask, mask_dst, reg_blur, reg_visible, + gd->back_texture, + (geometry_t){.width = gd->width, .height = gd->height}, + gd->back_fbo, gd->default_mask_texture); +} + +static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { + if (shader->prog) { + glDeleteProgram(shader->prog); + } + + shader->prog = 0; +} + +void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) { + auto bctx = (struct gl_blur_context *)ctx; + // Free GLSL shaders/programs + for (int i = 0; i < bctx->npasses; ++i) { + gl_free_blur_shader(&bctx->blur_shader[i]); + } + free(bctx->blur_shader); + + if (bctx->blur_texture_count && bctx->blur_textures) { + glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures); + free(bctx->blur_textures); + } + if (bctx->blur_texture_count && bctx->texture_sizes) { + free(bctx->texture_sizes); + } + if (bctx->blur_fbo_count && bctx->blur_fbos) { + glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos); + free(bctx->blur_fbos); + } + + bctx->blur_texture_count = 0; + bctx->blur_fbo_count = 0; + + free(bctx); + + gl_check_err(); +} + +/** + * Initialize GL blur filters. + */ +bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, + enum blur_method method, void *args) { + bool success = false; + auto ctx = (struct gl_blur_context *)blur_context; + + struct conv **kernels; + + int nkernels; + ctx->method = BLUR_METHOD_KERNEL; + if (method == BLUR_METHOD_KERNEL) { + nkernels = ((struct kernel_blur_args *)args)->kernel_count; + kernels = ((struct kernel_blur_args *)args)->kernels; + } else { + kernels = generate_blur_kernel(method, args, &nkernels); + } + + if (!nkernels) { + ctx->method = BLUR_METHOD_NONE; + return true; + } + + // Specify required textures and FBOs + ctx->blur_texture_count = 2; + ctx->blur_fbo_count = 1; + + ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t); + + char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); + // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane + // Thanks to hiciu for reporting. + setlocale(LC_NUMERIC, "C"); + + // clang-format off + static const char *FRAG_SHADER_BLUR = GLSL(330, + %s\n // other extension pragmas + uniform sampler2D tex_src; + uniform vec2 pixel_norm; + uniform float opacity; + in vec2 texcoord; + out vec4 out_color; + float mask_factor(); + void main() { + vec2 uv = texcoord * pixel_norm; + vec4 sum = vec4(0.0, 0.0, 0.0, 0.0); + %s //body of the convolution + out_color = sum / float(%.7g) * opacity * mask_factor(); + } + ); + static const char *FRAG_SHADER_BLUR_ADD = QUOTE( + sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g)); + ); + // clang-format on + + const char *shader_add = FRAG_SHADER_BLUR_ADD; + char *extension = strdup(""); + + for (int i = 0; i < nkernels; i++) { + auto kern = kernels[i]; + // Build shader + int width = kern->w, height = kern->h; + int nele = width * height; + // '%.7g' is at most 14 characters, inserted 3 times + size_t body_len = (strlen(shader_add) + 42) * (uint)nele; + char *shader_body = ccalloc(body_len, char); + char *pc = shader_body; + + // Make use of the linear interpolation hardware by sampling 2 pixels with + // one texture access by sampling between both pixels based on their + // relative weight. Easiest done in a single dimension as 2D bilinear + // filtering would raise additional constraints on the kernels. Therefore + // only use interpolation along the larger dimension. + double sum = 0.0; + if (width > height) { + // use interpolation in x dimension (width) + for (int j = 0; j < height; ++j) { + for (int k = 0; k < width; k += 2) { + double val1, val2; + val1 = kern->data[j * width + k]; + val2 = (k + 1 < width) + ? kern->data[j * width + k + 1] + : 0; + + double combined_weight = val1 + val2; + if (combined_weight == 0) { + continue; + } + sum += combined_weight; + + double offset_x = + k + (val2 / combined_weight) - (width / 2); + double offset_y = j - (height / 2); + pc += snprintf( + pc, body_len - (ulong)(pc - shader_body), + shader_add, combined_weight, offset_x, offset_y); + assert(pc < shader_body + body_len); + } + } + } else { + // use interpolation in y dimension (height) + for (int j = 0; j < height; j += 2) { + for (int k = 0; k < width; ++k) { + double val1, val2; + val1 = kern->data[j * width + k]; + val2 = (j + 1 < height) + ? kern->data[(j + 1) * width + k] + : 0; + + double combined_weight = val1 + val2; + if (combined_weight == 0) { + continue; + } + sum += combined_weight; + + double offset_x = k - (width / 2); + double offset_y = + j + (val2 / combined_weight) - (height / 2); + pc += snprintf( + pc, body_len - (ulong)(pc - shader_body), + shader_add, combined_weight, offset_x, offset_y); + assert(pc < shader_body + body_len); + } + } + } + + auto pass = ctx->blur_shader + i; + size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) + + strlen(shader_body) + 10 /* sum */ + + 1 /* null terminator */; + char *shader_str = ccalloc(shader_len, char); + auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR, + extension, shader_body, sum); + CHECK(real_shader_len >= 0); + CHECK((size_t)real_shader_len < shader_len); + free(shader_body); + + // Build program + pass->prog = gl_create_program_from_strv( + (const char *[]){vertex_shader, NULL}, + (const char *[]){shader_str, masking_glsl, NULL}); + free(shader_str); + if (!pass->prog) { + log_error("Failed to create GLSL program."); + success = false; + goto out; + } + glBindFragDataLocation(pass->prog, 0, "out_color"); + + // Get uniform addresses + bind_uniform(pass, pixel_norm); + bind_uniform(pass, opacity); + + bind_uniform(pass, mask_tex); + bind_uniform(pass, mask_offset); + bind_uniform(pass, mask_inverted); + bind_uniform(pass, mask_corner_radius); + log_info("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex, + pass->uniform_mask_offset, pass->uniform_mask_inverted, + pass->uniform_mask_corner_radius, pass->uniform_opacity); + pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); + + // Setup projection matrix + glUseProgram(pass->prog); + int pml = glGetUniformLocationChecked(pass->prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection); + glUseProgram(0); + + ctx->resize_width += kern->w / 2; + ctx->resize_height += kern->h / 2; + } + + if (nkernels == 1) { + // Generate an extra null pass so we don't need special code path for + // the single pass case + auto pass = &ctx->blur_shader[1]; + pass->prog = gl_create_program_from_strv( + (const char *[]){vertex_shader, NULL}, + (const char *[]){copy_with_mask_frag, masking_glsl, NULL}); + pass->uniform_pixel_norm = -1; + pass->uniform_opacity = -1; + pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); + bind_uniform(pass, mask_tex); + bind_uniform(pass, mask_offset); + bind_uniform(pass, mask_inverted); + bind_uniform(pass, mask_corner_radius); + + // Setup projection matrix + glUseProgram(pass->prog); + int pml = glGetUniformLocationChecked(pass->prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection); + glUseProgram(0); + + ctx->npasses = 2; + } else { + ctx->npasses = nkernels; + } + + success = true; +out: + if (method != BLUR_METHOD_KERNEL) { + // We generated the blur kernels, so we need to free them + for (int i = 0; i < nkernels; i++) { + free(kernels[i]); + } + free(kernels); + } + + free(extension); + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + + return success; +} + +bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection, + enum blur_method method, void *args) { + bool success = false; + auto ctx = (struct gl_blur_context *)blur_context; + + ctx->method = method; + + auto blur_params = generate_dual_kawase_params(args); + + // Specify required textures and FBOs + ctx->blur_texture_count = blur_params->iterations; + ctx->blur_fbo_count = blur_params->iterations; + + ctx->resize_width += blur_params->expand; + ctx->resize_height += blur_params->expand; + + ctx->npasses = 2; + ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t); + + char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); + // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane + // Thanks to hiciu for reporting. + setlocale(LC_NUMERIC, "C"); + + // Dual-kawase downsample shader / program + auto down_pass = ctx->blur_shader; + { + // clang-format off + static const char *FRAG_SHADER_DOWN = GLSL(330, + uniform sampler2D tex_src; + uniform float scale = 1.0; + uniform vec2 pixel_norm; + in vec2 texcoord; + out vec4 out_color; + void main() { + vec2 offset = %.7g * pixel_norm; + vec2 uv = texcoord * pixel_norm * (2.0 / scale); + vec4 sum = texture2D(tex_src, uv) * 4.0; + sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset); + sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset); + sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset); + sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset); + out_color = sum / 8.0; + } + ); + // clang-format on + + // Build shader + size_t shader_len = + strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */; + char *shader_str = ccalloc(shader_len, char); + auto real_shader_len = + snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset); + CHECK(real_shader_len >= 0); + CHECK((size_t)real_shader_len < shader_len); + + // Build program + down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); + free(shader_str); + if (!down_pass->prog) { + log_error("Failed to create GLSL program."); + success = false; + goto out; + } + glBindFragDataLocation(down_pass->prog, 0, "out_color"); + + // Get uniform addresses + bind_uniform(down_pass, pixel_norm); + down_pass->texorig_loc = + glGetUniformLocationChecked(down_pass->prog, "texorig"); + down_pass->scale_loc = + glGetUniformLocationChecked(down_pass->prog, "scale"); + + // Setup projection matrix + glUseProgram(down_pass->prog); + int pml = glGetUniformLocationChecked(down_pass->prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection); + glUseProgram(0); + } + + // Dual-kawase upsample shader / program + auto up_pass = ctx->blur_shader + 1; + { + // clang-format off + static const char *FRAG_SHADER_UP = GLSL(330, + uniform sampler2D tex_src; + uniform float scale = 1.0; + uniform vec2 pixel_norm; + uniform float opacity; + in vec2 texcoord; + out vec4 out_color; + float mask_factor(); + void main() { + vec2 offset = %.7g * pixel_norm; + vec2 uv = texcoord * pixel_norm / (2 * scale); + vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset); + sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0; + sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset); + sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0; + sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset); + sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0; + sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset); + sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0; + out_color = sum / 12.0 * opacity * mask_factor(); + } + ); + // clang-format on + + // Build shader + size_t shader_len = + strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */; + char *shader_str = ccalloc(shader_len, char); + auto real_shader_len = + snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset); + CHECK(real_shader_len >= 0); + CHECK((size_t)real_shader_len < shader_len); + + // Build program + up_pass->prog = gl_create_program_from_strv( + (const char *[]){vertex_shader, NULL}, + (const char *[]){shader_str, masking_glsl, NULL}); + free(shader_str); + if (!up_pass->prog) { + log_error("Failed to create GLSL program."); + success = false; + goto out; + } + glBindFragDataLocation(up_pass->prog, 0, "out_color"); + + // Get uniform addresses + bind_uniform(up_pass, pixel_norm); + bind_uniform(up_pass, opacity); + + bind_uniform(up_pass, mask_tex); + bind_uniform(up_pass, mask_offset); + bind_uniform(up_pass, mask_inverted); + bind_uniform(up_pass, mask_corner_radius); + + up_pass->texorig_loc = + glGetUniformLocationChecked(up_pass->prog, "texorig"); + up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale"); + + // Setup projection matrix + glUseProgram(up_pass->prog); + int pml = glGetUniformLocationChecked(up_pass->prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection); + glUseProgram(0); + } + + success = true; +out: + free(blur_params); + + if (!success) { + ctx = NULL; + } + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + + return success; +} + +void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) { + bool success; + auto gd = (struct gl_data *)base; + + auto ctx = ccalloc(1, struct gl_blur_context); + + if (!method || method >= BLUR_METHOD_INVALID) { + ctx->method = BLUR_METHOD_NONE; + return ctx; + } + + // Set projection matrix to gl viewport dimensions so we can use screen + // coordinates for all vertices + // Note: OpenGL matrices are column major + GLint viewport_dimensions[2]; + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); + GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0}, + {0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + + if (method == BLUR_METHOD_DUAL_KAWASE) { + success = gl_create_dual_kawase_blur_context(ctx, projection_matrix[0], + method, args); + } else { + success = + gl_create_kernel_blur_context(ctx, projection_matrix[0], method, args); + } + if (!success || ctx->method == BLUR_METHOD_NONE) { + goto out; + } + + // Texture size will be defined by gl_blur + ctx->blur_textures = ccalloc(ctx->blur_texture_count, GLuint); + ctx->texture_sizes = ccalloc(ctx->blur_texture_count, struct texture_size); + glGenTextures(ctx->blur_texture_count, ctx->blur_textures); + + for (int i = 0; i < ctx->blur_texture_count; ++i) { + glBindTexture(GL_TEXTURE_2D, ctx->blur_textures[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + // Generate FBO and textures when needed + ctx->blur_fbos = ccalloc(ctx->blur_fbo_count, GLuint); + glGenFramebuffers(ctx->blur_fbo_count, ctx->blur_fbos); + + for (int i = 0; i < ctx->blur_fbo_count; ++i) { + if (!ctx->blur_fbos[i]) { + log_error("Failed to generate framebuffer objects for blur"); + success = false; + goto out; + } + } + +out: + if (!success) { + gl_destroy_blur_context(&gd->base, ctx); + ctx = NULL; + } + + gl_check_err(); + return ctx; +} + +void gl_get_blur_size(void *blur_context, int *width, int *height) { + auto ctx = (struct gl_blur_context *)blur_context; + *width = ctx->resize_width; + *height = ctx->resize_height; +} diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c new file mode 100644 index 0000000..e6d4d90 --- /dev/null +++ b/src/backend/gl/egl.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: MPL-2.0 +/* + * Copyright (c) 2022 Yuxuan Shui <[email protected]> + */ + +#include <X11/Xlib-xcb.h> +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <xcb/xcb.h> + +#include "backend/backend.h" +#include "backend/backend_common.h" +#include "backend/gl/egl.h" +#include "backend/gl/gl_common.h" +#include "common.h" +#include "compiler.h" +#include "config.h" +#include "log.h" +#include "picom.h" +#include "utils.h" +#include "x.h" + +struct egl_pixmap { + EGLImage image; + xcb_pixmap_t pixmap; + bool owned; +}; + +struct egl_data { + struct gl_data gl; + EGLDisplay display; + EGLSurface target_win; + EGLContext ctx; +}; + +static PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glEGLImageTargetTexStorage = NULL; +static PFNEGLCREATEIMAGEKHRPROC eglCreateImageProc = NULL; +static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageProc = NULL; +static PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayProc = NULL; +static PFNEGLCREATEPLATFORMWINDOWSURFACEPROC eglCreatePlatformWindowSurfaceProc = NULL; + +/** + * Free a glx_texture_t. + */ +static void egl_release_image(backend_t *base, struct gl_texture *tex) { + struct egl_data *gd = (void *)base; + struct egl_pixmap *p = tex->user_data; + // Release binding + if (p->image != EGL_NO_IMAGE) { + eglDestroyImageProc(gd->display, p->image); + p->image = EGL_NO_IMAGE; + } + + if (p->owned) { + xcb_free_pixmap(base->c, p->pixmap); + p->pixmap = XCB_NONE; + } + + free(p); + tex->user_data = NULL; +} + +/** + * Destroy GLX related resources. + */ +void egl_deinit(backend_t *base) { + struct egl_data *gd = (void *)base; + + gl_deinit(&gd->gl); + + // Destroy GLX context + if (gd->ctx != EGL_NO_CONTEXT) { + eglMakeCurrent(gd->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(gd->display, gd->ctx); + gd->ctx = EGL_NO_CONTEXT; + } + + if (gd->target_win != EGL_NO_SURFACE) { + eglDestroySurface(gd->display, gd->target_win); + gd->target_win = EGL_NO_SURFACE; + } + + if (gd->display != EGL_NO_DISPLAY) { + eglTerminate(gd->display); + gd->display = EGL_NO_DISPLAY; + } + + free(gd); +} + +static void *egl_decouple_user_data(backend_t *base attr_unused, void *ud attr_unused) { + auto ret = cmalloc(struct egl_pixmap); + ret->owned = false; + ret->image = EGL_NO_IMAGE; + ret->pixmap = 0; + return ret; +} + +static bool egl_set_swap_interval(int interval, EGLDisplay dpy) { + return eglSwapInterval(dpy, interval); +} + +/** + * Initialize OpenGL. + */ +static backend_t *egl_init(session_t *ps) { + bool success = false; + struct egl_data *gd = NULL; + +#define get_proc(name, type) \ + name##Proc = (type)eglGetProcAddress(#name); \ + if (!name##Proc) { \ + log_error("Failed to get EGL function " #name); \ + goto end; \ + } + get_proc(eglCreateImage, PFNEGLCREATEIMAGEKHRPROC); + get_proc(eglDestroyImage, PFNEGLDESTROYIMAGEKHRPROC); + get_proc(eglGetPlatformDisplay, PFNEGLGETPLATFORMDISPLAYPROC); + get_proc(eglCreatePlatformWindowSurface, PFNEGLCREATEPLATFORMWINDOWSURFACEPROC); +#undef get_proc + + // Check if we have the X11 platform + const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (strstr(exts, "EGL_EXT_platform_x11") == NULL) { + log_error("X11 platform not available."); + return NULL; + } + + gd = ccalloc(1, struct egl_data); + gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->dpy, + (EGLAttrib[]){ + EGL_PLATFORM_X11_SCREEN_EXT, + ps->scr, + EGL_NONE, + }); + if (gd->display == EGL_NO_DISPLAY) { + log_error("Failed to get EGL display."); + goto end; + } + + EGLint major, minor; + if (!eglInitialize(gd->display, &major, &minor)) { + log_error("Failed to initialize EGL."); + goto end; + } + + if (major < 1 || (major == 1 && minor < 5)) { + log_error("EGL version too old, need at least 1.5."); + goto end; + } + + // Check if EGL supports OpenGL + const char *apis = eglQueryString(gd->display, EGL_CLIENT_APIS); + if (strstr(apis, "OpenGL") == NULL) { + log_error("EGL does not support OpenGL."); + goto end; + } + + eglext_init(gd->display); + init_backend_base(&gd->gl.base, ps); + if (!eglext.has_EGL_KHR_image_pixmap) { + log_error("EGL_KHR_image_pixmap not available."); + goto end; + } + + int ncfgs = 0; + if (eglGetConfigs(gd->display, NULL, 0, &ncfgs) != EGL_TRUE) { + log_error("Failed to get EGL configs."); + goto end; + } + + auto visual_info = x_get_visual_info(ps->c, ps->vis); + EGLConfig *cfgs = ccalloc(ncfgs, EGLConfig); + // clang-format off + if (eglChooseConfig(gd->display, + (EGLint[]){ + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_RED_SIZE, visual_info.red_size, + EGL_GREEN_SIZE, visual_info.green_size, + EGL_BLUE_SIZE, visual_info.blue_size, + EGL_ALPHA_SIZE, visual_info.alpha_size, + EGL_STENCIL_SIZE, 1, + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_NONE, + }, cfgs, ncfgs, &ncfgs) != EGL_TRUE) { + log_error("Failed to choose EGL config for the root window."); + goto end; + } + // clang-format on + + EGLConfig target_cfg = cfgs[0]; + free(cfgs); + + gd->target_win = eglCreatePlatformWindowSurfaceProc( + gd->display, target_cfg, (xcb_window_t[]){session_get_target_window(ps)}, NULL); + if (gd->target_win == EGL_NO_SURFACE) { + log_error("Failed to create EGL surface."); + goto end; + } + + if (eglBindAPI(EGL_OPENGL_API) != EGL_TRUE) { + log_error("Failed to bind OpenGL API."); + goto end; + } + + gd->ctx = eglCreateContext(gd->display, target_cfg, NULL, NULL); + if (gd->ctx == EGL_NO_CONTEXT) { + log_error("Failed to get GLX context."); + goto end; + } + + if (!eglMakeCurrent(gd->display, gd->target_win, gd->target_win, gd->ctx)) { + log_error("Failed to attach GLX context."); + goto end; + } + + if (!gl_init(&gd->gl, ps)) { + log_error("Failed to setup OpenGL"); + goto end; + } + if (!gd->gl.has_egl_image_storage) { + log_error("GL_EXT_EGL_image_storage extension not available."); + goto end; + } + + glEGLImageTargetTexStorage = + (PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC)eglGetProcAddress("glEGLImageTargetTexS" + "torageEXT"); + if (glEGLImageTargetTexStorage == NULL) { + log_error("Failed to get glEGLImageTargetTexStorageEXT."); + goto end; + } + + gd->gl.decouple_texture_user_data = egl_decouple_user_data; + gd->gl.release_user_data = egl_release_image; + + if (ps->o.vsync) { + if (!egl_set_swap_interval(1, gd->display)) { + log_error("Failed to enable vsync. %#x", eglGetError()); + } + } else { + egl_set_swap_interval(0, gd->display); + } + + success = true; + +end: + if (!success) { + if (gd != NULL) { + egl_deinit(&gd->gl.base); + } + return NULL; + } + + return &gd->gl.base; +} + +static void * +egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { + struct egl_data *gd = (void *)base; + struct egl_pixmap *eglpixmap = NULL; + + auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL); + if (!r) { + log_error("Invalid pixmap %#010x", pixmap); + return NULL; + } + + log_trace("Binding pixmap %#010x", pixmap); + auto wd = ccalloc(1, struct backend_image); + wd->max_brightness = 1; + auto inner = ccalloc(1, struct gl_texture); + inner->width = wd->ewidth = r->width; + inner->height = wd->eheight = r->height; + wd->inner = (struct backend_image_inner_base *)inner; + free(r); + + log_debug("depth %d", fmt.visual_depth); + + inner->y_inverted = true; + + eglpixmap = cmalloc(struct egl_pixmap); + eglpixmap->pixmap = pixmap; + eglpixmap->image = eglCreateImageProc(gd->display, gd->ctx, EGL_NATIVE_PIXMAP_KHR, + (EGLClientBuffer)(uintptr_t)pixmap, NULL); + eglpixmap->owned = owned; + + if (eglpixmap->image == EGL_NO_IMAGE) { + log_error("Failed to create eglpixmap for pixmap %#010x", pixmap); + goto err; + } + + log_trace("EGLImage %p", eglpixmap->image); + + // Create texture + inner->user_data = eglpixmap; + inner->texture = gl_new_texture(GL_TEXTURE_2D); + inner->has_alpha = fmt.alpha_size != 0; + wd->opacity = 1; + wd->color_inverted = false; + wd->dim = 0; + wd->inner->refcount = 1; + glBindTexture(GL_TEXTURE_2D, inner->texture); + glEGLImageTargetTexStorage(GL_TEXTURE_2D, eglpixmap->image, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + gl_check_err(); + return wd; +err: + if (eglpixmap && eglpixmap->image) { + eglDestroyImageProc(gd->display, eglpixmap->image); + } + free(eglpixmap); + + if (owned) { + xcb_free_pixmap(base->c, pixmap); + } + free(wd); + return NULL; +} + +static void egl_present(backend_t *base, const region_t *region attr_unused) { + struct egl_data *gd = (void *)base; + gl_present(base, region); + eglSwapBuffers(gd->display, gd->target_win); + if (!gd->gl.is_nvidia) { + glFinish(); + } +} + +static int egl_buffer_age(backend_t *base) { + if (!eglext.has_EGL_EXT_buffer_age) { + return -1; + } + + struct egl_data *gd = (void *)base; + EGLint val; + eglQuerySurface(gd->display, (EGLSurface)gd->target_win, EGL_BUFFER_AGE_EXT, &val); + return (int)val ?: -1; +} + +static void egl_diagnostics(backend_t *base) { + struct egl_data *gd = (void *)base; + bool warn_software_rendering = false; + const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"}; + auto egl_vendor = eglQueryString(gd->display, EGL_VENDOR); + printf("* Driver vendors:\n"); + printf(" * EGL: %s\n", egl_vendor); + if (eglext.has_EGL_MESA_query_driver) { + printf(" * EGL driver: %s\n", eglGetDisplayDriverName(gd->display)); + } + printf(" * GL: %s\n", glGetString(GL_VENDOR)); + + auto gl_renderer = (const char *)glGetString(GL_RENDERER); + printf("* GL renderer: %s\n", gl_renderer); + if (strstr(egl_vendor, "Mesa")) { + for (size_t i = 0; i < ARR_SIZE(software_renderer_names); i++) { + if (strstr(gl_renderer, software_renderer_names[i]) != NULL) { + warn_software_rendering = true; + break; + } + } + } + + if (warn_software_rendering) { + printf("\n(You are using a software renderer. Unless you are doing this\n" + "intentionally, this means you don't have a graphics driver\n" + "properly installed. Performance will suffer. Please fix this\n" + "before reporting your issue.)\n"); + } +} + +struct backend_operations egl_ops = { + .init = egl_init, + .deinit = egl_deinit, + .bind_pixmap = egl_bind_pixmap, + .release_image = gl_release_image, + .compose = gl_compose, + .image_op = gl_image_op, + .set_image_property = gl_set_image_property, + .clone_image = default_clone_image, + .blur = gl_blur, + .is_image_transparent = default_is_image_transparent, + .present = egl_present, + .buffer_age = egl_buffer_age, + .create_shadow_context = gl_create_shadow_context, + .destroy_shadow_context = gl_destroy_shadow_context, + .render_shadow = backend_render_shadow_from_mask, + .shadow_from_mask = gl_shadow_from_mask, + .make_mask = gl_make_mask, + .fill = gl_fill, + .create_blur_context = gl_create_blur_context, + .destroy_blur_context = gl_destroy_blur_context, + .get_blur_size = gl_get_blur_size, + .diagnostics = egl_diagnostics, + .device_status = gl_device_status, + .create_shader = gl_create_window_shader, + .destroy_shader = gl_destroy_window_shader, + .get_shader_attributes = gl_get_shader_attributes, + .max_buffer_age = 5, // Why? +}; + +PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName; +/** + * Check if a GLX extension exists. + */ +static inline bool egl_has_extension(EGLDisplay dpy, const char *ext) { + const char *egl_exts = eglQueryString(dpy, EGL_EXTENSIONS); + if (!egl_exts) { + log_error("Failed get EGL extension list."); + return false; + } + + auto inlen = strlen(ext); + const char *curr = egl_exts; + bool match = false; + while (curr && !match) { + const char *end = strchr(curr, ' '); + if (!end) { + // Last extension string + match = strcmp(ext, curr) == 0; + } else if (curr + inlen == end) { + // Length match, do match string + match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0; + } + curr = end ? end + 1 : NULL; + } + + if (!match) { + log_info("Missing EGL extension %s.", ext); + } else { + log_info("Found EGL extension %s.", ext); + } + + return match; +} + +struct eglext_info eglext = {0}; + +void eglext_init(EGLDisplay dpy) { + if (eglext.initialized) { + return; + } + eglext.initialized = true; +#define check_ext(name) eglext.has_##name = egl_has_extension(dpy, #name) + check_ext(EGL_EXT_buffer_age); + check_ext(EGL_EXT_create_context_robustness); + check_ext(EGL_KHR_image_pixmap); +#ifdef EGL_MESA_query_driver + check_ext(EGL_MESA_query_driver); +#endif +#undef check_ext + + // Checking if the returned function pointer is NULL is not really necessary, + // or maybe not even useful, since eglGetProcAddress might always return + // something. We are doing it just for completeness' sake. + +#ifdef EGL_MESA_query_driver + eglGetDisplayDriverName = + (PFNEGLGETDISPLAYDRIVERNAMEPROC)eglGetProcAddress("eglGetDisplayDriverName"); + if (!eglGetDisplayDriverName) { + eglext.has_EGL_MESA_query_driver = false; + } +#endif +} diff --git a/src/backend/gl/egl.h b/src/backend/gl/egl.h new file mode 100644 index 0000000..171b173 --- /dev/null +++ b/src/backend/gl/egl.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui <[email protected]> +#pragma once +#include <stdbool.h> +// Older version of glx.h defines function prototypes for these extensions... +// Rename them to avoid conflicts +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GL/gl.h> +#include <GL/glext.h> +#include <xcb/render.h> +#include <xcb/xcb.h> + +#include "compiler.h" +#include "log.h" +#include "utils.h" +#include "x.h" + +struct eglext_info { + bool initialized; + bool has_EGL_MESA_query_driver; + bool has_EGL_EXT_buffer_age; + bool has_EGL_EXT_create_context_robustness; + bool has_EGL_KHR_image_pixmap; +}; + +extern struct eglext_info eglext; + +#ifdef EGL_MESA_query_driver +extern PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName; +#endif + +void eglext_init(EGLDisplay); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 8cc5a05..a7d2aab 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -2,10 +2,10 @@ // Copyright (c) Yuxuan Shui <[email protected]> #include <GL/gl.h> #include <GL/glext.h> -#include <locale.h> #include <stdbool.h> #include <stdio.h> #include <string.h> +#include <time.h> #include <xcb/render.h> // for xcb_render_fixed_t, XXX #include "backend/backend.h" @@ -22,51 +22,6 @@ #include "backend/backend_common.h" #include "backend/gl/gl_common.h" -#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__ -#define QUOTE(...) #__VA_ARGS__ - -static const GLuint vert_coord_loc = 0; -static const GLuint vert_in_texcoord_loc = 1; - -struct gl_blur_context { - enum blur_method method; - gl_blur_shader_t *blur_shader; - - /// Temporary textures used for blurring - GLuint *blur_textures; - int blur_texture_count; - /// Temporary fbos used for blurring - GLuint *blur_fbos; - int blur_fbo_count; - - /// Cached dimensions of each blur_texture. They are the same size as the target, - /// so they are always big enough without resizing. - /// Turns out calling glTexImage to resize is expensive, so we avoid that. - struct texture_size { - int width; - int height; - } * texture_sizes; - - /// Cached dimensions of the offscreen framebuffer. It's the same size as the - /// target but is expanded in either direction by resize_width / resize_height. - int fb_width, fb_height; - - /// How much do we need to resize the damaged region for blurring. - int resize_width, resize_height; - - int npasses; -}; - -static GLint glGetUniformLocationChecked(GLuint p, const char *name) { - auto ret = glGetUniformLocation(p, name); - if (ret < 0) { - log_info("Failed to get location of uniform '%s'. This is normal when " - "using custom shaders.", - name); - } - return ret; -} - GLuint gl_create_shader(GLenum shader_type, const char *shader_str) { log_trace("===\n%s\n===", shader_str); @@ -83,7 +38,7 @@ GLuint gl_create_shader(GLenum shader_type, const char *shader_str) { { GLint status = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (GL_FALSE == status) { + if (status == GL_FALSE) { GLint log_len = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); if (log_len) { @@ -103,6 +58,7 @@ end: glDeleteShader(shader); shader = 0; } + gl_check_err(); return shader; } @@ -115,15 +71,16 @@ GLuint gl_create_program(const GLuint *const shaders, int nshaders) { goto end; } - for (int i = 0; i < nshaders; ++i) + for (int i = 0; i < nshaders; ++i) { glAttachShader(program, shaders[i]); + } glLinkProgram(program); // Get program status { GLint status = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &status); - if (GL_FALSE == status) { + if (status == GL_FALSE) { GLint log_len = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); if (log_len) { @@ -138,59 +95,83 @@ GLuint gl_create_program(const GLuint *const shaders, int nshaders) { end: if (program) { - for (int i = 0; i < nshaders; ++i) + for (int i = 0; i < nshaders; ++i) { glDetachShader(program, shaders[i]); + } } if (program && !success) { glDeleteProgram(program); program = 0; } + gl_check_err(); return program; } /** - * @brief Create a program from vertex and fragment shader strings. + * @brief Create a program from NULL-terminated arrays of vertex and fragment shader + * strings. */ -GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) { - GLuint vert_shader = 0; - GLuint frag_shader = 0; - GLuint prog = 0; - - if (vert_shader_str) - vert_shader = gl_create_shader(GL_VERTEX_SHADER, vert_shader_str); - if (frag_shader_str) - frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, frag_shader_str); +GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders) { + int vert_count, frag_count; + for (vert_count = 0; vert_shaders && vert_shaders[vert_count]; ++vert_count) { + } + for (frag_count = 0; frag_shaders && frag_shaders[frag_count]; ++frag_count) { + } - { - GLuint shaders[2]; - int count = 0; - if (vert_shader) { - shaders[count++] = vert_shader; - } - if (frag_shader) { - shaders[count++] = frag_shader; + GLuint prog = 0; + auto shaders = (GLuint *)ccalloc(vert_count + frag_count, GLuint); + for (int i = 0; i < vert_count; ++i) { + shaders[i] = gl_create_shader(GL_VERTEX_SHADER, vert_shaders[i]); + if (shaders[i] == 0) { + goto out; } - if (count) { - prog = gl_create_program(shaders, count); + } + for (int i = 0; i < frag_count; ++i) { + shaders[vert_count + i] = + gl_create_shader(GL_FRAGMENT_SHADER, frag_shaders[i]); + if (shaders[vert_count + i] == 0) { + goto out; } } - if (vert_shader) - glDeleteShader(vert_shader); - if (frag_shader) - glDeleteShader(frag_shader); + prog = gl_create_program(shaders, vert_count + frag_count); + +out: + for (int i = 0; i < vert_count + frag_count; ++i) { + if (shaders[i] != 0) { + glDeleteShader(shaders[i]); + } + } + free(shaders); + gl_check_err(); return prog; } -static void gl_free_prog_main(gl_win_shader_t *pprogram) { - if (!pprogram) +/** + * @brief Create a program from vertex and fragment shader strings. + */ +GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) { + const char *vert_shaders[2] = {vert_shader_str, NULL}; + const char *frag_shaders[2] = {frag_shader_str, NULL}; + + return gl_create_program_from_strv(vert_shaders, frag_shaders); +} + +void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader) { + if (!shader) { return; + } + + auto pprogram = (gl_win_shader_t *)shader; if (pprogram->prog) { glDeleteProgram(pprogram->prog); pprogram->prog = 0; } + gl_check_err(); + + free(shader); } /* @@ -368,9 +349,15 @@ static GLuint gl_average_texture_color(backend_t *base, struct backend_image *im * @param reg_visible ignored */ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target, - GLint *coord, GLuint *indices, int nrects) { + struct backend_image *mask, coord_t mask_offset, GLint *coord, + GLuint *indices, int nrects) { + // FIXME(yshui) breaks when `mask` and `img` doesn't have the same y_inverted + // value. but we don't ever hit this problem because all of our + // images and masks are y_inverted. auto gd = (struct gl_data *)base; auto inner = (struct gl_texture *)img->inner; + auto mask_texture = + mask ? ((struct gl_texture *)mask->inner)->texture : gd->default_mask_texture; if (!img || !inner->texture) { log_error("Missing texture."); return; @@ -381,27 +368,64 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe brightness = gl_average_texture_color(base, img); } - assert(gd->win_shader.prog); - glUseProgram(gd->win_shader.prog); - if (gd->win_shader.unifm_opacity >= 0) { - glUniform1f(gd->win_shader.unifm_opacity, (float)img->opacity); + auto win_shader = inner->shader; + if (!win_shader) { + win_shader = gd->default_shader; } - if (gd->win_shader.unifm_invert_color >= 0) { - glUniform1i(gd->win_shader.unifm_invert_color, img->color_inverted); + + assert(win_shader); + assert(win_shader->prog); + glUseProgram(win_shader->prog); + if (win_shader->uniform_opacity >= 0) { + glUniform1f(win_shader->uniform_opacity, (float)img->opacity); + } + if (win_shader->uniform_invert_color >= 0) { + glUniform1i(win_shader->uniform_invert_color, img->color_inverted); + } + if (win_shader->uniform_tex >= 0) { + glUniform1i(win_shader->uniform_tex, 0); } - if (gd->win_shader.unifm_tex >= 0) { - glUniform1i(gd->win_shader.unifm_tex, 0); + if (win_shader->uniform_dim >= 0) { + glUniform1f(win_shader->uniform_dim, (float)img->dim); } - if (gd->win_shader.unifm_dim >= 0) { - glUniform1f(gd->win_shader.unifm_dim, (float)img->dim); + if (win_shader->uniform_brightness >= 0) { + glUniform1i(win_shader->uniform_brightness, 1); } - if (gd->win_shader.unifm_brightness >= 0) { - glUniform1i(gd->win_shader.unifm_brightness, 1); + if (win_shader->uniform_max_brightness >= 0) { + glUniform1f(win_shader->uniform_max_brightness, (float)img->max_brightness); } - if (gd->win_shader.unifm_max_brightness >= 0) { - glUniform1f(gd->win_shader.unifm_max_brightness, (float)img->max_brightness); + if (win_shader->uniform_corner_radius >= 0) { + glUniform1f(win_shader->uniform_corner_radius, (float)img->corner_radius); + } + if (win_shader->uniform_border_width >= 0) { + auto border_width = img->border_width; + if (border_width > img->corner_radius) { + border_width = 0; + } + glUniform1f(win_shader->uniform_border_width, (float)border_width); + } + if (win_shader->uniform_time >= 0) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + glUniform1f(win_shader->uniform_time, + (float)ts.tv_sec * 1000.0F + (float)ts.tv_nsec / 1.0e6F); + } + + glUniform1i(win_shader->uniform_mask_tex, 2); + glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x, + (float)mask_offset.y); + if (mask != NULL) { + glUniform1i(win_shader->uniform_mask_inverted, mask->color_inverted); + glUniform1f(win_shader->uniform_mask_corner_radius, + (GLfloat)mask->corner_radius); + } else { + glUniform1i(win_shader->uniform_mask_inverted, 0); + glUniform1f(win_shader->uniform_mask_corner_radius, 0); } + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, mask_texture); + // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", // x, y, width, height, dx, dy, ptex->width, ptex->height, z); @@ -437,6 +461,11 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe glDeleteVertexArrays(1, &vao); // Cleanup + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDrawBuffer(GL_BACK); @@ -454,19 +483,18 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe /// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates /// @param[in] nrects, rects rectangles -/// @param[in] dst_x, dst_y origin of the OpenGL texture, affect the calculated texture +/// @param[in] image_dst origin of the OpenGL texture, affect the calculated texture /// coordinates +/// @param[in] extend_height height of the drawing extent /// @param[in] texture_height height of the OpenGL texture /// @param[in] root_height height of the back buffer /// @param[in] y_inverted whether the texture is y inverted /// @param[out] coord, indices output -static void -x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int texture_height, - int root_height, bool y_inverted, GLint *coord, GLuint *indices) { - dst_y = root_height - dst_y; - if (y_inverted) { - dst_y -= texture_height; - } +void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, + int extent_height, int texture_height, int root_height, + bool y_inverted, GLint *coord, GLuint *indices) { + image_dst.y = root_height - image_dst.y; + image_dst.y -= extent_height; for (int i = 0; i < nrects; i++) { // Y-flip. Note after this, crect.y1 > crect.y2 @@ -476,7 +504,8 @@ x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int text // Calculate texture coordinates // (texture_x1, texture_y1), texture coord for the _bottom left_ corner - GLint texture_x1 = crect.x1 - dst_x, texture_y1 = crect.y2 - dst_y, + GLint texture_x1 = crect.x1 - image_dst.x, + texture_y1 = crect.y2 - image_dst.y, texture_x2 = texture_x1 + (crect.x2 - crect.x1), texture_y2 = texture_y1 + (crect.y1 - crect.y2); @@ -516,9 +545,9 @@ x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int text } // TODO(yshui) make use of reg_visible -void gl_compose(backend_t *base, void *image_data, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_tgt, const region_t *reg_visible attr_unused) { +void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask, + coord_t mask_dst, const region_t *reg_tgt, + const region_t *reg_visible attr_unused) { auto gd = (struct gl_data *)base; struct backend_image *img = image_data; auto inner = (struct gl_texture *)img->inner; @@ -541,373 +570,43 @@ void gl_compose(backend_t *base, void *image_data, auto coord = ccalloc(nrects * 16, GLint); auto indices = ccalloc(nrects * 6, GLuint); - x_rect_to_coords(nrects, rects, dst_x1, dst_y1, inner->height, gd->height, - inner->y_inverted, coord, indices); - - // Interpolate the texture coordinates into the specified range - for (unsigned int i = 2; i < 16; i+=4) { - coord[i+0] = lerp_range(0, dst_x2 - dst_x1, 0, inner->width, coord[i+0]); - coord[i+1] = lerp_range(0, dst_y2 - dst_y1, 0, inner->height, coord[i+1]); - } - - _gl_compose(base, img, gd->back_fbo, coord, indices, nrects); + coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y}; + x_rect_to_coords(nrects, rects, image_dst, inner->height, inner->height, + gd->height, inner->y_inverted, coord, indices); + _gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects); free(indices); free(coord); } /** - * Blur contents in a particular region. - */ -bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, - const GLuint vao[2], const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - int dst_y_fb_coord = bctx->fb_height - extent->y2; - - int curr = 0; - for (int i = 0; i < bctx->npasses; ++i) { - const gl_blur_shader_t *p = &bctx->blur_shader[i]; - assert(p->prog); - - assert(bctx->blur_textures[curr]); - - // The origin to use when sampling from the source texture - GLint texorig_x = extent->x1, texorig_y = dst_y_fb_coord; - GLint tex_width, tex_height; - GLuint src_texture; - - if (i == 0) { - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; - } else { - src_texture = bctx->blur_textures[curr]; - auto src_size = bctx->texture_sizes[curr]; - tex_width = src_size.width; - tex_height = src_size.height; - } - - glBindTexture(GL_TEXTURE_2D, src_texture); - glUseProgram(p->prog); - glUniform2f(p->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - - // The number of indices in the selected vertex array - GLsizei nelems; - - if (i < bctx->npasses - 1) { - assert(bctx->blur_fbos[0]); - assert(bctx->blur_textures[!curr]); - - // not last pass, draw into framebuffer, with resized regions - glBindVertexArray(vao[1]); - nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[0]); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, bctx->blur_textures[!curr], 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - return false; - } - - glUniform1f(p->unifm_opacity, 1.0); - } else { - // last pass, draw directly into the back buffer, with origin - // regions - glBindVertexArray(vao[0]); - nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - - glUniform1f(p->unifm_opacity, (float)opacity); - } - - glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - - // XXX use multiple draw calls is probably going to be slow than - // just simply blur the whole area. - - curr = !curr; - } - - return true; -} - -bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, - const GLuint vao[2], const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - int dst_y_fb_coord = bctx->fb_height - extent->y2; - - int iterations = bctx->blur_texture_count; - int scale_factor = 1; - - // Kawase downsample pass - const gl_blur_shader_t *down_pass = &bctx->blur_shader[0]; - assert(down_pass->prog); - glUseProgram(down_pass->prog); - - glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); - - for (int i = 0; i < iterations; ++i) { - // Scale output width / height by half in each iteration - scale_factor <<= 1; - - GLuint src_texture; - int tex_width, tex_height; - - if (i == 0) { - // first pass: copy from back buffer - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; - } else { - // copy from previous pass - src_texture = bctx->blur_textures[i - 1]; - auto src_size = bctx->texture_sizes[i - 1]; - tex_width = src_size.width; - tex_height = src_size.height; - } - - assert(src_texture); - assert(bctx->blur_fbos[i]); - - glBindTexture(GL_TEXTURE_2D, src_texture); - glBindVertexArray(vao[1]); - auto nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor); - - glUniform2f(down_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - } - - // Kawase upsample pass - const gl_blur_shader_t *up_pass = &bctx->blur_shader[1]; - assert(up_pass->prog); - glUseProgram(up_pass->prog); - - glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); - - for (int i = iterations - 1; i >= 0; --i) { - // Scale output width / height back by two in each iteration - scale_factor >>= 1; - - const GLuint src_texture = bctx->blur_textures[i]; - assert(src_texture); - - // Calculate normalized half-width/-height of a src pixel - auto src_size = bctx->texture_sizes[i]; - int tex_width = src_size.width; - int tex_height = src_size.height; - - // The number of indices in the selected vertex array - GLsizei nelems; - - glBindTexture(GL_TEXTURE_2D, src_texture); - if (i > 0) { - assert(bctx->blur_fbos[i - 1]); - - // not last pass, draw into next framebuffer - glBindVertexArray(vao[1]); - nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glUniform1f(up_pass->unifm_opacity, (GLfloat)1); - } else { - // last pass, draw directly into the back buffer - glBindVertexArray(vao[0]); - nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - - glUniform1f(up_pass->unifm_opacity, (GLfloat)opacity); - } - - glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor); - glUniform2f(up_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - } - - return true; -} - -bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blur, - const region_t *reg_visible attr_unused) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - bool ret = false; - - if (gd->width != bctx->fb_width || gd->height != bctx->fb_height) { - // Resize the temporary textures used for blur in case the root - // size changed - bctx->fb_width = gd->width; - bctx->fb_height = gd->height; - - for (int i = 0; i < bctx->blur_texture_count; ++i) { - auto tex_size = bctx->texture_sizes + i; - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - // Use smaller textures for each iteration (quarter of the - // previous texture) - tex_size->width = 1 + ((bctx->fb_width - 1) >> (i + 1)); - tex_size->height = 1 + ((bctx->fb_height - 1) >> (i + 1)); - } else { - tex_size->width = bctx->fb_width; - tex_size->height = bctx->fb_height; - } - - glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width, - tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); - - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - // Attach texture to FBO target - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - bctx->blur_textures[i], 0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - return false; - } - } - } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - } - - // Remainder: regions are in Xorg coordinates - auto reg_blur_resized = - resize_region(reg_blur, bctx->resize_width, bctx->resize_height); - const rect_t *extent = pixman_region32_extents((region_t *)reg_blur), - *extent_resized = pixman_region32_extents(®_blur_resized); - int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1; - if (width == 0 || height == 0) { - return true; - } - - int nrects, nrects_resized; - const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects), - *rects_resized = - pixman_region32_rectangles(®_blur_resized, &nrects_resized); - if (!nrects || !nrects_resized) { - return true; - } - - auto coord = ccalloc(nrects * 16, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - x_rect_to_coords(nrects, rects, extent_resized->x1, extent_resized->y2, - bctx->fb_height, gd->height, false, coord, indices); - - auto coord_resized = ccalloc(nrects_resized * 16, GLint); - auto indices_resized = ccalloc(nrects_resized * 6, GLuint); - x_rect_to_coords(nrects_resized, rects_resized, extent_resized->x1, - extent_resized->y2, bctx->fb_height, bctx->fb_height, false, - coord_resized, indices_resized); - pixman_region32_fini(®_blur_resized); - - GLuint vao[2]; - glGenVertexArrays(2, vao); - GLuint bo[4]; - glGenBuffers(4, bo); - - glBindVertexArray(vao[0]); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, - indices, GL_STATIC_DRAW); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - glBindVertexArray(vao[1]); - glBindBuffer(GL_ARRAY_BUFFER, bo[2]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16, - coord_resized, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized, - GL_STATIC_DRAW); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - int vao_nelems[2] = {nrects * 6, nrects_resized * 6}; - - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, vao, vao_nelems); - } else { - ret = gl_kernel_blur(base, opacity, ctx, extent_resized, vao, vao_nelems); - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(4, bo); - glBindVertexArray(0); - glDeleteVertexArrays(2, vao); - glUseProgram(0); - - free(indices); - free(coord); - free(indices_resized); - free(coord_resized); - - gl_check_err(); - return ret; -} - -// clang-format off -const char *vertex_shader = GLSL(330, - uniform mat4 projection; - uniform float scale = 1.0; - uniform vec2 texorig; - layout(location = 0) in vec2 coord; - layout(location = 1) in vec2 in_texcoord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(coord, 0, scale); - texcoord = in_texcoord + texorig; - } -); -// clang-format on - -/** * Load a GLSL main program from shader strings. */ -static int gl_win_shader_from_string(const char *vshader_str, const char *fshader_str, - gl_win_shader_t *ret) { +static bool gl_win_shader_from_stringv(const char **vshader_strv, + const char **fshader_strv, gl_win_shader_t *ret) { // Build program - ret->prog = gl_create_program_from_str(vshader_str, fshader_str); + ret->prog = gl_create_program_from_strv(vshader_strv, fshader_strv); if (!ret->prog) { log_error("Failed to create GLSL program."); - return -1; + gl_check_err(); + return false; } // Get uniform addresses - ret->unifm_opacity = glGetUniformLocationChecked(ret->prog, "opacity"); - ret->unifm_invert_color = glGetUniformLocationChecked(ret->prog, "invert_color"); - ret->unifm_tex = glGetUniformLocationChecked(ret->prog, "tex"); - ret->unifm_dim = glGetUniformLocationChecked(ret->prog, "dim"); - ret->unifm_brightness = glGetUniformLocationChecked(ret->prog, "brightness"); - ret->unifm_max_brightness = - glGetUniformLocationChecked(ret->prog, "max_brightness"); + bind_uniform(ret, opacity); + bind_uniform(ret, invert_color); + bind_uniform(ret, tex); + bind_uniform(ret, dim); + bind_uniform(ret, brightness); + bind_uniform(ret, max_brightness); + bind_uniform(ret, corner_radius); + bind_uniform(ret, border_width); + bind_uniform(ret, time); + + bind_uniform(ret, mask_tex); + bind_uniform(ret, mask_offset); + bind_uniform(ret, mask_inverted); + bind_uniform(ret, mask_corner_radius); gl_check_err(); @@ -934,51 +633,6 @@ void gl_resize(struct gl_data *gd, int width, int height) { gl_check_err(); } -// clang-format off -static const char dummy_frag[] = GLSL(330, - uniform sampler2D tex; - in vec2 texcoord; - void main() { - gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); - } -); - -static const char fill_frag[] = GLSL(330, - uniform vec4 color; - void main() { - gl_FragColor = color; - } -); - -static const char fill_vert[] = GLSL(330, - layout(location = 0) in vec2 in_coord; - uniform mat4 projection; - void main() { - gl_Position = projection * vec4(in_coord, 0, 1); - } -); - -static const char interpolating_frag[] = GLSL(330, - uniform sampler2D tex; - in vec2 texcoord; - void main() { - gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); - } -); - -static const char interpolating_vert[] = GLSL(330, - uniform mat4 projection; - uniform vec2 texsize; - layout(location = 0) in vec2 in_coord; - layout(location = 1) in vec2 in_texcoord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(in_coord, 0, 1); - texcoord = in_texcoord / texsize; - } -); -// clang-format on - /// Fill a given region in bound framebuffer. /// @param[in] y_inverted whether the y coordinates in `clip` should be inverted static void _gl_fill(backend_t *base, struct color c, const region_t *clip, GLuint target, @@ -1048,9 +702,45 @@ void gl_fill(backend_t *base, struct color c, const region_t *clip) { return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true); } +void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) { + auto tex = ccalloc(1, struct gl_texture); + auto img = default_new_backend_image(size.width, size.height); + tex->width = size.width; + tex->height = size.height; + tex->texture = gl_new_texture(GL_TEXTURE_2D); + tex->has_alpha = false; + tex->y_inverted = true; + img->inner = (struct backend_image_inner_base *)tex; + img->inner->refcount = 1; + + glBindTexture(GL_TEXTURE_2D, tex->texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size.width, size.height, 0, GL_RED, + GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + GLuint fbo; + glBlendFunc(GL_ONE, GL_ZERO); + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + tex->texture, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + _gl_fill(base, (struct color){1, 1, 1, 1}, reg, fbo, size.height, false); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + return img; +} + static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) { auto gd = (struct gl_data *)base; - gd->release_user_data(base, inner); + if (inner->user_data) { + gd->release_user_data(base, inner); + } assert(inner->user_data == NULL); glDeleteTextures(1, &inner->texture); @@ -1070,513 +760,44 @@ void gl_release_image(backend_t *base, void *image_data) { free(wd); } -static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { - if (shader->prog) { - glDeleteProgram(shader->prog); - } +void *gl_create_window_shader(backend_t *backend_data attr_unused, const char *source) { + auto win_shader = (gl_win_shader_t *)ccalloc(1, gl_win_shader_t); - shader->prog = 0; -} + const char *vert_shaders[2] = {vertex_shader, NULL}; + const char *frag_shaders[4] = {win_shader_glsl, masking_glsl, source, NULL}; -void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) { - auto bctx = (struct gl_blur_context *)ctx; - // Free GLSL shaders/programs - for (int i = 0; i < bctx->npasses; ++i) { - gl_free_blur_shader(&bctx->blur_shader[i]); + if (!gl_win_shader_from_stringv(vert_shaders, frag_shaders, win_shader)) { + free(win_shader); + return NULL; } - free(bctx->blur_shader); - if (bctx->blur_texture_count && bctx->blur_textures) { - glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures); - free(bctx->blur_textures); - } - if (bctx->blur_texture_count && bctx->texture_sizes) { - free(bctx->texture_sizes); - } - if (bctx->blur_fbo_count && bctx->blur_fbos) { - glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos); - free(bctx->blur_fbos); - } - - bctx->blur_texture_count = 0; - bctx->blur_fbo_count = 0; - - free(bctx); - - gl_check_err(); -} - -/** - * Initialize GL blur filters. - */ -bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, - enum blur_method method, void *args) { - bool success = false; - auto ctx = (struct gl_blur_context *)blur_context; - - struct conv **kernels; - - int nkernels; - ctx->method = BLUR_METHOD_KERNEL; - if (method == BLUR_METHOD_KERNEL) { - nkernels = ((struct kernel_blur_args *)args)->kernel_count; - kernels = ((struct kernel_blur_args *)args)->kernels; - } else { - kernels = generate_blur_kernel(method, args, &nkernels); - } - - if (!nkernels) { - ctx->method = BLUR_METHOD_NONE; - return true; - } - - // Specify required textures and FBOs - ctx->blur_texture_count = 2; - ctx->blur_fbo_count = 1; - - ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t); - - char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); - // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane - // Thanks to hiciu for reporting. - setlocale(LC_NUMERIC, "C"); - - // clang-format off - static const char *FRAG_SHADER_BLUR = GLSL(330, - %s\n // other extension pragmas - uniform sampler2D tex_src; - uniform vec2 pixel_norm; - uniform float opacity; - in vec2 texcoord; - out vec4 out_color; - void main() { - vec2 uv = texcoord * pixel_norm; - vec4 sum = vec4(0.0, 0.0, 0.0, 0.0); - %s //body of the convolution - out_color = sum / float(%.7g) * opacity; - } - ); - static const char *FRAG_SHADER_BLUR_ADD = QUOTE( - sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g)); - ); - // clang-format on - - const char *shader_add = FRAG_SHADER_BLUR_ADD; - char *extension = strdup(""); - - for (int i = 0; i < nkernels; i++) { - auto kern = kernels[i]; - // Build shader - int width = kern->w, height = kern->h; - int nele = width * height; - // '%.7g' is at most 14 characters, inserted 3 times - size_t body_len = (strlen(shader_add) + 42) * (uint)nele; - char *shader_body = ccalloc(body_len, char); - char *pc = shader_body; - - // Make use of the linear interpolation hardware by sampling 2 pixels with - // one texture access by sampling between both pixels based on their - // relative weight. Easiest done in a single dimension as 2D bilinear - // filtering would raise additional constraints on the kernels. Therefore - // only use interpolation along the larger dimension. - double sum = 0.0; - if (width > height) { - // use interpolation in x dimension (width) - for (int j = 0; j < height; ++j) { - for (int k = 0; k < width; k += 2) { - double val1, val2; - val1 = kern->data[j * width + k]; - val2 = (k + 1 < width) - ? kern->data[j * width + k + 1] - : 0; - - double combined_weight = val1 + val2; - if (combined_weight == 0) { - continue; - } - sum += combined_weight; - - double offset_x = - k + (val2 / combined_weight) - (width / 2); - double offset_y = j - (height / 2); - pc += snprintf( - pc, body_len - (ulong)(pc - shader_body), - shader_add, combined_weight, offset_x, offset_y); - assert(pc < shader_body + body_len); - } - } - } else { - // use interpolation in y dimension (height) - for (int j = 0; j < height; j += 2) { - for (int k = 0; k < width; ++k) { - double val1, val2; - val1 = kern->data[j * width + k]; - val2 = (j + 1 < height) - ? kern->data[(j + 1) * width + k] - : 0; - - double combined_weight = val1 + val2; - if (combined_weight == 0) { - continue; - } - sum += combined_weight; - - double offset_x = k - (width / 2); - double offset_y = - j + (val2 / combined_weight) - (height / 2); - pc += snprintf( - pc, body_len - (ulong)(pc - shader_body), - shader_add, combined_weight, offset_x, offset_y); - assert(pc < shader_body + body_len); - } - } - } - - auto pass = ctx->blur_shader + i; - size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) + - strlen(shader_body) + 10 /* sum */ + - 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR, - extension, shader_body, sum); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - free(shader_body); - - // Build program - pass->prog = gl_create_program_from_str(vertex_shader, shader_str); - free(shader_str); - if (!pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(pass->prog, 0, "out_color"); - - // Get uniform addresses - pass->unifm_pixel_norm = - glGetUniformLocationChecked(pass->prog, "pixel_norm"); - pass->unifm_opacity = glGetUniformLocationChecked(pass->prog, "opacity"); - pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - - ctx->resize_width += kern->w / 2; - ctx->resize_height += kern->h / 2; - } - - if (nkernels == 1) { - // Generate an extra null pass so we don't need special code path for - // the single pass case - auto pass = &ctx->blur_shader[1]; - pass->prog = gl_create_program_from_str(vertex_shader, dummy_frag); - pass->unifm_pixel_norm = -1; - pass->unifm_opacity = -1; - pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - - ctx->npasses = 2; - } else { - ctx->npasses = nkernels; - } - - success = true; -out: - if (method != BLUR_METHOD_KERNEL) { - // We generated the blur kernels, so we need to free them - for (int i = 0; i < nkernels; i++) { - free(kernels[i]); - } - free(kernels); - } - - free(extension); - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); - - return success; -} - -bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection, - enum blur_method method, void *args) { - bool success = false; - auto ctx = (struct gl_blur_context *)blur_context; - - ctx->method = method; - - auto blur_params = generate_dual_kawase_params(args); - - // Specify required textures and FBOs - ctx->blur_texture_count = blur_params->iterations; - ctx->blur_fbo_count = blur_params->iterations; - - ctx->resize_width += blur_params->expand; - ctx->resize_height += blur_params->expand; - - ctx->npasses = 2; - ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t); - - char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); - // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane - // Thanks to hiciu for reporting. - setlocale(LC_NUMERIC, "C"); - - // Dual-kawase downsample shader / program - auto down_pass = ctx->blur_shader; - { - // clang-format off - static const char *FRAG_SHADER_DOWN = GLSL(330, - uniform sampler2D tex_src; - uniform float scale = 1.0; - uniform vec2 pixel_norm; - in vec2 texcoord; - out vec4 out_color; - void main() { - vec2 offset = %.7g * pixel_norm; - vec2 uv = texcoord * pixel_norm * (2.0 / scale); - vec4 sum = texture2D(tex_src, uv) * 4.0; - sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset); - sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset); - out_color = sum / 8.0; - } - ); - // clang-format on - - // Build shader - size_t shader_len = - strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = - snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - - // Build program - down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); - free(shader_str); - if (!down_pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(down_pass->prog, 0, "out_color"); - - // Get uniform addresses - down_pass->unifm_pixel_norm = - glGetUniformLocationChecked(down_pass->prog, "pixel_norm"); - down_pass->texorig_loc = - glGetUniformLocationChecked(down_pass->prog, "texorig"); - down_pass->scale_loc = - glGetUniformLocationChecked(down_pass->prog, "scale"); - - // Setup projection matrix - glUseProgram(down_pass->prog); - int pml = glGetUniformLocationChecked(down_pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - } - - // Dual-kawase upsample shader / program - auto up_pass = ctx->blur_shader + 1; - { - // clang-format off - static const char *FRAG_SHADER_UP = GLSL(330, - uniform sampler2D tex_src; - uniform float scale = 1.0; - uniform vec2 pixel_norm; - uniform float opacity; - in vec2 texcoord; - out vec4 out_color; - void main() { - vec2 offset = %.7g * pixel_norm; - vec2 uv = texcoord * pixel_norm / (2 * scale); - vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset); - sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0; - sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0; - sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0; - sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset); - sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0; - out_color = sum / 12.0 * opacity; - } - ); - // clang-format on - - // Build shader - size_t shader_len = - strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = - snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - - // Build program - up_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); - free(shader_str); - if (!up_pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(up_pass->prog, 0, "out_color"); - - // Get uniform addresses - up_pass->unifm_pixel_norm = - glGetUniformLocationChecked(up_pass->prog, "pixel_norm"); - up_pass->unifm_opacity = - glGetUniformLocationChecked(up_pass->prog, "opacity"); - up_pass->texorig_loc = - glGetUniformLocationChecked(up_pass->prog, "texorig"); - up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale"); - - // Setup projection matrix - glUseProgram(up_pass->prog); - int pml = glGetUniformLocationChecked(up_pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - } - - success = true; -out: - free(blur_params); - - if (!success) { - ctx = NULL; - } - - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); - - return success; -} - -void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) { - bool success; - auto gd = (struct gl_data *)base; - - auto ctx = ccalloc(1, struct gl_blur_context); - - if (!method || method >= BLUR_METHOD_INVALID) { - ctx->method = BLUR_METHOD_NONE; - return ctx; - } + GLint viewport_dimensions[2]; + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); // Set projection matrix to gl viewport dimensions so we can use screen // coordinates for all vertices // Note: OpenGL matrices are column major - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, - {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, + GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0}, + {0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0}, {0, 0, 0, 0}, {-1, -1, 0, 1}}; - if (method == BLUR_METHOD_DUAL_KAWASE) { - success = gl_create_dual_kawase_blur_context(ctx, projection_matrix[0], - method, args); - } else { - success = - gl_create_kernel_blur_context(ctx, projection_matrix[0], method, args); - } - if (!success || ctx->method == BLUR_METHOD_NONE) { - goto out; - } - - // Texture size will be defined by gl_blur - ctx->blur_textures = ccalloc(ctx->blur_texture_count, GLuint); - ctx->texture_sizes = ccalloc(ctx->blur_texture_count, struct texture_size); - glGenTextures(ctx->blur_texture_count, ctx->blur_textures); - - for (int i = 0; i < ctx->blur_texture_count; ++i) { - glBindTexture(GL_TEXTURE_2D, ctx->blur_textures[i]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - // Generate FBO and textures when needed - ctx->blur_fbos = ccalloc(ctx->blur_fbo_count, GLuint); - glGenFramebuffers(ctx->blur_fbo_count, ctx->blur_fbos); - - for (int i = 0; i < ctx->blur_fbo_count; ++i) { - if (!ctx->blur_fbos[i]) { - log_error("Failed to generate framebuffer objects for blur"); - success = false; - goto out; - } - } - -out: - if (!success) { - gl_destroy_blur_context(&gd->base, ctx); - ctx = NULL; - } - - gl_check_err(); - return ctx; -} + int pml = glGetUniformLocationChecked(win_shader->prog, "projection"); + glUseProgram(win_shader->prog); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + glUseProgram(0); -void gl_get_blur_size(void *blur_context, int *width, int *height) { - auto ctx = (struct gl_blur_context *)blur_context; - *width = ctx->resize_width; - *height = ctx->resize_height; + return win_shader; } -// clang-format off -const char *win_shader_glsl = GLSL(330, - uniform float opacity; - uniform float dim; - uniform bool invert_color; - in vec2 texcoord; - uniform sampler2D tex; - uniform sampler2D brightness; - uniform float max_brightness; - - void main() { - vec4 c = texelFetch(tex, ivec2(texcoord), 0); - if (invert_color) { - c = vec4(c.aaa - c.rgb, c.a); - } - c = vec4(c.rgb * (1.0 - dim), c.a) * opacity; - - vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb; - // Ref: https://en.wikipedia.org/wiki/Relative_luminance - float brightness = rgb_brightness.r * 0.21 + - rgb_brightness.g * 0.72 + - rgb_brightness.b * 0.07; - if (brightness > max_brightness) - c.rgb = c.rgb * (max_brightness / brightness); - - gl_FragColor = c; +uint64_t gl_get_shader_attributes(backend_t *backend_data attr_unused, void *shader) { + auto win_shader = (gl_win_shader_t *)shader; + uint64_t ret = 0; + if (glGetUniformLocation(win_shader->prog, "time") >= 0) { + ret |= SHADER_ATTRIBUTE_ANIMATED; } -); - -const char *present_vertex_shader = GLSL(330, - uniform mat4 projection; - layout(location = 0) in vec2 coord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(coord, 0, 1); - texcoord = coord; - } -); -// clang-format on + return ret; +} bool gl_init(struct gl_data *gd, session_t *ps) { // Initialize GLX data structure @@ -1602,7 +823,7 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glViewport(0, 0, viewport_dimensions[0], viewport_dimensions[1]); // Clear screen - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClearColor(0.0F, 0.0F, 0.0F, 1.0F); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glGenFramebuffers(1, &gd->back_fbo); @@ -1619,24 +840,35 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); + gd->default_mask_texture = gl_new_texture(GL_TEXTURE_2D); + if (!gd->default_mask_texture) { + log_error("Failed to generate a default mask texture"); + return false; + } + + glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, + (GLbyte[]){'\xff'}); + glBindTexture(GL_TEXTURE_2D, 0); + + // Initialize shaders + gd->default_shader = gl_create_window_shader(NULL, win_shader_default); + if (!gd->default_shader) { + log_error("Failed to create window shaders"); + return false; + } + // Set projection matrix to gl viewport dimensions so we can use screen // coordinates for all vertices // Note: OpenGL matrices are column major - GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, - {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, + GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0}, + {0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0}, {0, 0, 0, 0}, {-1, -1, 0, 1}}; - // Initialize shaders - gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader); - int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection"); - glUseProgram(gd->win_shader.prog); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag); gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color"); - pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection"); + int pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection"); glUseProgram(gd->fill_shader.prog); glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); @@ -1652,6 +884,17 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); + gd->shadow_shader.prog = + gl_create_program_from_str(present_vertex_shader, shadow_colorization_frag); + gd->shadow_shader.uniform_color = + glGetUniformLocationChecked(gd->shadow_shader.prog, "color"); + pml = glGetUniformLocationChecked(gd->shadow_shader.prog, "projection"); + glUseProgram(gd->shadow_shader.prog); + glUniform1i(glGetUniformLocationChecked(gd->shadow_shader.prog, "tex"), 0); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + glUseProgram(0); + glBindFragDataLocation(gd->shadow_shader.prog, 0, "out_color"); + gd->brightness_shader.prog = gl_create_program_from_str(interpolating_vert, interpolating_frag); if (!gd->brightness_shader.prog) { @@ -1689,18 +932,24 @@ bool gl_init(struct gl_data *gd, session_t *ps) { } else { gd->is_nvidia = false; } + gd->has_robustness = gl_has_extension("GL_ARB_robustness"); + gd->has_egl_image_storage = gl_has_extension("GL_EXT_EGL_image_storage"); + gl_check_err(); return true; } void gl_deinit(struct gl_data *gd) { - gl_free_prog_main(&gd->win_shader); - if (gd->logger) { log_remove_target_tls(gd->logger); gd->logger = NULL; } + if (gd->default_shader) { + gl_destroy_window_shader(&gd->base, gd->default_shader); + gd->default_shader = NULL; + } + gl_check_err(); } @@ -1832,7 +1081,8 @@ static void gl_image_apply_alpha(backend_t *base, struct backend_image *img, glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, inner->texture, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); - _gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, 0, false); + _gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, inner->height, + !inner->y_inverted); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo); @@ -1904,19 +1154,202 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, return true; } -bool gl_read_pixel(backend_t *base attr_unused, void *image_data, int x, int y, - struct color *output) { - struct backend_image *tex = image_data; - auto inner = (struct gl_texture *)tex->inner; - GLfloat color[4]; - glReadPixels(x, inner->y_inverted ? inner->height - y : y, 1, 1, GL_RGBA, - GL_FLOAT, color); - output->alpha = color[3]; - output->red = color[0]; - output->green = color[1]; - output->blue = color[2]; - - bool ret = glGetError() == GL_NO_ERROR; - gl_clear_err(); - return ret; +bool gl_set_image_property(backend_t *backend_data, enum image_properties prop, + void *image_data, void *args) { + if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) { + return default_set_image_property(backend_data, prop, image_data, args); + } + + struct backend_image *img = image_data; + auto inner = (struct gl_texture *)img->inner; + inner->shader = args; + return true; +} + +struct gl_shadow_context { + double radius; + void *blur_context; +}; + +struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius) { + auto ctx = ccalloc(1, struct gl_shadow_context); + ctx->radius = radius; + ctx->blur_context = NULL; + + if (radius > 0) { + struct gaussian_blur_args args = { + .size = (int)radius, + .deviation = gaussian_kernel_std_for_size(radius, 0.5 / 256.0), + }; + ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_GAUSSIAN, &args); + if (!ctx->blur_context) { + log_error("Failed to create shadow context"); + free(ctx); + return NULL; + } + } + return (struct backend_shadow_context *)ctx; +} + +void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx) { + auto ctx_ = (struct gl_shadow_context *)ctx; + if (ctx_->blur_context) { + gl_destroy_blur_context(base, (struct backend_blur_context *)ctx_->blur_context); + } + free(ctx_); +} + +void *gl_shadow_from_mask(backend_t *base, void *mask, + struct backend_shadow_context *sctx, struct color color) { + log_debug("Create shadow from mask"); + auto gd = (struct gl_data *)base; + auto img = (struct backend_image *)mask; + auto inner = (struct gl_texture *)img->inner; + auto gsctx = (struct gl_shadow_context *)sctx; + int radius = (int)gsctx->radius; + + auto new_inner = ccalloc(1, struct gl_texture); + new_inner->width = inner->width + radius * 2; + new_inner->height = inner->height + radius * 2; + new_inner->texture = gl_new_texture(GL_TEXTURE_2D); + new_inner->has_alpha = inner->has_alpha; + new_inner->y_inverted = true; + auto new_img = default_new_backend_image(new_inner->width, new_inner->height); + new_img->inner = (struct backend_image_inner_base *)new_inner; + new_img->inner->refcount = 1; + + // Render the mask to a texture, so inversion and corner radius can be + // applied. + auto source_texture = gl_new_texture(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, source_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0, + GL_RED, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + source_texture, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + if (img->color_inverted) { + // If the mask is inverted, clear the source_texture to white, so the + // "outside" of the mask would be correct + glClearColor(1, 1, 1, 1); + } else { + glClearColor(0, 0, 0, 1); + } + glClear(GL_COLOR_BUFFER_BIT); + { + // clang-format off + // interleaved vertex coordinates and texture coordinates + GLint coords[] = {radius , radius , 0 , 0, + radius + inner->width, radius , inner->width, 0, + radius + inner->width, radius + inner->height, inner->width, inner->height, + radius , radius + inner->height, 0 , inner->height,}; + // clang-format on + GLuint indices[] = {0, 1, 2, 2, 3, 0}; + _gl_compose(base, mask, fbo, NULL, (coord_t){0}, coords, indices, 1); + } + + gl_check_err(); + + auto tmp_texture = source_texture; + if (gsctx->blur_context != NULL) { + glActiveTexture(GL_TEXTURE0); + tmp_texture = gl_new_texture(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tmp_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, + new_inner->height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tmp_texture, 0); + + region_t reg_blur; + pixman_region32_init_rect(®_blur, 0, 0, (unsigned int)new_inner->width, + (unsigned int)new_inner->height); + // gl_blur expects reg_blur to be in X coordinate system (i.e. y flipped), + // but we are covering the whole texture so we don't need to worry about + // that. + gl_blur_impl( + 1.0, gsctx->blur_context, NULL, (coord_t){0}, ®_blur, NULL, + source_texture, + (geometry_t){.width = new_inner->width, .height = new_inner->height}, + fbo, gd->default_mask_texture); + pixman_region32_fini(®_blur); + } + + // Colorize the shadow with color. + log_debug("Colorize shadow"); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, new_inner->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, new_inner->width, new_inner->height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + new_inner->texture, 0); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + + glBindTexture(GL_TEXTURE_2D, tmp_texture); + glUseProgram(gd->shadow_shader.prog); + glUniform4f(gd->shadow_shader.uniform_color, (GLfloat)color.red, + (GLfloat)color.green, (GLfloat)color.blue, (GLfloat)color.alpha); + + // clang-format off + GLuint indices[] = {0, 1, 2, 2, 3, 0}; + GLint coord[] = {0 , 0 , + new_inner->width , 0 , + new_inner->width , new_inner->height, + 0 , new_inner->height,}; + // clang-format on + + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint bo[2]; + glGenBuffers(2, bo); + glBindBuffer(GL_ARRAY_BUFFER, bo[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 8, coord, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, + GL_STATIC_DRAW); + + glEnableVertexAttribArray(vert_coord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); + + glDisableVertexAttribArray(vert_coord_loc); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(2, bo); + + glDeleteTextures(1, (GLuint[]){source_texture}); + if (tmp_texture != source_texture) { + glDeleteTextures(1, (GLuint[]){tmp_texture}); + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + gl_check_err(); + return new_img; +} + +enum device_status gl_device_status(backend_t *base) { + auto gd = (struct gl_data *)base; + if (!gd->has_robustness) { + return DEVICE_STATUS_NORMAL; + } + if (glGetGraphicsResetStatusARB() == GL_NO_ERROR) { + return DEVICE_STATUS_NORMAL; + } + return DEVICE_STATUS_RESETTING; } diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index b1d93b0..3a78865 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -12,16 +12,40 @@ #define CASESTRRET(s) \ case s: return #s +struct gl_blur_context; + +static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) { + auto ret = glGetUniformLocation(p, name); + if (ret < 0) { + log_info("Failed to get location of uniform '%s'. This is normal when " + "using custom shaders.", + name); + } + return ret; +} + +#define bind_uniform(shader, uniform) \ + (shader)->uniform_##uniform = glGetUniformLocationChecked((shader)->prog, #uniform) // Program and uniforms for window shader typedef struct { + UT_hash_handle hh; + uint32_t id; GLuint prog; - GLint unifm_opacity; - GLint unifm_invert_color; - GLint unifm_tex; - GLint unifm_dim; - GLint unifm_brightness; - GLint unifm_max_brightness; + GLint uniform_opacity; + GLint uniform_invert_color; + GLint uniform_tex; + GLint uniform_dim; + GLint uniform_brightness; + GLint uniform_max_brightness; + GLint uniform_corner_radius; + GLint uniform_border_width; + GLint uniform_time; + + GLint uniform_mask_tex; + GLint uniform_mask_offset; + GLint uniform_mask_corner_radius; + GLint uniform_mask_inverted; } gl_win_shader_t; // Program and uniforms for brightness shader @@ -29,13 +53,23 @@ typedef struct { GLuint prog; } gl_brightness_shader_t; +typedef struct { + GLuint prog; + GLint uniform_color; +} gl_shadow_shader_t; + // Program and uniforms for blur shader typedef struct { GLuint prog; - GLint unifm_pixel_norm; - GLint unifm_opacity; + GLint uniform_pixel_norm; + GLint uniform_opacity; GLint texorig_loc; GLint scale_loc; + + GLint uniform_mask_tex; + GLint uniform_mask_offset; + GLint uniform_mask_corner_radius; + GLint uniform_mask_inverted; } gl_blur_shader_t; typedef struct { @@ -53,6 +87,7 @@ struct gl_texture { // Textures for auxiliary uses. GLuint auxiliary_texture[2]; + gl_win_shader_t *shader; void *user_data; }; @@ -60,14 +95,22 @@ struct gl_data { backend_t base; // If we are using proprietary NVIDIA driver bool is_nvidia; + // If ARB_robustness extension is present + bool has_robustness; + // If EXT_EGL_image_storage extension is present + bool has_egl_image_storage; // Height and width of the root window int height, width; - gl_win_shader_t win_shader; + // Hash-table of window shaders + gl_win_shader_t *default_shader; gl_brightness_shader_t brightness_shader; gl_fill_shader_t fill_shader; + gl_shadow_shader_t shadow_shader; GLuint back_texture, back_fbo; GLuint present_prog; + GLuint default_mask_texture; + /// Called when an gl_texture is decoupled from the texture it refers. Returns /// the decoupled user_data void *(*decouple_texture_user_data)(backend_t *base, void *user_data); @@ -83,16 +126,25 @@ typedef struct session session_t; #define GL_PROG_MAIN_INIT \ { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, } +void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, + int extent_height, int texture_height, int root_height, + bool y_inverted, GLint *coord, GLuint *indices); + GLuint gl_create_shader(GLenum shader_type, const char *shader_str); GLuint gl_create_program(const GLuint *const shaders, int nshaders); GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str); +GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders); +void *gl_create_window_shader(backend_t *backend_data, const char *source); +void gl_destroy_window_shader(backend_t *backend_data, void *shader); +uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader); +bool gl_set_image_property(backend_t *backend_data, enum image_properties prop, + void *image_data, void *args); /** * @brief Render a region with texture data. */ -void gl_compose(backend_t *, void *ptex, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_tgt, const region_t *reg_visible); +void gl_compose(backend_t *, void *image_data, coord_t image_dst, void *mask, + coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible); void gl_resize(struct gl_data *, int width, int height); @@ -105,19 +157,29 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, const region_t *reg_op, const region_t *reg_visible, void *arg); void gl_release_image(backend_t *base, void *image_data); +void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg); void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible); -bool gl_blur(backend_t *base, double opacity, void *, const region_t *reg_blur, - const region_t *reg_visible); +bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, + const region_t *reg_blur, const region_t *reg_visible); +bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, + coord_t mask_dst, const region_t *reg_blur, + const region_t *reg_visible attr_unused, GLuint source_texture, + geometry_t source_size, GLuint target_fbo, GLuint default_mask); void *gl_create_blur_context(backend_t *base, enum blur_method, void *args); void gl_destroy_blur_context(backend_t *base, void *ctx); +struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius); +void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx); +void *gl_shadow_from_mask(backend_t *base, void *mask, + struct backend_shadow_context *sctx, struct color color); void gl_get_blur_size(void *blur_context, int *width, int *height); void gl_fill(backend_t *base, struct color, const region_t *clip); void gl_present(backend_t *base, const region_t *); bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output); +enum device_status gl_device_status(backend_t *base); static inline void gl_delete_texture(GLuint texture) { glDeleteTextures(1, &texture); @@ -218,3 +280,13 @@ static inline bool gl_has_extension(const char *ext) { log_info("Missing GL extension %s.", ext); return false; } + +static const GLuint vert_coord_loc = 0; +static const GLuint vert_in_texcoord_loc = 1; + +#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__ +#define QUOTE(...) #__VA_ARGS__ + +extern const char vertex_shader[], copy_with_mask_frag[], masking_glsl[], dummy_frag[], + fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], win_shader_glsl[], + win_shader_default[], present_vertex_shader[], shadow_colorization_frag[]; diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index 1397d19..109bec9 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -302,16 +302,21 @@ static backend_t *glx_init(session_t *ps) { continue; } - gd->ctx = glXCreateContextAttribsARB(ps->dpy, cfg[i], 0, true, - (int[]){ - GLX_CONTEXT_MAJOR_VERSION_ARB, - 3, - GLX_CONTEXT_MINOR_VERSION_ARB, - 3, - GLX_CONTEXT_PROFILE_MASK_ARB, - GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - 0, - }); + int *attributes = (int[]){GLX_CONTEXT_MAJOR_VERSION_ARB, + 3, + GLX_CONTEXT_MINOR_VERSION_ARB, + 3, + GLX_CONTEXT_PROFILE_MASK_ARB, + GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + 0, + 0, + 0}; + if (glxext.has_GLX_ARB_create_context_robustness) { + attributes[6] = GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB; + attributes[7] = GLX_LOSE_CONTEXT_ON_RESET_ARB; + } + + gd->ctx = glXCreateContextAttribsARB(ps->dpy, cfg[i], 0, true, attributes); free(cfg); if (!gd->ctx) { @@ -388,11 +393,10 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b } log_trace("Binding pixmap %#010x", pixmap); - auto wd = ccalloc(1, struct backend_image); - wd->max_brightness = 1; + auto wd = default_new_backend_image(r->width, r->height); auto inner = ccalloc(1, struct gl_texture); - inner->width = wd->ewidth = r->width; - inner->height = wd->eheight = r->height; + inner->width = r->width; + inner->height = r->height; wd->inner = (struct backend_image_inner_base *)inner; free(r); @@ -440,9 +444,6 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b inner->user_data = glxpixmap; inner->texture = gl_new_texture(GL_TEXTURE_2D); inner->has_alpha = fmt.alpha_size != 0; - wd->opacity = 1; - wd->color_inverted = false; - wd->dim = 0; wd->inner->refcount = 1; glBindTexture(GL_TEXTURE_2D, inner->texture); glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL); @@ -494,7 +495,7 @@ static void glx_diagnostics(backend_t *base) { auto gl_renderer = (const char *)glGetString(GL_RENDERER); printf("* GL renderer: %s\n", gl_renderer); - if (strcmp(glx_vendor, "Mesa Project and SGI")) { + if (strcmp(glx_vendor, "Mesa Project and SGI") == 0) { for (size_t i = 0; i < ARR_SIZE(software_renderer_names); i++) { if (strstr(gl_renderer, software_renderer_names[i]) != NULL) { warn_software_rendering = true; @@ -529,19 +530,26 @@ struct backend_operations glx_ops = { .release_image = gl_release_image, .compose = gl_compose, .image_op = gl_image_op, - .set_image_property = default_set_image_property, - .read_pixel = gl_read_pixel, + .set_image_property = gl_set_image_property, .clone_image = default_clone_image, .blur = gl_blur, .is_image_transparent = default_is_image_transparent, .present = glx_present, .buffer_age = glx_buffer_age, - .render_shadow = default_backend_render_shadow, + .create_shadow_context = gl_create_shadow_context, + .destroy_shadow_context = gl_destroy_shadow_context, + .render_shadow = backend_render_shadow_from_mask, + .shadow_from_mask = gl_shadow_from_mask, + .make_mask = gl_make_mask, .fill = gl_fill, .create_blur_context = gl_create_blur_context, .destroy_blur_context = gl_destroy_blur_context, .get_blur_size = gl_get_blur_size, .diagnostics = glx_diagnostics, + .device_status = gl_device_status, + .create_shader = gl_create_window_shader, + .destroy_shader = gl_destroy_window_shader, + .get_shader_attributes = gl_get_shader_attributes, .max_buffer_age = 5, // Why? }; @@ -609,6 +617,7 @@ void glxext_init(Display *dpy, int screen) { check_ext(GLX_EXT_texture_from_pixmap); check_ext(GLX_ARB_create_context); check_ext(GLX_EXT_buffer_age); + check_ext(GLX_ARB_create_context_robustness); #ifdef GLX_MESA_query_renderer check_ext(GLX_MESA_query_renderer); #endif diff --git a/src/backend/gl/glx.h b/src/backend/gl/glx.h index 1061f0b..44b4da0 100644 --- a/src/backend/gl/glx.h +++ b/src/backend/gl/glx.h @@ -55,6 +55,7 @@ struct glxext_info { bool has_GLX_ARB_create_context; bool has_GLX_EXT_buffer_age; bool has_GLX_MESA_query_renderer; + bool has_GLX_ARB_create_context_robustness; }; extern struct glxext_info glxext; diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c new file mode 100644 index 0000000..4a18e62 --- /dev/null +++ b/src/backend/gl/shaders.c @@ -0,0 +1,187 @@ +#include "gl_common.h" + +// clang-format off +const char dummy_frag[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + void main() { + gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); + } +); + +const char copy_with_mask_frag[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + float mask_factor(); + void main() { + gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0) * mask_factor(); + } +); + +const char fill_frag[] = GLSL(330, + uniform vec4 color; + void main() { + gl_FragColor = color; + } +); + +const char fill_vert[] = GLSL(330, + layout(location = 0) in vec2 in_coord; + uniform mat4 projection; + void main() { + gl_Position = projection * vec4(in_coord, 0, 1); + } +); + +const char interpolating_frag[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + void main() { + gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); + } +); + +const char interpolating_vert[] = GLSL(330, + uniform mat4 projection; + uniform vec2 texsize; + layout(location = 0) in vec2 in_coord; + layout(location = 1) in vec2 in_texcoord; + out vec2 texcoord; + void main() { + gl_Position = projection * vec4(in_coord, 0, 1); + texcoord = in_texcoord / texsize; + } +); +const char masking_glsl[] = GLSL(330, + uniform sampler2D mask_tex; + uniform vec2 mask_offset; + uniform float mask_corner_radius; + uniform bool mask_inverted; + in vec2 texcoord; + float mask_rectangle_sdf(vec2 point, vec2 half_size) { + vec2 d = abs(point) - half_size; + return length(max(d, 0.0)); + } + float mask_factor() { + vec2 mask_size = textureSize(mask_tex, 0); + vec2 maskcoord = texcoord - mask_offset; + vec4 mask = texture2D(mask_tex, maskcoord / mask_size); + if (mask_corner_radius != 0) { + vec2 inner_size = mask_size - vec2(mask_corner_radius) * 2.0f; + float dist = mask_rectangle_sdf(maskcoord - mask_size / 2.0f, + inner_size / 2.0f) - mask_corner_radius; + if (dist > 0.0f) { + mask.r *= (1.0f - clamp(dist, 0.0f, 1.0f)); + } + } + if (mask_inverted) { + mask.rgb = 1.0 - mask.rgb; + } + return mask.r; + } +); +const char win_shader_glsl[] = GLSL(330, + uniform float opacity; + uniform float dim; + uniform float corner_radius; + uniform float border_width; + uniform bool invert_color; + in vec2 texcoord; + uniform sampler2D tex; + uniform sampler2D brightness; + uniform float max_brightness; + // Signed distance field for rectangle center at (0, 0), with size of + // half_size * 2 + float rectangle_sdf(vec2 point, vec2 half_size) { + vec2 d = abs(point) - half_size; + return length(max(d, 0.0)); + } + + vec4 default_post_processing(vec4 c) { + vec4 border_color = texture(tex, vec2(0.0, 0.5)); + if (invert_color) { + c = vec4(c.aaa - c.rgb, c.a); + border_color = vec4(border_color.aaa - border_color.rgb, border_color.a); + } + c = vec4(c.rgb * (1.0 - dim), c.a) * opacity; + border_color = vec4(border_color.rgb * (1.0 - dim), border_color.a) * opacity; + + vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb; + // Ref: https://en.wikipedia.org/wiki/Relative_luminance + float brightness = rgb_brightness.r * 0.21 + + rgb_brightness.g * 0.72 + + rgb_brightness.b * 0.07; + if (brightness > max_brightness) { + c.rgb = c.rgb * (max_brightness / brightness); + border_color.rgb = border_color.rgb * (max_brightness / brightness); + } + + // Rim color is the color of the outer rim of the window, if there is no + // border, it's the color of the window itself, otherwise it's the border. + // Using mix() to avoid a branch here. + vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f)); + + vec2 outer_size = vec2(textureSize(tex, 0)); + vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f; + float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f, + inner_size / 2.0f) - corner_radius; + if (rect_distance > 0.0f) { + c = (1.0f - clamp(rect_distance, 0.0f, 1.0f)) * rim_color; + } else { + float factor = clamp(rect_distance + border_width, 0.0f, 1.0f); + c = (1.0f - factor) * c + factor * border_color; + } + + return c; + } + + vec4 window_shader(); + float mask_factor(); + + void main() { + gl_FragColor = window_shader() * mask_factor(); + } +); + +const char win_shader_default[] = GLSL(330, + in vec2 texcoord; + uniform sampler2D tex; + vec4 default_post_processing(vec4 c); + vec4 window_shader() { + vec4 c = texelFetch(tex, ivec2(texcoord), 0); + return default_post_processing(c); + } +); + +const char present_vertex_shader[] = GLSL(330, + uniform mat4 projection; + layout(location = 0) in vec2 coord; + out vec2 texcoord; + void main() { + gl_Position = projection * vec4(coord, 0, 1); + texcoord = coord; + } +); +const char vertex_shader[] = GLSL(330, + uniform mat4 projection; + uniform float scale = 1.0; + uniform vec2 texorig; + layout(location = 0) in vec2 coord; + layout(location = 1) in vec2 in_texcoord; + out vec2 texcoord; + void main() { + gl_Position = projection * vec4(coord, 0, scale); + texcoord = in_texcoord + texorig; + } +); +const char shadow_colorization_frag[] = GLSL(330, + uniform vec4 color; + uniform sampler2D tex; + in vec2 texcoord; + out vec4 out_color; + void main() { + vec4 c = texelFetch(tex, ivec2(texcoord), 0); + out_color = c.r * color; + } +); +// clang-format on diff --git a/src/backend/meson.build b/src/backend/meson.build index b8f0ad9..1e8e72b 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -3,5 +3,5 @@ srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backe # enable opengl if get_option('opengl') - srcs += [ files('gl/gl_common.c', 'gl/glx.c') ] + srcs += [ files('gl/gl_common.c', 'gl/glx.c', 'gl/blur.c', 'gl/shaders.c', 'gl/egl.c') ] endif diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index ccf358b..2b7f8e1 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -88,19 +88,153 @@ struct _xrender_image_data_inner { bool owned; }; -static void compose_impl(struct _xrender_data *xd, const struct backend_image *img, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_paint, const region_t *reg_visible, - xcb_render_picture_t result) { - auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; +struct xrender_rounded_rectangle_cache { + int refcount; + // A cached picture of a rounded rectangle. Xorg rasterizes shapes on CPU so it's + // exceedingly slow. + xcb_render_picture_t p; +}; + +struct xrender_image { + struct backend_image base; + + struct xrender_rounded_rectangle_cache *rounded_rectangle; +}; + +/// Make a picture of size width x height, which has a rounded rectangle of corner_radius +/// rendered in it. +struct xrender_rounded_rectangle_cache * +make_rounded_corner_cache(xcb_connection_t *c, xcb_render_picture_t src, + xcb_drawable_t root, int width, int height, int corner_radius) { + auto picture = x_create_picture_with_standard(c, root, width, height, + XCB_PICT_STANDARD_ARGB_32, 0, NULL); + if (picture == XCB_NONE) { + return NULL; + } + + int inner_height = height - 2 * corner_radius; + int cap_height = corner_radius; + if (inner_height < 0) { + cap_height = height / 2; + inner_height = 0; + } + auto points = ccalloc(cap_height * 4 + 4, xcb_render_pointfix_t); + int point_count = 0; + +#define ADD_POINT(px, py) \ + assert(point_count < cap_height * 4 + 4); \ + points[point_count].x = DOUBLE_TO_XFIXED(px); \ + points[point_count].y = DOUBLE_TO_XFIXED(py); \ + point_count += 1; + + // The top cap + for (int i = 0; i <= cap_height; i++) { + double y = corner_radius - i; + double delta = sqrt(corner_radius * corner_radius - y * y); + double left = corner_radius - delta; + double right = width - corner_radius + delta; + if (left >= right) { + continue; + } + ADD_POINT(left, i); + ADD_POINT(right, i); + } + + // The middle rectangle + if (inner_height > 0) { + ADD_POINT(0, cap_height + inner_height); + ADD_POINT(width, cap_height + inner_height); + } + + // The bottom cap + for (int i = cap_height + inner_height + 1; i <= height; i++) { + double y = corner_radius - (height - i); + double delta = sqrt(corner_radius * corner_radius - y * y); + double left = corner_radius - delta; + double right = width - corner_radius + delta; + if (left >= right) { + break; + } + ADD_POINT(left, i); + ADD_POINT(right, i); + } +#undef ADD_POINT + + XCB_AWAIT_VOID(xcb_render_tri_strip, c, XCB_RENDER_PICT_OP_SRC, src, picture, + x_get_pictfmt_for_standard(c, XCB_PICT_STANDARD_A_8), 0, 0, + (uint32_t)point_count, points); + free(points); + auto ret = ccalloc(1, struct xrender_rounded_rectangle_cache); + ret->p = picture; + ret->refcount = 1; + return ret; +} + +static xcb_render_picture_t process_mask(struct _xrender_data *xd, struct xrender_image *mask, + xcb_render_picture_t alpha_pict, bool *allocated) { + auto inner = (struct _xrender_image_data_inner *)mask->base.inner; + if (!mask->base.color_inverted && mask->base.corner_radius == 0) { + *allocated = false; + return inner->pict; + } + const auto tmpw = to_u16_checked(inner->width); + const auto tmph = to_u16_checked(inner->height); + *allocated = true; + x_clear_picture_clip_region(xd->base.c, inner->pict); + auto ret = x_create_picture_with_visual( + xd->base.c, xd->base.root, inner->width, inner->height, inner->visual, + XCB_RENDER_CP_REPEAT, + (xcb_render_create_picture_value_list_t[]){XCB_RENDER_REPEAT_PAD}); + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, + ret, 0, 0, 0, 0, 0, 0, tmpw, tmph); + // Remember: the mask has a 1-pixel border + if (mask->base.corner_radius != 0) { + if (mask->rounded_rectangle == NULL) { + mask->rounded_rectangle = make_rounded_corner_cache( + xd->base.c, xd->white_pixel, xd->base.root, inner->width - 2, + inner->height - 2, (int)mask->base.corner_radius); + } + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + mask->rounded_rectangle->p, XCB_NONE, ret, 0, 0, 0, + 0, 1, 1, (uint16_t)(tmpw - 2), (uint16_t)(tmph - 2)); + } + + if (mask->base.color_inverted) { + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_XOR, xd->white_pixel, + XCB_NONE, ret, 0, 0, 0, 0, 0, 0, tmpw, tmph); + } + + if (alpha_pict != XCB_NONE) { + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, ret, alpha_pict, + ret, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width), + to_u16_checked(inner->height)); + } + + return ret; +} + +static void +compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, + struct xrender_image *mask, coord_t mask_dst, const region_t *reg_paint, + const region_t *reg_visible, xcb_render_picture_t result) { + const struct backend_image *img = &xrimg->base; + bool mask_allocated = false; + auto mask_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; + if (mask != NULL) { + mask_pict = process_mask( + xd, mask, img->opacity < 1.0 ? mask_pict : XCB_NONE, &mask_allocated); + } auto inner = (struct _xrender_image_data_inner *)img->inner; region_t reg; bool has_alpha = inner->has_alpha || img->opacity != 1; - const auto tmpw = to_u16_checked(dst_x2 - dst_x1); - const auto tmph = to_u16_checked(dst_y2 - dst_y1); - const auto tmpew = to_u16_checked(dst_x2 - dst_x1); - const auto tmpeh = to_u16_checked(dst_y2 - dst_y1); + const auto tmpw = to_u16_checked(inner->width); + const auto tmph = to_u16_checked(inner->height); + const auto tmpew = to_u16_checked(img->ewidth); + const auto tmpeh = to_u16_checked(img->eheight); + // Remember: the mask has a 1-pixel border + const auto mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1); + const auto mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1); const xcb_render_color_t dim_color = { .red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)}; @@ -111,20 +245,12 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i pixman_region32_init(®); pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible); x_set_picture_clip_region(xd->base.c, result, 0, 0, ®); - -#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) - { - const xcb_render_transform_t transform = { - DOUBLE_TO_XFIXED((double)img->ewidth / (double)tmpew), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0), - DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED((double)img->eheight / (double)tmpeh), DOUBLE_TO_XFIXED(0.0), - DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(1.0), - }; - xcb_render_set_picture_transform(xd->base.c, inner->pict, transform); - xcb_render_set_picture_filter(xd->base.c, inner->pict, 7, "nearest", 0, NULL); - } -#undef DOUBLE_TO_XFIXED - - if ((img->color_inverted || img->dim != 0) && has_alpha) { + if (img->corner_radius != 0 && xrimg->rounded_rectangle == NULL) { + xrimg->rounded_rectangle = make_rounded_corner_cache( + xd->base.c, xd->white_pixel, xd->base.root, inner->width, + inner->height, (int)img->corner_radius); + } + if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) { // Apply image properties using a temporary image, because the source // image is transparent. Otherwise the properties can be applied directly // on the target image. @@ -133,17 +259,25 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i inner->height, inner->visual, 0, NULL); // Set clip region translated to source coordinate - x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst_x1), - to_i16_checked(-dst_y1), ®); + x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x), + to_i16_checked(-dst.y), ®); // Copy source -> tmp - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, inner->pict, + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); + + if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) { + // Clip tmp_pict with a rounded rectangle + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + xrimg->rounded_rectangle->p, XCB_NONE, + tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); + } + if (img->color_inverted) { if (inner->has_alpha) { auto tmp_pict2 = x_create_picture_with_visual( xd->base.c, xd->base.root, tmpw, tmph, inner->visual, 0, NULL); - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, tmp_pict, XCB_NONE, tmp_pict2, 0, 0, 0, 0, 0, 0, tmpw, tmph); @@ -174,30 +308,31 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i } xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict, - alpha_pict, result, 0, 0, 0, 0, to_i16_checked(dst_x1), - to_i16_checked(dst_y1), tmpew, tmpeh); + mask_pict, result, 0, 0, mask_dst_x, mask_dst_y, + to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew, + tmpeh); xcb_render_free_picture(xd->base.c, tmp_pict); } else { uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC); - xcb_render_composite(xd->base.c, op, inner->pict, alpha_pict, result, 0, - 0, 0, 0, to_i16_checked(dst_x1), - to_i16_checked(dst_y1), tmpew, tmpeh); + xcb_render_composite(xd->base.c, op, inner->pict, mask_pict, result, 0, 0, + mask_dst_x, mask_dst_y, to_i16_checked(dst.x), + to_i16_checked(dst.y), tmpew, tmpeh); if (img->dim != 0 || img->color_inverted) { // Apply properties, if we reach here, then has_alpha == false assert(!has_alpha); if (img->color_inverted) { xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE, xd->white_pixel, XCB_NONE, result, 0, - 0, 0, 0, to_i16_checked(dst_x1), - to_i16_checked(dst_y1), tmpew, tmpeh); + 0, 0, 0, to_i16_checked(dst.x), + to_i16_checked(dst.y), tmpew, tmpeh); } if (img->dim != 0) { // Dim the actually content of window xcb_rectangle_t rect = { - .x = to_i16_checked(dst_x1), - .y = to_i16_checked(dst_y1), + .x = to_i16_checked(dst.x), + .y = to_i16_checked(dst.y), .width = tmpew, .height = tmpeh, }; @@ -207,15 +342,17 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i } } } + if (mask_allocated) { + xcb_render_free_picture(xd->base.c, mask_pict); + } pixman_region32_fini(®); } -static void compose(backend_t *base, void *img_data, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, +static void compose(backend_t *base, void *img_data, coord_t dst, void *mask, coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible) { - // TODO(dccsillag): use dst_{x,y}2 struct _xrender_data *xd = (void *)base; - return compose_impl(xd, img_data, dst_x1, dst_y1, dst_x2, dst_y2, reg_paint, reg_visible, xd->back[2]); + return compose_impl(xd, img_data, dst, mask, mask_dst, reg_paint, reg_visible, + xd->back[2]); } static void fill(backend_t *base, struct color c, const region_t *clip) { @@ -236,8 +373,8 @@ static void fill(backend_t *base, struct color c, const region_t *clip) { .height = to_u16_checked(extent->y2 - extent->y1)}}); } -static bool blur(backend_t *backend_data, double opacity, void *ctx_, - const region_t *reg_blur, const region_t *reg_visible) { +static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask, + coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible) { struct _xrender_blur_context *bctx = ctx_; if (bctx->method == BLUR_METHOD_NONE) { return true; @@ -288,7 +425,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, pixman_region32_fini(&clip); xcb_render_picture_t src_pict = xd->back[2], dst_pict = tmp_picture[0]; - auto alpha_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)]; + auto mask_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)]; + bool mask_allocated = false; + if (mask != NULL) { + mask_pict = process_mask(xd, mask, opacity != 1.0 ? mask_pict : XCB_NONE, + &mask_allocated); + } int current = 0; x_set_picture_clip_region(c, src_pict, 0, 0, ®_op_resized); @@ -324,11 +466,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, } else { x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); // This is the last pass, and we are doing more than 1 pass - xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict, - alpha_pict, xd->back[2], 0, 0, 0, 0, - to_i16_checked(extent_resized->x1), - to_i16_checked(extent_resized->y1), - width_resized, height_resized); + xcb_render_composite( + c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], + 0, 0, to_i16_checked(extent_resized->x1 - mask_dst.x + 1), + to_i16_checked(extent_resized->y1 - mask_dst.y + 1), + to_i16_checked(extent_resized->x1), + to_i16_checked(extent_resized->y1), width_resized, height_resized); } // reset filter @@ -344,8 +487,10 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, if (i == 1) { x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); xcb_render_composite( - c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, xd->back[2], 0, 0, - 0, 0, to_i16_checked(extent_resized->x1), + c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], 0, 0, + to_i16_checked(extent_resized->x1 - mask_dst.x + 1), + to_i16_checked(extent_resized->y1 - mask_dst.y + 1), + to_i16_checked(extent_resized->x1), to_i16_checked(extent_resized->y1), width_resized, height_resized); } @@ -366,11 +511,11 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool return NULL; } - auto img = ccalloc(1, struct backend_image); + auto img = ccalloc(1, struct xrender_image); auto inner = ccalloc(1, struct _xrender_image_data_inner); inner->depth = (uint8_t)fmt.visual_depth; - inner->width = img->ewidth = r->width; - inner->height = img->eheight = r->height; + inner->width = img->base.ewidth = r->width; + inner->height = img->base.eheight = r->height; inner->pixmap = pixmap; inner->has_alpha = fmt.alpha_size != 0; inner->pict = @@ -379,8 +524,9 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool inner->visual = fmt.visual; inner->refcount = 1; - img->inner = (struct backend_image_inner_base *)inner; - img->opacity = 1; + img->base.inner = (struct backend_image_inner_base *)inner; + img->base.opacity = 1; + img->rounded_rectangle = NULL; free(r); if (inner->pict == XCB_NONE) { @@ -397,11 +543,28 @@ static void release_image_inner(backend_t *base, struct _xrender_image_data_inne } free(inner); } + +static void +release_rounded_corner_cache(backend_t *base, struct xrender_rounded_rectangle_cache *cache) { + if (!cache) { + return; + } + + assert(cache->refcount > 0); + cache->refcount--; + if (cache->refcount == 0) { + xcb_free_pixmap(base->c, cache->p); + free(cache); + } +} + static void release_image(backend_t *base, void *image) { - struct backend_image *img = image; - img->inner->refcount--; - if (img->inner->refcount == 0) { - release_image_inner(base, (void *)img->inner); + struct xrender_image *img = image; + release_rounded_corner_cache(base, img->rounded_rectangle); + img->rounded_rectangle = NULL; + img->base.inner->refcount -= 1; + if (img->base.inner->refcount == 0) { + release_image_inner(base, (void *)img->base.inner); } free(img); } @@ -523,6 +686,52 @@ new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) { return new_inner; } +static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) { + struct _xrender_data *xd = (void *)base; + // Give the mask a 1 pixel wide border to emulate the clamp to border behavior of + // OpenGL textures. + auto w16 = to_u16_checked(size.width + 2); + auto h16 = to_u16_checked(size.height + 2); + auto inner = + new_inner(base, size.width + 2, size.height + 2, + x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32), 32); + xcb_render_change_picture(base->c, inner->pict, XCB_RENDER_CP_REPEAT, + (uint32_t[]){XCB_RENDER_REPEAT_PAD}); + const rect_t *extent = pixman_region32_extents((region_t *)reg); + x_set_picture_clip_region(base->c, xd->back[2], 1, 1, reg); + xcb_render_fill_rectangles( + base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, + (xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0xffff}, 1, + (xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1 + 1), + .y = to_i16_checked(extent->y1 + 1), + .width = to_u16_checked(extent->x2 - extent->x1), + .height = to_u16_checked(extent->y2 - extent->y1)}}); + x_clear_picture_clip_region(xd->base.c, inner->pict); + + // Paint the border transparent + xcb_render_fill_rectangles( + base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, + (xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0}, 4, + (xcb_rectangle_t[]){{.x = 0, .y = 0, .width = w16, .height = 1}, + {.x = 0, .y = 0, .width = 1, .height = h16}, + {.x = 0, .y = (short)(h16 - 1), .width = w16, .height = 1}, + {.x = (short)(w16 - 1), .y = 0, .width = 1, .height = h16}}); + inner->refcount = 1; + + auto img = ccalloc(1, struct xrender_image); + img->base.eheight = size.height + 2; + img->base.ewidth = size.width + 2; + img->base.border_width = 0; + img->base.color_inverted = false; + img->base.corner_radius = 0; + img->base.max_brightness = 1; + img->base.opacity = 1; + img->base.dim = 0; + img->base.inner = (struct backend_image_inner_base *)inner; + img->rounded_rectangle = NULL; + return img; +} + static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) { if (img->inner->refcount == 1) { return true; @@ -648,30 +857,6 @@ static void get_blur_size(void *blur_context, int *width, int *height) { *height = ctx->resize_height; } -static bool -read_pixel(backend_t *backend_data, void *image_data, int x, int y, struct color *output) { - auto xd = (struct _xrender_data *)backend_data; - auto img = (struct backend_image *)image_data; - auto inner = (struct _xrender_image_data_inner *)img->inner; - - auto r = XCB_AWAIT(xcb_get_image, xd->base.c, XCB_IMAGE_FORMAT_XY_PIXMAP, inner->pixmap, - to_i16_checked(x), to_i16_checked(y), 1, 1, (uint32_t)-1L); - - if (!r) { - return false; - } - - // Color format seems to be BGRA8888, see glamor_format_for_pixmap from the - // Xserver codebase. - uint8_t *pixels = xcb_get_image_data(r); - output->blue = pixels[0] / 255.0; - output->green = pixels[1] / 255.0; - output->red = pixels[2] / 255.0; - output->alpha = pixels[3] / 255.0; - - return true; -} - static backend_t *backend_xrender_init(session_t *ps) { auto xd = ccalloc(1, struct _xrender_data); init_backend_base(&xd->base, ps); @@ -751,6 +936,29 @@ err: return NULL; } +void *clone_image(backend_t *base attr_unused, const void *image_data, + const region_t *reg_visible attr_unused) { + auto new_img = ccalloc(1, struct xrender_image); + *new_img = *(struct xrender_image *)image_data; + new_img->base.inner->refcount++; + if (new_img->rounded_rectangle) { + new_img->rounded_rectangle->refcount++; + } + return new_img; +} + +static bool +set_image_property(backend_t *base, enum image_properties op, void *image, void *args) { + auto xrimg = (struct xrender_image *)image; + if (op == IMAGE_PROPERTY_CORNER_RADIUS && + ((double *)args)[0] != xrimg->base.corner_radius) { + // Free cached rounded rectangle if corner radius changed + release_rounded_corner_cache(base, xrimg->rounded_rectangle); + xrimg->rounded_rectangle = NULL; + } + return default_set_image_property(base, op, image, args); +} + struct backend_operations xrender_ops = { .init = backend_xrender_init, .deinit = deinit, @@ -760,7 +968,10 @@ struct backend_operations xrender_ops = { .fill = fill, .bind_pixmap = bind_pixmap, .release_image = release_image, + .create_shadow_context = default_create_shadow_context, + .destroy_shadow_context = default_destroy_shadow_context, .render_shadow = default_backend_render_shadow, + .make_mask = make_mask, //.prepare_win = prepare_win, //.release_win = release_win, .is_image_transparent = default_is_image_transparent, @@ -768,9 +979,8 @@ struct backend_operations xrender_ops = { .max_buffer_age = 2, .image_op = image_op, - .read_pixel = read_pixel, - .clone_image = default_clone_image, - .set_image_property = default_set_image_property, + .clone_image = clone_image, + .set_image_property = set_image_property, .create_blur_context = create_blur_context, .destroy_blur_context = destroy_blur_context, .get_blur_size = get_blur_size, @@ -579,8 +579,8 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { // Copy target name out int tgtlen = 0; - for (; pattern[offset] && - (isalnum((unsigned char)pattern[offset]) || '_' == pattern[offset]); + for (; pattern[offset] && (isalnum((unsigned char)pattern[offset]) || + '_' == pattern[offset] || '.' == pattern[offset]); ++offset) { ++tgtlen; } @@ -852,6 +852,10 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) C2H_SKIP_SPACES(); } + if (raw == true) { + log_warn("Raw string patterns has been deprecated. pos %d", offset); + } + // Check for delimiters if (pattern[offset] == '\"' || pattern[offset] == '\'') { pleaf->ptntype = C2_L_PTSTRING; @@ -886,11 +890,10 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) case 'v': *(ptptnstr++) = '\v'; break; case 'o': case 'x': { - char *tstr = strndup(pattern + offset + 1, 2); + scoped_charp tstr = strndup(pattern + offset + 1, 2); char *pstr = NULL; long val = strtol( tstr, &pstr, ('o' == pattern[offset] ? 8 : 16)); - free(tstr); if (pstr != &tstr[2] || val <= 0) c2_error("Invalid octal/hex escape " "sequence."); @@ -1148,11 +1151,16 @@ static void c2_free(c2_ptr_t p) { /** * Free a condition tree in c2_lptr_t. */ -c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) { - if (!lp) +c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) { + if (!lp) { return NULL; + } c2_lptr_t *pnext = lp->next; + if (f) { + f(lp->data); + } + lp->data = NULL; c2_free(lp->ptr); free(lp); @@ -1324,13 +1332,13 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w switch (pleaf->ptntype) { // Deal with integer patterns case C2_L_PTINT: { - long *targets = NULL; - long *targets_free = NULL; + long long *targets = NULL; + long long *targets_free = NULL; size_t ntargets = 0; // Get the value // A predefined target - long predef_target = 0; + long long predef_target = 0; if (pleaf->predef != C2_L_PUNDEFINED) { *perr = false; switch (pleaf->predef) { @@ -1379,7 +1387,7 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); if (ntargets > 0) { - targets = targets_free = ccalloc(ntargets, long); + targets = targets_free = ccalloc(ntargets, long long); *perr = false; for (size_t i = 0; i < ntargets; ++i) { targets[i] = winprop_get_int(prop, i); @@ -1395,7 +1403,7 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w // Do comparison bool res = false; for (size_t i = 0; i < ntargets; ++i) { - long tgt = targets[i]; + long long tgt = targets[i]; switch (pleaf->op) { case C2_L_OEXISTS: res = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true); @@ -1672,3 +1680,21 @@ bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condl return false; } + +/// Iterate over all conditions in a condition linked list. Call the callback for each of +/// the conditions. If the callback returns true, the iteration stops early. +/// +/// Returns whether the iteration was stopped early. +bool c2_list_foreach(const c2_lptr_t *condlist, c2_list_foreach_cb_t cb, void *data) { + for (auto i = condlist; i; i = i->next) { + if (cb(i, data)) { + return true; + } + } + return false; +} + +/// Return user data stored in a condition. +void *c2_list_get_data(const c2_lptr_t *condlist) { + return condlist->data; +} @@ -12,15 +12,31 @@ #pragma once #include <stdbool.h> +#include <stddef.h> typedef struct _c2_lptr c2_lptr_t; typedef struct session session_t; struct managed_win; +typedef void (*c2_userdata_free)(void *); c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data); -c2_lptr_t *c2_free_lptr(c2_lptr_t *lp); +c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f); -bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, void **pdata); +bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, + void **pdata); bool c2_list_postprocess(session_t *ps, c2_lptr_t *list); +typedef bool (*c2_list_foreach_cb_t)(const c2_lptr_t *cond, void *data); +bool c2_list_foreach(const c2_lptr_t *list, c2_list_foreach_cb_t cb, void *data); +/// Return user data stored in a condition. +void *c2_list_get_data(const c2_lptr_t *condlist); + +/** + * Destroy a condition list. + */ +static inline void c2_list_free(c2_lptr_t **pcondlst, c2_userdata_free f) { + while ((*pcondlst = c2_free_lptr(*pcondlst, f))) { + } + *pcondlst = NULL; +} diff --git a/src/cache.c b/src/cache.c index 1ffb31c..ce9c501 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1,8 +1,8 @@ #include <uthash.h> +#include "cache.h" #include "compiler.h" #include "utils.h" -#include "cache.h" struct cache_entry { char *key; diff --git a/src/common.h b/src/common.h index b7f2fe0..809d4e5 100644 --- a/src/common.h +++ b/src/common.h @@ -36,9 +36,9 @@ #include <X11/Xlib.h> #include <ev.h> #include <pixman.h> -#include <xcb/xproto.h> #include <xcb/render.h> #include <xcb/sync.h> +#include <xcb/xproto.h> #include "uthash_extra.h" #ifdef CONFIG_OPENGL @@ -55,11 +55,11 @@ #include "backend/driver.h" #include "compiler.h" #include "config.h" +#include "list.h" #include "region.h" +#include "render.h" #include "types.h" #include "utils.h" -#include "list.h" -#include "render.h" #include "win_defs.h" #include "x.h" @@ -130,6 +130,14 @@ typedef struct _latom { struct _latom *next; } latom_t; +struct shader_info { + char *key; + char *source; + void *backend_shader; + uint64_t attributes; + UT_hash_handle hh; +}; + /// Structure containing all necessary data for a session. typedef struct session { // === Event handlers === @@ -141,13 +149,10 @@ typedef struct session { ev_timer fade_timer; /// Timer for animations ev_timer animation_timer; - /// Timer for delayed drawing, right now only used by - /// swopti - ev_timer delayed_draw_timer; /// Use an ev_idle callback for drawing /// So we only start drawing when events are processed ev_idle draw_idle; - /// Called everytime we have timeouts or new data on socket, + /// Called every time we have timeouts or new data on socket, /// so we can be sure if xcb read from X socket at anytime during event /// handling, we will not left any event unhandled in the queue ev_prepare event_check; @@ -155,6 +160,8 @@ typedef struct session { ev_signal usr1_signal; /// Signal handler for SIGINT ev_signal int_signal; + + // === Backend related === /// backend data backend_t *backend_data; /// backend blur context @@ -165,6 +172,8 @@ typedef struct session { void *file_watch_handle; /// libev mainloop struct ev_loop *loop; + /// Shaders + struct shader_info *shaders; // === Display related === /// Whether the X server is grabbed by us @@ -191,9 +200,7 @@ typedef struct session { int root_desktop_num; /// Desktop switch direction int root_desktop_switch_direction; - // Damage of root window. - // Damage root_damage; - /// X Composite overlay window. Used if <code>--paint-on-overlay</code>. + /// X Composite overlay window. xcb_window_t overlay; /// The target window for debug mode xcb_window_t debug_window; @@ -237,7 +244,7 @@ typedef struct session { /// Whether we need to redraw the screen bool redraw_needed; - /// Cache a xfixes region so we don't need to allocate it everytime. + /// Cache a xfixes region so we don't need to allocate it every time. /// A workaround for yshui/picom#301 xcb_xfixes_region_t damaged_region; /// The region needs to painted on next paint. @@ -251,7 +258,7 @@ typedef struct session { /// Pre-generated alpha pictures. xcb_render_picture_t *alpha_picts; /// Time of last fading. In milliseconds. - long fade_time; + long long fade_time; /// Time of last window animation step. In milliseconds. long animation_time; /// Head pointer of the error ignore linked list. @@ -299,17 +306,13 @@ typedef struct session { xcb_render_picture_t cshadow_picture; /// 1x1 white Picture. xcb_render_picture_t white_picture; - /// Gaussian map of shadow. - struct conv *gaussian_map; + /// Backend shadow context. + struct backend_shadow_context *shadow_context; // for shadow precomputation /// A region in which shadow is not painted on. region_t shadow_exclude_reg; // === Software-optimization-related === - /// Currently used refresh rate. - int refresh_rate; - /// Interval between refresh in nanoseconds. - long refresh_intv; /// Nanosecond offset of the first painting. long paint_tm_offset; diff --git a/src/compiler.h b/src/compiler.h index f146bd2..00da4cb 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -85,6 +85,12 @@ # define fallthrough() #endif +#if __has_attribute(cleanup) +# define cleanup(func) __attribute__((cleanup(func))) +#else +# error "Compiler is missing cleanup attribute" +#endif + #if __STDC_VERSION__ >= 201112L # define attr_noret _Noreturn #else @@ -102,7 +108,7 @@ #endif #ifndef __has_include -# define __has_include(x) 0 +#define __has_include(x) 0 #endif #if !defined(__STDC_NO_THREADS__) && __has_include(<threads.h>) diff --git a/src/config.c b/src/config.c index 1d58f00..cb5e08a 100644 --- a/src/config.c +++ b/src/config.c @@ -3,13 +3,21 @@ // Copyright (c) 2013 Richard Grenville <[email protected]> #include <ctype.h> +#include <errno.h> +#include <fcntl.h> #include <limits.h> #include <math.h> #include <stdbool.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> #include <xcb/render.h> // for xcb_render_fixed_t, XXX +#include <test.h> + #include "c2.h" #include "common.h" #include "compiler.h" @@ -23,6 +31,98 @@ #include "config.h" +const char *xdg_config_home(void) { + char *xdgh = getenv("XDG_CONFIG_HOME"); + char *home = getenv("HOME"); + const char *default_dir = "/.config"; + + if (!xdgh) { + if (!home) { + return NULL; + } + + xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1); + + strcpy(xdgh, home); + strcat(xdgh, default_dir); + } else { + xdgh = strdup(xdgh); + } + + return xdgh; +} + +char **xdg_config_dirs(void) { + char *xdgd = getenv("XDG_CONFIG_DIRS"); + size_t count = 0; + + if (!xdgd) { + xdgd = "/etc/xdg"; + } + + for (int i = 0; xdgd[i]; i++) { + if (xdgd[i] == ':') { + count++; + } + } + + // Store the string and the result pointers together so they can be + // freed together + char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1); + auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd); + auto path = dirs; + + for (size_t i = 0; i < count; i++) { + dir_list[i] = path; + path = strchr(path, ':'); + *path = '\0'; + path++; + } + dir_list[count] = path; + + size_t fill = 0; + for (size_t i = 0; i <= count; i++) { + if (dir_list[i][0] == '/') { + dir_list[fill] = dir_list[i]; + fill++; + } + } + + dir_list[fill] = NULL; + + return dir_list; +} + +TEST_CASE(xdg_config_dirs) { + auto old_var = getenv("XDG_CONFIG_DIRS"); + if (old_var) { + old_var = strdup(old_var); + } + unsetenv("XDG_CONFIG_DIRS"); + + auto result = xdg_config_dirs(); + TEST_STREQUAL(result[0], "/etc/xdg"); + TEST_EQUAL(result[1], NULL); + free(result); + + setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1); + result = xdg_config_dirs(); + TEST_STREQUAL(result[0], "/etc/xdg"); + TEST_STREQUAL(result[1], "/"); + TEST_EQUAL(result[2], NULL); + free(result); + + setenv("XDG_CONFIG_DIRS", ":", 1); + result = xdg_config_dirs(); + TEST_EQUAL(result[0], NULL); + free(result); + + if (old_var) { + setenv("XDG_CONFIG_DIRS", old_var, 1); + free(old_var); + } +} + /** * Parse a long number. */ @@ -468,6 +568,114 @@ bool parse_rule_corners(c2_lptr_t **res, const char *src) { return c2_parse(res, endptr, (void *)val); } +/// Search for auxiliary file under a base directory +static char *locate_auxiliary_file_at(const char *base, const char *scope, const char *file) { + scoped_charp path = mstrjoin(base, scope); + mstrextend(&path, "/"); + mstrextend(&path, file); + if (access(path, O_RDONLY) == 0) { + // Canonicalize path to avoid duplicates + char *abspath = realpath(path, NULL); + return abspath; + } + return NULL; +} + +/** + * Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary + * file. + * + * Follows the XDG specification to search for the shader file in configuration locations. + * + * The search order is: + * 1) If an absolute path is given, use it directly. + * 2) Search for the file directly under `include_dir`. + * 3) Search for the file in the XDG configuration directories, under path + * /picom/<scope>/ + */ +char *locate_auxiliary_file(const char *scope, const char *path, const char *include_dir) { + if (!path || strlen(path) == 0) { + return NULL; + } + + // Filename is absolute path, so try to load from there + if (path[0] == '/') { + if (access(path, O_RDONLY) == 0) { + return realpath(path, NULL); + } + } + + // First try to load file from the include directory (i.e. relative to the + // config file) + if (include_dir && strlen(include_dir)) { + char *ret = locate_auxiliary_file_at(include_dir, "", path); + if (ret) { + return ret; + } + } + + // Fall back to searching in user config directory + scoped_charp picom_scope = mstrjoin("/picom/", scope); + scoped_charp config_home = (char *)xdg_config_home(); + char *ret = locate_auxiliary_file_at(config_home, picom_scope, path); + if (ret) { + return ret; + } + + // Fall back to searching in system config directory + auto config_dirs = xdg_config_dirs(); + for (int i = 0; config_dirs[i]; i++) { + ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path); + if (ret) { + free(config_dirs); + return ret; + } + } + free(config_dirs); + + return ret; +} + +/** + * Parse a list of window shader rules. + */ +bool parse_rule_window_shader(c2_lptr_t **res, const char *src, const char *include_dir) { + if (!src) { + return false; + } + + // Find custom shader terminator + const char *endptr = strchr(src, ':'); + if (!endptr) { + log_error("Custom shader terminator not found: %s", src); + return false; + } + + // Parse and create custom shader + scoped_charp untrimed_shader_source = strdup(src); + if (!untrimed_shader_source) { + return false; + } + auto source_end = strchr(untrimed_shader_source, ':'); + *source_end = '\0'; + + size_t length; + char *tmp = (char *)trim_both(untrimed_shader_source, &length); + tmp[length] = '\0'; + char *shader_source = NULL; + + if (strcasecmp(tmp, "default") != 0) { + shader_source = locate_auxiliary_file("shaders", tmp, include_dir); + if (!shader_source) { + log_error("Custom shader file \"%s\" not found for rule: %s", tmp, src); + free(shader_source); + return false; + } + } + + return c2_parse(res, ++endptr, (void *)shader_source); +} + /** * Add a pattern to a condition linked list. */ @@ -578,7 +786,8 @@ 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_GLX, + .backend = BKEND_XRENDER, + .legacy_backends = false, .glx_no_stencil = false, .mark_wmwin_focused = false, .mark_ovredir_focused = false, @@ -594,14 +803,12 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .benchmark_wid = XCB_NONE, .logpath = NULL, - .refresh_rate = 0, - .sw_opti = false, .use_damage = true, .shadow_red = 0.0, .shadow_green = 0.0, .shadow_blue = 0.0, - .shadow_radius = 12, + .shadow_radius = 18, .shadow_offset_x = -15, .shadow_offset_y = -15, .shadow_opacity = .75, @@ -610,18 +817,20 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .xinerama_shadow_crop = false, .shadow_clip_list = NULL, - .corner_radius = 12, + .corner_radius = 0, - .fade_in_step = 0.03, + .fade_in_step = 0.028, .fade_out_step = 0.03, .fade_delta = 10, .no_fading_openclose = false, .no_fading_destroyed_argb = false, .fade_blacklist = NULL, + // Picom Allusive + .animations = true, .animation_for_open_window = OPEN_WINDOW_ANIMATION_ZOOM, - .animation_for_transient_window = OPEN_WINDOW_ANIMATION_ZOOM, + .animation_for_transient_window = OPEN_WINDOW_ANIMATION_NONE, .animation_for_unmap_window = OPEN_WINDOW_ANIMATION_ZOOM, .animation_for_workspace_switch_in = OPEN_WINDOW_ANIMATION_ZOOM, .animation_for_workspace_switch_out = OPEN_WINDOW_ANIMATION_ZOOM, @@ -631,6 +840,11 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .animation_delta = 10, .animation_force_steps = false, .animation_clamping = true, + .animation_open_blacklist = NULL, + .animation_unmap_blacklist = NULL, + + .corner_rules = NULL, + .blur_rules = NULL, .inactive_opacity = 1.0, .inactive_opacity_override = false, @@ -647,6 +861,8 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .blur_background_blacklist = NULL, .blur_kerns = NULL, .blur_kernel_count = 0, + .window_shader_fg = NULL, + .window_shader_fg_rules = NULL, .inactive_dim = 0.0, .inactive_dim_fixed = false, .invert_color_list = NULL, @@ -661,11 +877,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .track_leader = false, - .rounded_corners_blacklist = NULL, - - .animation_open_blacklist = NULL, - .animation_unmap_blacklist = NULL, - .corner_rules = NULL + .rounded_corners_blacklist = NULL }; // clang-format on diff --git a/src/config.h b/src/config.h index 0ed0357..84418d1 100644 --- a/src/config.h +++ b/src/config.h @@ -15,6 +15,8 @@ #include <xcb/xcb.h> #include <xcb/xfixes.h> +#include "uthash_extra.h" + #ifdef CONFIG_LIBCONFIG #include <libconfig.h> #endif @@ -34,6 +36,7 @@ enum backend { BKEND_GLX, BKEND_XR_GLX_HYBRID, BKEND_DUMMY, + BKEND_EGL, NUM_BKEND, }; @@ -100,8 +103,8 @@ typedef struct options { /// Render to a separate window instead of taking over the screen bool debug_mode; // === General === - /// Use the experimental new backends? - bool experimental_backends; + /// Use the legacy backends? + bool legacy_backends; /// Path to write PID to. char *write_pid_path; /// The backend in use. @@ -152,10 +155,6 @@ typedef struct options { win_option_t wintype_option[NUM_WINTYPES]; // === VSync & software optimization === - /// User-specified refresh rate. - int refresh_rate; - /// Whether to enable refresh-rate-based software optimization. - bool sw_opti; /// VSync method to use; bool vsync; /// Whether to use glFinish() instead of glFlush() for (possibly) better @@ -221,12 +220,10 @@ typedef struct options { bool animation_force_steps; /// Whether to clamp animations bool animation_clamping; - /// TODO: window animation blacklist - /// TODO: open/close animations // === Opacity === /// Default opacity for inactive windows. - /// 32-bit integer with the format of _NET_WM_OPACITY. + /// 32-bit integer with the format of _NET_WM_WINDOW_OPACITY. double inactive_opacity; /// Default opacity for inactive windows. double active_opacity; @@ -236,8 +233,8 @@ typedef struct options { /// Frame opacity. Relative to window opacity, also affects shadow /// opacity. double frame_opacity; - /// Whether to detect _NET_WM_OPACITY on client windows. Used on window - /// managers that don't pass _NET_WM_OPACITY to frame windows. + /// Whether to detect _NET_WM_WINDOW_OPACITY on client windows. Used on window + /// managers that don't pass _NET_WM_WINDOW_OPACITY to frame windows. bool detect_client_opacity; // === Other window processing === @@ -261,6 +258,10 @@ typedef struct options { struct conv **blur_kerns; /// Number of convolution kernels int blur_kernel_count; + /// Custom fragment shader for painting windows + char *window_shader_fg; + /// Rules to change custom fragment shader for painting windows. + c2_lptr_t *window_shader_fg_rules; /// How much to dim an inactive window. 0.0 - 1.0, 0 to disable. double inactive_dim; /// Whether to use fixed inactive dim opacity, instead of deciding @@ -270,8 +271,14 @@ typedef struct options { c2_lptr_t *invert_color_list; /// Rules to change window opacity. c2_lptr_t *opacity_rules; - + // Rules for setting corner radius c2_lptr_t *corner_rules; + // Rules to make windows use blur + c2_lptr_t *blur_rules; + // Rules to exclude windows from having a open animation + c2_lptr_t *animation_open_blacklist; + // Rules to exclude windows from having a unmap animation + c2_lptr_t *animation_unmap_blacklist; /// Limit window brightness double max_brightness; // Radius of rounded window corners @@ -279,10 +286,6 @@ typedef struct options { /// Rounded corners blacklist. A linked list of conditions. c2_lptr_t *rounded_corners_blacklist; - c2_lptr_t *animation_open_blacklist; - - c2_lptr_t *animation_unmap_blacklist; - // === Focus related === /// Whether to try to detect WM windows and mark them as focused. bool mark_wmwin_focused; @@ -307,6 +310,9 @@ typedef struct options { // Make transparent windows clip other windows, instead of blending on top of // them bool transparent_clipping; + /// A list of conditions of windows to which transparent clipping + /// should not apply + c2_lptr_t *transparent_clipping_blacklist; } options_t; extern const char *const BACKEND_STRS[NUM_BKEND + 1]; @@ -316,7 +322,9 @@ bool must_use parse_int(const char *, int *); struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count); bool must_use parse_geometry(session_t *, const char *, region_t *); bool must_use parse_rule_opacity(c2_lptr_t **, const char *); -bool must_use parse_rule_corners(c2_lptr_t **, const char *); +bool must_use parse_rule_window_shader(c2_lptr_t **, const char *, const char *); +char *must_use locate_auxiliary_file(const char *scope, const char *path, + const char *include_dir); enum blur_method must_use parse_blur_method(const char *src); enum open_window_animation must_use parse_open_window_animation(const char *src); @@ -326,6 +334,9 @@ enum open_window_animation must_use parse_open_window_animation(const char *src) bool condlst_add(c2_lptr_t **, const char *); #ifdef CONFIG_LIBCONFIG +const char *xdg_config_home(void); +char **xdg_config_dirs(void); + /// Parse a configuration file /// Returns the actually config_file name used, allocated on heap /// Outputs: diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 2494e0e..0df975b 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2012-2014 Richard Grenville <[email protected]> +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -30,102 +31,11 @@ static inline int lcfg_lookup_bool(const config_t *config, const char *path, boo int ival; int ret = config_lookup_bool(config, path, &ival); - if (ret) + if (ret) { *value = ival; - - return ret; -} - -const char *xdg_config_home(void) { - char *xdgh = getenv("XDG_CONFIG_HOME"); - char *home = getenv("HOME"); - const char *default_dir = "/.config"; - - if (!xdgh) { - if (!home) { - return NULL; - } - - xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1); - - strcpy(xdgh, home); - strcat(xdgh, default_dir); - } else { - xdgh = strdup(xdgh); - } - - return xdgh; -} - -char **xdg_config_dirs(void) { - char *xdgd = getenv("XDG_CONFIG_DIRS"); - size_t count = 0; - - if (!xdgd) { - xdgd = "/etc/xdg"; - } - - for (int i = 0; xdgd[i]; i++) { - if (xdgd[i] == ':') { - count++; - } - } - - // Store the string and the result pointers together so they can be - // freed together - char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1); - auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd); - auto path = dirs; - - for (size_t i = 0; i < count; i++) { - dir_list[i] = path; - path = strchr(path, ':'); - *path = '\0'; - path++; - } - dir_list[count] = path; - - size_t fill = 0; - for (size_t i = 0; i <= count; i++) { - if (dir_list[i][0] == '/') { - dir_list[fill] = dir_list[i]; - fill++; - } } - dir_list[fill] = NULL; - - return dir_list; -} - -TEST_CASE(xdg_config_dirs) { - auto old_var = getenv("XDG_CONFIG_DIRS"); - if (old_var) { - old_var = strdup(old_var); - } - unsetenv("XDG_CONFIG_DIRS"); - - auto result = xdg_config_dirs(); - TEST_STREQUAL(result[0], "/etc/xdg"); - TEST_EQUAL(result[1], NULL); - free(result); - - setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1); - result = xdg_config_dirs(); - TEST_STREQUAL(result[0], "/etc/xdg"); - TEST_STREQUAL(result[1], "/"); - TEST_EQUAL(result[2], NULL); - free(result); - - setenv("XDG_CONFIG_DIRS", ":", 1); - result = xdg_config_dirs(); - TEST_EQUAL(result[0], NULL); - free(result); - - if (old_var) { - setenv("XDG_CONFIG_DIRS", old_var, 1); - free(old_var); - } + return ret; } /// Search for config file under a base directory @@ -275,6 +185,36 @@ parse_cfg_condlst_corner(options_t *opt, const config_t *pcfg, const char *name) } } +/** + * Parse a window shader rule list in configuration file. + */ +static inline void parse_cfg_condlst_shader(options_t *opt, const config_t *pcfg, + const char *name, const char *include_dir) { + 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_window_shader( + &opt->window_shader_fg_rules, + config_setting_get_string_elem(setting, i), + include_dir)) { + exit(1); + } + } + } + // Treat it as a single pattern if it's a string + else if (config_setting_type(setting) == CONFIG_TYPE_STRING) { + if (!parse_rule_window_shader(&opt->window_shader_fg_rules, + config_setting_get_string(setting), + include_dir)) { + exit(1); + } + } + } +} + static inline void parse_wintype_config(const config_t *cfg, const char *member_name, win_option_t *o, win_option_mask_t *mask) { char *str = mstrjoin("wintypes.", member_name); @@ -283,7 +223,7 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_ int ival = 0; const char *sval = NULL; - + if (setting) { if (config_setting_lookup_bool(setting, "shadow", &ival)) { o->shadow = ival; @@ -362,6 +302,11 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shadow_enable, bool *fading_enable, bool *conv_kern_hasneg, win_option_mask_t *winopt_mask) { + + const char *deprecation_message = + "option has been deprecated. Please remove it from your configuration file. " + "If you encounter any problems without this feature, please feel free to " + "open a bug report"; char *path = NULL; FILE *f; config_t cfg; @@ -387,15 +332,14 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad config_set_options(&cfg, CONFIG_OPTION_ALLOW_OVERRIDES); #endif { - // dirname() could modify the original string, thus we must pass a - // copy - char *path2 = strdup(path); - char *parent = dirname(path2); + char *abspath = realpath(path, NULL); + char *parent = dirname(abspath); // path2 may be modified - if (parent) + if (parent) { config_set_include_dir(&cfg, parent); + } - free(path2); + free(abspath); } { @@ -414,15 +358,21 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // Get options from the configuration file. We don't do range checking // right now. It will be done later + // --dbus + lcfg_lookup_bool(&cfg, "dbus", &opt->dbus); + // -D (fade_delta) - if (config_lookup_int(&cfg, "fade-delta", &ival)) + if (config_lookup_int(&cfg, "fade-delta", &ival)) { opt->fade_delta = ival; + } // -I (fade_in_step) - if (config_lookup_float(&cfg, "fade-in-step", &dval)) + if (config_lookup_float(&cfg, "fade-in-step", &dval)) { opt->fade_in_step = normalize_d(dval); + } // -O (fade_out_step) - if (config_lookup_float(&cfg, "fade-out-step", &dval)) + if (config_lookup_float(&cfg, "fade-out-step", &dval)) { opt->fade_out_step = normalize_d(dval); + } // -r (shadow_radius) config_lookup_int(&cfg, "shadow-radius", &opt->shadow_radius); // -o (shadow_opacity) @@ -432,11 +382,13 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // -t (shadow_offset_y) config_lookup_int(&cfg, "shadow-offset-y", &opt->shadow_offset_y); // -i (inactive_opacity) - if (config_lookup_float(&cfg, "inactive-opacity", &dval)) + if (config_lookup_float(&cfg, "inactive-opacity", &dval)) { opt->inactive_opacity = normalize_d(dval); + } // --active_opacity - if (config_lookup_float(&cfg, "active-opacity", &dval)) + 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 @@ -444,26 +396,12 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // -e (frame_opacity) config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity); // -c (shadow_enable) - if (config_lookup_bool(&cfg, "shadow", &ival)) - *shadow_enable = ival; - // -C (no_dock_shadow) - if (config_lookup_bool(&cfg, "no-dock-shadow", &ival)) { - log_error("Option `no-dock-shadow` has been removed. Please use the " - "wintype option `shadow` of `dock` instead."); - goto err; - } - // -G (no_dnd_shadow) - if (config_lookup_bool(&cfg, "no-dnd-shadow", &ival)) { - log_error("Option `no-dnd-shadow` has been removed. Please use the " - "wintype option `shadow` of `dnd` instead."); - goto err; - }; + lcfg_lookup_bool(&cfg, "shadow", shadow_enable); // -m (menu_opacity) if (config_lookup_float(&cfg, "menu-opacity", &dval)) { - log_warn("Option `menu-opacity` is deprecated, and will be " - "removed.Please use the " - "wintype option `opacity` of `popup_menu` and `dropdown_menu` " - "instead."); + log_warn("Option `menu-opacity` is deprecated, and will be removed." + "Please use the wintype option `opacity` of `popup_menu`" + "and `dropdown_menu` instead."); opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = dval; opt->wintype_option[WINTYPE_POPUP_MENU].opacity = dval; winopt_mask[WINTYPE_DROPDOWN_MENU].opacity = true; @@ -510,19 +448,16 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // --detect-client-opacity lcfg_lookup_bool(&cfg, "detect-client-opacity", &opt->detect_client_opacity); // --refresh-rate - if (config_lookup_int(&cfg, "refresh-rate", &opt->refresh_rate)) { - if (opt->refresh_rate < 0) { - log_warn("Invalid refresh rate %d, fallback to 0", opt->refresh_rate); - opt->refresh_rate = 0; - } + if (config_lookup_int(&cfg, "refresh-rate", &ival)) { + log_warn("The refresh-rate %s", deprecation_message); } // --vsync if (config_lookup_string(&cfg, "vsync", &sval)) { - opt->vsync = parse_vsync(sval); - log_warn("vsync option will take a boolean from now on. \"%s\" is " - "interpreted as \"%s\" for compatibility, but this will stop " - "working soon", - sval, opt->vsync ? "true" : "false"); + bool parsed_vsync = parse_vsync(sval); + log_error("vsync option will take a boolean from now on. \"%s\" in " + "your configuration should be changed to \"%s\"", + sval, parsed_vsync ? "true" : "false"); + goto err; } lcfg_lookup_bool(&cfg, "vsync", &opt->vsync); // --backend @@ -551,7 +486,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad opt->logpath = strdup(sval); } // --sw-opti - lcfg_lookup_bool(&cfg, "sw-opti", &opt->sw_opti); + if (lcfg_lookup_bool(&cfg, "sw-opti", &bval)) { + log_warn("The sw-opti %s", deprecation_message); + } // --use-ewmh-active-win lcfg_lookup_bool(&cfg, "use-ewmh-active-win", &opt->use_ewmh_active_win); // --unredir-if-possible @@ -574,6 +511,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad lcfg_lookup_bool(&cfg, "no-ewmh-fullscreen", &opt->no_ewmh_fullscreen); // --transparent-clipping lcfg_lookup_bool(&cfg, "transparent-clipping", &opt->transparent_clipping); + // --transparent-clipping-exclude + parse_cfg_condlst(&cfg, &opt->transparent_clipping_blacklist, + "transparent-clipping-exclude"); // --shadow-exclude parse_cfg_condlst(&cfg, &opt->shadow_blacklist, "shadow-exclude"); // --clip-shadow-above @@ -641,16 +581,18 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad lcfg_lookup_bool(&cfg, "animation-clamping", &opt->animation_clamping); // --focus-exclude parse_cfg_condlst(&cfg, &opt->focus_blacklist, "focus-exclude"); + // --invert-color-include + parse_cfg_condlst(&cfg, &opt->invert_color_list, "invert-color-include"); + // --blur-background-exclude + parse_cfg_condlst(&cfg, &opt->blur_background_blacklist, "blur-background-exclude"); // animation exclude parse_cfg_condlst(&cfg, &opt->animation_open_blacklist, "animation-open-exclude"); // animation exclude parse_cfg_condlst(&cfg, &opt->animation_unmap_blacklist, "animation-unmap-exclude"); - // --corners-rule + // corners-rule parse_cfg_condlst_corner(opt, &cfg, "corners-rule"); - // --invert-color-include - parse_cfg_condlst(&cfg, &opt->invert_color_list, "invert-color-include"); - // --blur-background-exclude - parse_cfg_condlst(&cfg, &opt->blur_background_blacklist, "blur-background-exclude"); + // blur-rule + parse_cfg_condlst(&cfg, &opt->blur_rules, "blur-rule"); // --opacity-rule parse_cfg_condlst_opct(opt, &cfg, "opacity-rule"); // --unredir-if-possible-exclude @@ -701,6 +643,7 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad if (config_lookup_string(&cfg, "glx-swap-method", &sval)) { char *endptr; long val = strtol(sval, &endptr, 10); + bool should_remove = true; if (*endptr || !(*sval)) { // sval is not a number, or an empty string val = -1; @@ -708,12 +651,13 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad if (strcmp(sval, "undefined") != 0 && val != 0) { // If not undefined, we will use damage and buffer-age to limit // the rendering area. - opt->use_damage = true; + should_remove = false; } - log_warn("glx-swap-method has been deprecated since v6, your setting " - "\"%s\" should be %s.", - sval, - opt->use_damage ? "replaced by `use-damage = true`" : "removed"); + log_error("glx-swap-method has been removed, your setting " + "\"%s\" should be %s.", + sval, + !should_remove ? "replaced by `use-damage = true`" : "removed"); + goto err; } // --use-damage lcfg_lookup_bool(&cfg, "use-damage", &opt->use_damage); @@ -726,15 +670,20 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad opt->max_brightness = 1.0; } + // --window-shader-fg + if (config_lookup_string(&cfg, "window-shader-fg", &sval)) { + opt->window_shader_fg = + locate_auxiliary_file("shaders", sval, config_get_include_dir(&cfg)); + } + + // --window-shader-fg-rule + parse_cfg_condlst_shader(opt, &cfg, "window-shader-fg-rule", + config_get_include_dir(&cfg)); + // --glx-use-gpushader4 - if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival) && ival) { - log_warn("glx-use-gpushader4 is deprecated since v6, please remove it " - "from" - "your config file"); - } - // --xrender-sync - if (config_lookup_bool(&cfg, "xrender-sync", &ival) && ival) { - log_error("Please use xrender-sync-fence instead of xrender-sync."); + if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival)) { + log_error("glx-use-gpushader4 has been removed, please remove it " + "from your config file"); goto err; } // --xrender-sync-fence @@ -743,21 +692,6 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad if (lcfg_lookup_bool(&cfg, "clear-shadow", &bval)) log_warn("\"clear-shadow\" is removed as an option, and is always" " enabled now. Consider removing it from your config file"); - if (lcfg_lookup_bool(&cfg, "paint-on-overlay", &bval)) { - log_error("\"paint-on-overlay\" has been removed as an option, and " - "the feature is enabled whenever possible"); - goto err; - } - - if (config_lookup_float(&cfg, "alpha-step", &dval)) { - log_error("\"alpha-step\" has been removed, compton now tries to make use" - " of all alpha values"); - goto err; - } - - const char *deprecation_message attr_unused = - "has been removed. If you encounter problems " - "without this feature, please feel free to open a bug report"; config_setting_t *blur_cfg = config_lookup(&cfg, "blur"); if (blur_cfg) { @@ -72,7 +72,11 @@ typedef uint32_t cdbus_enum_t; cdbus_reply_errm((ps), dbus_message_new_error_printf( \ (srcmsg), (err_name), (err_format), ##__VA_ARGS__)) +#define PICOM_WINDOW_INTERFACE "picom.Window" +#define PICOM_COMPOSITOR_INTERFACE "picom.Compositor" + static DBusHandlerResult cdbus_process(DBusConnection *conn, DBusMessage *m, void *); +static DBusHandlerResult cdbus_process_windows(DBusConnection *c, DBusMessage *msg, void *ud); static dbus_bool_t cdbus_callback_add_timeout(DBusTimeout *timeout, void *data); @@ -179,7 +183,12 @@ bool cdbus_init(session_t *ps, const char *uniq) { dbus_error_free(&err); goto fail; } - dbus_connection_add_filter(cd->dbus_conn, cdbus_process, ps, NULL); + dbus_connection_register_object_path( + cd->dbus_conn, CDBUS_OBJECT_NAME, + (DBusObjectPathVTable[]){{NULL, cdbus_process}}, ps); + dbus_connection_register_fallback( + cd->dbus_conn, CDBUS_OBJECT_NAME "/windows", + (DBusObjectPathVTable[]){{NULL, cdbus_process_windows}}, ps); return true; fail: ps->dbus_data = NULL; @@ -436,6 +445,56 @@ static bool cdbus_apdarg_wid(session_t *ps attr_unused, DBusMessage *msg, const } /** + * Callback to append a Window argument to a message as a variant. + */ +static bool +cdbus_append_wid_variant(session_t *ps attr_unused, DBusMessage *msg, const void *data) { + assert(data); + cdbus_window_t val = *(const xcb_window_t *)data; + + DBusMessageIter it, it2; + dbus_message_iter_init_append(msg, &it); + if (!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, + CDBUS_TYPE_WINDOW_STR, &it2)) { + return false; + } + if (!dbus_message_iter_append_basic(&it2, CDBUS_TYPE_WINDOW, &val)) { + log_error("Failed to append argument."); + return false; + } + if (!dbus_message_iter_close_container(&it, &it2)) { + return false; + } + + return true; +} + +/** + * Callback to append a bool argument to a message as a variant. + */ +static bool +cdbus_append_bool_variant(session_t *ps attr_unused, DBusMessage *msg, const void *data) { + assert(data); + + dbus_bool_t val = *(const bool *)data; + DBusMessageIter it, it2; + dbus_message_iter_init_append(msg, &it); + if (!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, + DBUS_TYPE_BOOLEAN_AS_STRING, &it2)) { + return false; + } + if (!dbus_message_iter_append_basic(&it2, DBUS_TYPE_BOOLEAN, &val)) { + log_error("Failed to append argument."); + return false; + } + if (!dbus_message_iter_close_container(&it, &it2)) { + return false; + } + + return true; +} + +/** * Callback to append an cdbus_enum_t argument to a message. */ static bool cdbus_apdarg_enum(session_t *ps attr_unused, DBusMessage *msg, const void *data) { @@ -466,6 +525,46 @@ cdbus_apdarg_string(session_t *ps attr_unused, DBusMessage *msg, const void *dat } /** + * Callback to append a string argument to a message as a variant. + */ +static bool +cdbus_append_string_variant(session_t *ps attr_unused, DBusMessage *msg, const void *data) { + const char *str = *(const char **)data; + if (!str) { + str = ""; + } + + DBusMessageIter it, it2; + dbus_message_iter_init_append(msg, &it); + if (!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &it2)) { + return false; + } + if (!dbus_message_iter_append_basic(&it2, DBUS_TYPE_STRING, &str)) { + log_error("Failed to append argument."); + return false; + } + if (!dbus_message_iter_close_container(&it, &it2)) { + return false; + } + + return true; +} + +static bool cdbus_append_empty_dict(session_t *ps attr_unused, DBusMessage *msg, + const void *data attr_unused) { + DBusMessageIter it, it2; + dbus_message_iter_init_append(msg, &it); + if (!dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, "{sv}", &it2)) { + return false; + } + if (!dbus_message_iter_close_container(&it, &it2)) { + return false; + } + return true; +} + +/** * Callback to append all window IDs to a message. */ static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data attr_unused) { @@ -515,14 +614,14 @@ static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data * add an argument * @param data data pointer to pass to the function */ -static bool cdbus_signal(session_t *ps, const char *name, +static bool cdbus_signal(session_t *ps, const char *interface, const char *name, bool (*func)(session_t *ps, DBusMessage *msg, const void *data), const void *data) { struct cdbus_data *cd = ps->dbus_data; DBusMessage *msg = NULL; // Create a signal - msg = dbus_message_new_signal(CDBUS_OBJECT_NAME, CDBUS_INTERFACE_NAME, name); + msg = dbus_message_new_signal(CDBUS_OBJECT_NAME, interface, name); if (!msg) { log_error("Failed to create D-Bus signal."); return false; @@ -551,8 +650,9 @@ static bool cdbus_signal(session_t *ps, const char *name, /** * Send a signal with a Window ID as argument. */ -static inline bool cdbus_signal_wid(session_t *ps, const char *name, xcb_window_t wid) { - return cdbus_signal(ps, name, cdbus_apdarg_wid, &wid); +static inline bool +cdbus_signal_wid(session_t *ps, const char *interface, const char *name, xcb_window_t wid) { + return cdbus_signal(ps, interface, name, cdbus_apdarg_wid, &wid); } /** @@ -735,6 +835,84 @@ static bool cdbus_process_list_win(session_t *ps, DBusMessage *msg) { /** * Process a win_get D-Bus request. */ +static bool +cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_t wid) { + const char *target = NULL; + const char *interface = NULL; + DBusError err = {}; + + if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &target, DBUS_TYPE_INVALID)) { + log_error("Failed to parse argument of \"Get\" (%s).", err.message); + dbus_error_free(&err); + return false; + } + + if (strcmp(interface, PICOM_WINDOW_INTERFACE)) { + return false; + } + + auto w = find_managed_win(ps, wid); + + if (!w) { + log_error("Window %#010x not found.", wid); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); + return true; + } + +#define cdbus_m_win_get_do(tgt, member, apdarg_func) \ + if (!strcmp(#tgt, target)) { \ + cdbus_reply(ps, msg, apdarg_func, &w->member); \ + return true; \ + } + + if (!strcmp("Mapped", target)) { + cdbus_reply(ps, msg, cdbus_append_bool_variant, + (bool[]){win_is_mapped_in_x(w)}); + return true; + } + + if (!strcmp(target, "Id")) { + cdbus_reply(ps, msg, cdbus_append_wid_variant, &w->base.id); + return true; + } + + // next + if (!strcmp("Next", target)) { + cdbus_window_t next_id = 0; + if (!list_node_is_last(&ps->window_stack, &w->base.stack_neighbour)) { + next_id = list_entry(w->base.stack_neighbour.next, struct win, + stack_neighbour) + ->id; + } + cdbus_reply(ps, msg, cdbus_append_wid_variant, &next_id); + return true; + } + + cdbus_m_win_get_do(ClientWin, client_win, cdbus_append_wid_variant); + cdbus_m_win_get_do(Leader, leader, cdbus_append_wid_variant); + cdbus_m_win_get_do(Name, name, cdbus_append_string_variant); + if (!strcmp("Type", target)) { + cdbus_reply(ps, msg, cdbus_append_string_variant, &WINTYPES[w->window_type]); + return true; + } + if (!strcmp("RawFocused", target)) { + cdbus_reply(ps, msg, cdbus_append_bool_variant, + (bool[]){win_is_focused_raw(ps, w)}); + return true; + } + +#undef cdbus_m_win_get_do + + log_error(CDBUS_ERROR_BADTGT_S, target); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); + + return true; +} + +/** + * Process a win_get D-Bus request. + */ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) { cdbus_window_t wid = XCB_NONE; const char *target = NULL; @@ -761,7 +939,10 @@ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) { return true; \ } - cdbus_m_win_get_do(base.id, cdbus_reply_wid); + if (!strcmp(target, "id")) { + cdbus_reply_wid(ps, msg, w->base.id); + return true; + } // next if (!strcmp("next", target)) { @@ -972,7 +1153,7 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { // version if (!strcmp("version", target)) { - cdbus_reply_string(ps, msg, COMPTON_VERSION); + cdbus_reply_string(ps, msg, PICOM_VERSION); return true; } @@ -1005,8 +1186,8 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { cdbus_m_opts_get_do(stoppaint_force, cdbus_reply_enum); cdbus_m_opts_get_do(logpath, cdbus_reply_string); - cdbus_m_opts_get_do(refresh_rate, cdbus_reply_int32); - cdbus_m_opts_get_do(sw_opti, cdbus_reply_bool); + cdbus_m_opts_get_stub(refresh_rate, cdbus_reply_int32, 0); + cdbus_m_opts_get_stub(sw_opti, cdbus_reply_bool, false); cdbus_m_opts_get_do(vsync, cdbus_reply_bool); if (!strcmp("backend", target)) { assert(ps->o.backend < sizeof(BACKEND_STRS) / sizeof(BACKEND_STRS[0])); @@ -1207,6 +1388,21 @@ static bool cdbus_process_introspect(session_t *ps, DBusMessage *msg) { " <method name='reset' />\n" " <method name='repaint' />\n" " </interface>\n" + " <interface name='" PICOM_COMPOSITOR_INTERFACE "'>\n" + " <signal name='WinAdded'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " <signal name='WinDestroyed'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " <signal name='WinMapped'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " <signal name='WinUnmapped'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " </interface>\n" + " <node name='windows' />\n" "</node>\n"; cdbus_reply_string(ps, msg, str_introspect); @@ -1216,6 +1412,90 @@ static bool cdbus_process_introspect(session_t *ps, DBusMessage *msg) { ///@} /** + * Process an D-Bus Introspect request, for /windows. + */ +static bool cdbus_process_windows_root_introspect(session_t *ps, DBusMessage *msg) { + static const char *str_introspect = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection " + "1.0//EN\"\n" + " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name='org.freedesktop.DBus.Introspectable'>\n" + " <method name='Introspect'>\n" + " <arg name='data' direction='out' type='s' />\n" + " </method>\n" + " </interface>\n"; + + char *ret = NULL; + mstrextend(&ret, str_introspect); + + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + if (!w->managed) { + continue; + } + char *tmp = NULL; + asprintf(&tmp, "<node name='%#010x'/>\n", w->id); + mstrextend(&ret, tmp); + free(tmp); + } + mstrextend(&ret, "</node>"); + + bool success = cdbus_reply_string(ps, msg, ret); + free(ret); + return success; +} + +static bool cdbus_process_window_introspect(session_t *ps, DBusMessage *msg) { + // clang-format off + static const char *str_introspect = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection " + "1.0//EN\"\n" + " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name='org.freedesktop.DBus.Introspectable'>\n" + " <method name='Introspect'>\n" + " <arg name='data' direction='out' type='s' />\n" + " </method>\n" + " </interface>\n" + " <interface name='org.freedesktop.DBus.Properties'>\n" + " <method name='Get'>\n" + " <arg type='s' name='interface_name' direction='in'/>\n" + " <arg type='s' name='property_name' direction='in'/>\n" + " <arg type='v' name='value' direction='out'/>\n" + " </method>\n" + " <method name='GetAll'>\n" + " <arg type='s' name='interface_name' direction='in'/>\n" + " <arg type='a{sv}' name='properties' direction='out'/>\n" + " </method>\n" + " <method name='Set'>\n" + " <arg type='s' name='interface_name' direction='in'/>\n" + " <arg type='s' name='property_name' direction='in'/>\n" + " <arg type='v' name='value' direction='in'/>\n" + " </method>\n" + " <signal name='PropertiesChanged'>\n" + " <arg type='s' name='interface_name'/>\n" + " <arg type='a{sv}' name='changed_properties'/>\n" + " <arg type='as' name='invalidated_properties'/>\n" + " </signal>\n" + " </interface>\n" + " <interface name='" PICOM_WINDOW_INTERFACE "'>\n" + " <property type='" CDBUS_TYPE_WINDOW_STR "' name='Leader' access='read'/>\n" + " <property type='" CDBUS_TYPE_WINDOW_STR "' name='ClientWin' access='read'/>\n" + " <property type='" CDBUS_TYPE_WINDOW_STR "' name='Id' access='read'/>\n" + " <property type='" CDBUS_TYPE_WINDOW_STR "' name='Next' access='read'/>\n" + " <property type='b' name='RawFocused' access='read'/>\n" + " <property type='b' name='Mapped' access='read'/>\n" + " <property type='s' name='Name' access='read'/>\n" + " <property type='s' name='Type' access='read'/>\n" + " </interface>\n" + "</node>\n"; + // clang-format on + + return cdbus_reply_string(ps, msg, str_introspect); +} + +/** * Process a message from D-Bus. */ static DBusHandlerResult @@ -1299,42 +1579,122 @@ cdbus_process(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) { return handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } +/** + * Process a message from D-Bus, for /windows path. + */ +static DBusHandlerResult +cdbus_process_windows(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) { + session_t *ps = ud; + bool handled = false; + const char *path = dbus_message_get_path(msg); + const char *last_segment = strrchr(path, '/'); + if (last_segment == NULL) { + if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) && + !dbus_message_get_no_reply(msg)) + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S); + return DBUS_HANDLER_RESULT_HANDLED; + } + bool is_root = strncmp(last_segment, "/windows", 8) == 0; + if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", + "Introspect")) { + if (is_root) { + handled = cdbus_process_windows_root_introspect(ps, msg); + } else { + handled = cdbus_process_window_introspect(ps, msg); + } + goto finished; + } + + if (!is_root) { + auto wid = (cdbus_window_t)strtol(last_segment + 1, NULL, 0); + if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Properties", + "GetAll")) { + handled = cdbus_reply(ps, msg, cdbus_append_empty_dict, NULL); + goto finished; + } + + if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Properties", "Get")) { + handled = cdbus_process_window_property_get(ps, msg, wid); + goto finished; + } + } + + if (dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameAcquired") || + dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameLost")) { + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (DBUS_MESSAGE_TYPE_ERROR == dbus_message_get_type(msg)) { + log_error("Error message of path \"%s\" " + "interface \"%s\", member \"%s\", error \"%s\"", + dbus_message_get_path(msg), dbus_message_get_interface(msg), + dbus_message_get_member(msg), dbus_message_get_error_name(msg)); + } else { + log_error("Illegal message of type \"%s\", path \"%s\" " + "interface \"%s\", member \"%s\"", + cdbus_repr_msgtype(msg), dbus_message_get_path(msg), + dbus_message_get_interface(msg), dbus_message_get_member(msg)); + } + if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) && + !dbus_message_get_no_reply(msg)) { + handled = cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S); + } + +finished: + if (!handled && dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL && + !dbus_message_get_no_reply(msg)) { + handled = + cdbus_reply_err(ps, msg, CDBUS_ERROR_UNKNOWN, CDBUS_ERROR_UNKNOWN_S); + } + return handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + /** @name Core callbacks */ ///@{ void cdbus_ev_win_added(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_added", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_added", w->id); + cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinAdded", w->id); + } } void cdbus_ev_win_destroyed(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_destroyed", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_destroyed", w->id); + cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinDestroyed", w->id); + } } void cdbus_ev_win_mapped(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_mapped", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_mapped", w->id); + cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinMapped", w->id); + } } void cdbus_ev_win_unmapped(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_unmapped", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_unmapped", w->id); + cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinUnmapped", w->id); + } } void cdbus_ev_win_focusout(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_focusout", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_focusout", w->id); + } } void cdbus_ev_win_focusin(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_focusin", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_focusin", w->id); + } } //!@} diff --git a/src/diagnostic.c b/src/diagnostic.c index d275b1a..2cb3c8f 100644 --- a/src/diagnostic.c +++ b/src/diagnostic.c @@ -2,17 +2,17 @@ // Copyright (c) 2018 Yuxuan Shui <[email protected]> #include <stdio.h> -#include <xcb/xcb.h> #include <xcb/composite.h> +#include <xcb/xcb.h> #include "backend/driver.h" -#include "diagnostic.h" +#include "common.h" #include "config.h" +#include "diagnostic.h" #include "picom.h" -#include "common.h" void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) { - printf("**Version:** " COMPTON_VERSION "\n"); + printf("**Version:** " PICOM_VERSION "\n"); //printf("**CFLAGS:** %s\n", "??"); printf("\n### Extensions:\n\n"); printf("* Shape: %s\n", ps->shape_exists ? "Yes" : "No"); diff --git a/src/event.c b/src/event.c index 5e4017f..beec195 100644 --- a/src/event.c +++ b/src/event.c @@ -508,7 +508,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t queue_redraw(ps); } - // If _NET_WM_OPACITY changes + // If _NET_WM_WINDOW_OPACITY changes if (ev->atom == ps->atoms->a_NET_WM_WINDOW_OPACITY) { auto w = find_managed_win(ps, ev->window) ?: find_toplevel(ps, ev->window); if (w) { diff --git a/src/kernel.c b/src/kernel.c index 5151045..cbb5cd1 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -90,15 +90,20 @@ conv *gaussian_kernel(double r, int size) { /// Estimate the element of the sum of the first row in a gaussian kernel with standard /// deviation `r` and size `size`, static inline double estimate_first_row_sum(double size, double r) { + // `factor` is integral of gaussian from -size to size double factor = erf(size / r / sqrt(2)); + // `a` is gaussian at (size, 0) double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r; + // The sum of the whole kernel is normalized to 1, i.e. each element is divided by + // factor sqaured. So the sum of the first row is a * factor / factor^2 = a / + // factor return a / factor; } -/// Pick a suitable gaussian kernel radius for a given kernel size. The returned radius -/// is the maximum possible radius (<= size*2) that satisfies no sum of the rows in -/// the kernel are less than `row_limit` (up to certain precision). -static inline double gaussian_kernel_std_for_size(int size, double row_limit) { +/// Pick a suitable gaussian kernel standard deviation for a given kernel size. The +/// returned radius is the maximum possible radius (<= size*2) that satisfies no sum of +/// the rows in the kernel are less than `row_limit` (up to certain precision). +double gaussian_kernel_std_for_size(double size, double row_limit) { assert(size > 0); if (row_limit >= 1.0 / 2.0 / size) { return size * 2; @@ -121,14 +126,14 @@ static inline double gaussian_kernel_std_for_size(int size, double row_limit) { /// transparent, so the transition from shadow to the background is smooth. /// /// @param[in] shadow_radius the radius of the shadow -conv *gaussian_kernel_autodetect_deviation(int shadow_radius) { +conv *gaussian_kernel_autodetect_deviation(double shadow_radius) { assert(shadow_radius >= 0); - int size = shadow_radius * 2 + 1; + int size = (int)(shadow_radius * 2 + 1); if (shadow_radius == 0) { return gaussian_kernel(0, size); } - double std = gaussian_kernel_std_for_size(shadow_radius, 1.0 / 256.0); + double std = gaussian_kernel_std_for_size(shadow_radius, 0.5 / 256.0); return gaussian_kernel(std, size); } diff --git a/src/kernel.h b/src/kernel.h index 251d127..d1dd2ee 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -22,12 +22,15 @@ double attr_pure sum_kernel_normalized(const conv *map, int x, int y, int width, /// `size`. conv *gaussian_kernel(double r, int size); +/// Estimate the best standard deviation for a give kernel size. +double gaussian_kernel_std_for_size(double size, double row_limit); + /// Create a gaussian kernel with auto detected standard deviation. The choosen standard /// deviation tries to make sure the outer most pixels of the shadow are completely /// transparent. /// /// @param[in] shadow_radius the radius of the shadow -conv *gaussian_kernel_autodetect_deviation(int shadow_radius); +conv *gaussian_kernel_autodetect_deviation(double shadow_radius); /// preprocess kernels to make shadow generation faster /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive @@ -340,7 +340,7 @@ static void gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) { auto g = (struct gl_string_marker_logger *)tgt; // strip newlines at the end of the string - while (len > 0 && str[len-1] == '\n') { + while (len > 0 && str[len - 1] == '\n') { len--; } g->gl_string_marker((GLsizei)len, str); diff --git a/src/meson.build b/src/meson.build index 0a882f9..60d83a8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -59,7 +59,7 @@ endif if get_option('opengl') cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES'] - deps += [dependency('gl', required: true)] + deps += [dependency('gl', required: true), dependency('egl', required: true)] srcs += [ 'opengl.c' ] endif diff --git a/src/options.c b/src/options.c index 785adb4..c102042 100644 --- a/src/options.c +++ b/src/options.c @@ -7,6 +7,8 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/ioctl.h> +#include <termios.h> #include <unistd.h> #include <xcb/render.h> // for xcb_render_fixed_t, XXX @@ -20,524 +22,301 @@ #pragma GCC diagnostic error "-Wunused-parameter" +struct picom_option { + const char *long_name; + int has_arg; + int val; + const char *arg_name; + const char *help; +}; + +// clang-format off +static const struct option *longopts = NULL; +static const struct picom_option picom_options[] = { +#ifdef CONFIG_LIBCONFIG + {"config" , required_argument, 256, NULL , "Path to the configuration file."}, +#endif + {"help" , no_argument , 'h', NULL , "Print this help message and exit."}, + {"shadow-radius" , required_argument, 'r', NULL , "The blur radius for shadows. (default 12)"}, + {"shadow-opacity" , required_argument, 'o', NULL , "The translucency for shadows. (default .75)"}, + {"shadow-offset-x" , required_argument, 'l', NULL , "The left offset for shadows. (default -15)"}, + {"shadow-offset-y" , required_argument, 't', NULL , "The top offset for shadows. (default -15)"}, + {"fade-in-step" , required_argument, 'I', NULL , "Opacity change between steps while fading in. (default 0.028)"}, + {"fade-out-step" , required_argument, 'O', NULL , "Opacity change between steps while fading out. (default 0.03)"}, + {"fade-delta" , required_argument, 'D', NULL , "The time between steps in a fade in milliseconds. (default 10)"}, + {"menu-opacity" , required_argument, 'm', NULL , "The opacity for menus. (default 1.0)"}, + {"shadow" , no_argument , 'c', NULL , "Enabled client-side shadows on windows."}, + {"clear-shadow" , no_argument , 'z', NULL , "Don't dreaw shadow behind the window."}, + {"fading" , no_argument , 'f', NULL , "Fade windows in/out when opening/closing and when opacity changes, " + "unless --no-fading-openclose is used."}, + {"inactive-opacity" , required_argument, 'i', NULL , "Opacity of inactive windows. (0.1 - 1.0)"}, + {"frame-opacity" , required_argument, 'e', NULL , "Opacity of window titlebars and borders. (0.1 - 1.0)"}, + {"daemon" , no_argument , 'b', NULL , "Daemonize process."}, + {"shadow-red" , required_argument, 257, NULL , "Red color value of shadow (0.0 - 1.0, defaults to 0)."}, + {"shadow-green" , required_argument, 258, NULL , "Green color value of shadow (0.0 - 1.0, defaults to 0)."}, + {"shadow-blue" , required_argument, 259, NULL , "Blue color value of shadow (0.0 - 1.0, defaults to 0)."}, + {"inactive-opacity-override" , no_argument , 260, NULL , "Inactive opacity set by -i overrides value of _NET_WM_WINDOW_OPACITY."}, + {"inactive-dim" , required_argument, 261, NULL , "Dim inactive windows. (0.0 - 1.0, defaults to 0)"}, + {"mark-wmwin-focused" , no_argument , 262, NULL , "Try to detect WM windows and mark them as active."}, + {"shadow-exclude" , required_argument, 263, NULL , "Exclude conditions for shadows."}, + {"mark-ovredir-focused" , no_argument , 264, NULL , "Mark windows that have no WM frame as active."}, + {"no-fading-openclose" , no_argument , 265, NULL , "Do not fade on window open/close."}, + {"shadow-ignore-shaped" , no_argument , 266, NULL , "Do not paint shadows on shaped windows. (Deprecated, use --shadow-exclude " + "\'bounding_shaped\' or --shadow-exclude \'bounding_shaped && " + "!rounded_corners\' instead.)"}, + {"detect-rounded-corners" , no_argument , 267, NULL , "Try to detect windows with rounded corners and don't consider them shaped " + "windows. Affects --shadow-ignore-shaped, --unredir-if-possible, and " + "possibly others. You need to turn this on manually if you want to match " + "against rounded_corners in conditions."}, + {"detect-client-opacity" , no_argument , 268, NULL , "Detect _NET_WM_WINDOW_OPACITY on client windows, useful for window " + "managers not passing _NET_WM_WINDOW_OPACITY of client windows to frame"}, + {"refresh-rate" , required_argument, 269, NULL , NULL}, + {"vsync" , optional_argument, 270, NULL , "Enable VSync"}, + {"sw-opti" , no_argument , 274, NULL , NULL}, + {"vsync-aggressive" , no_argument , 275, NULL , NULL}, + {"use-ewmh-active-win" , no_argument , 276, NULL , "Use _NET_WM_ACTIVE_WINDOW on the root window to determine which window is " + "focused instead of using FocusIn/Out events"}, + {"respect-prop-shadow" , no_argument , 277, NULL , NULL}, + {"unredir-if-possible" , no_argument , 278, NULL , "Unredirect all windows if a full-screen opaque window is detected, to " + "maximize performance for full-screen applications."}, + {"focus-exclude" , required_argument, 279, "COND" , "Specify a list of conditions of windows that should always be considered focused."}, + {"inactive-dim-fixed" , no_argument , 280, NULL , "Use fixed inactive dim value."}, + {"detect-transient" , no_argument , 281, NULL , "Use WM_TRANSIENT_FOR to group windows, and consider windows in the same " + "group focused at the same time."}, + {"detect-client-leader" , no_argument , 282, NULL , "Use WM_CLIENT_LEADER to group windows, and consider windows in the same group " + "focused at the same time. This usually means windows from the same application " + "will be considered focused or unfocused at the same time. WM_TRANSIENT_FOR has " + "higher priority if --detect-transient is enabled, too."}, + {"blur-background" , no_argument , 283, NULL , "Blur background of semi-transparent / ARGB windows. May impact performance"}, + {"blur-background-frame" , no_argument , 284, NULL , "Blur background of windows when the window frame is not opaque. Implies " + "--blur-background."}, + {"blur-background-fixed" , no_argument , 285, NULL , "Use fixed blur strength instead of adjusting according to window opacity."}, +#ifdef CONFIG_DBUS + {"dbus" , no_argument , 286, NULL , "Enable remote control via D-Bus. See the D-BUS API section in the man page " + "for more details."}, +#endif + {"logpath" , required_argument, 287, NULL , NULL}, + {"invert-color-include" , required_argument, 288, "COND" , "Specify a list of conditions of windows that should be painted with " + "inverted color."}, + {"opengl" , no_argument , 289, NULL , NULL}, + {"backend" , required_argument, 290, NULL , "Backend. Possible values are: xrender" +#ifdef CONFIG_OPENGL + ", glx" +#endif + }, + {"glx-no-stencil" , no_argument , 291, NULL , NULL}, + {"benchmark" , required_argument, 293, NULL , "Benchmark mode. Repeatedly paint until reaching the specified cycles."}, + {"benchmark-wid" , required_argument, 294, NULL , "Specify window ID to repaint in benchmark mode. If omitted or is 0, the whole" + " screen is repainted."}, + {"blur-background-exclude" , required_argument, 296, "COND" , "Exclude conditions for background blur."}, + {"active-opacity" , required_argument, 297, NULL , "Default opacity for active windows. (0.0 - 1.0)"}, + {"glx-no-rebind-pixmap" , no_argument , 298, NULL , NULL}, + {"glx-swap-method" , required_argument, 299, NULL , NULL}, + {"fade-exclude" , required_argument, 300, "COND" , "Exclude conditions for fading."}, + {"blur-kern" , required_argument, 301, NULL , "Specify the blur convolution kernel, see man page for more details"}, + {"resize-damage" , required_argument, 302, NULL , NULL}, // only used by legacy backends + {"glx-use-gpushader4" , no_argument , 303, NULL , NULL}, + {"opacity-rule" , required_argument, 304, "OPACITY:COND", "Specify a list of opacity rules, see man page for more details"}, + {"shadow-exclude-reg" , required_argument, 305, NULL , NULL}, + {"paint-exclude" , required_argument, 306, NULL , NULL}, + {"xinerama-shadow-crop" , no_argument , 307, NULL , "Crop shadow of a window fully on a particular Xinerama screen to the screen."}, + {"unredir-if-possible-exclude" , required_argument, 308, "COND" , "Conditions of windows that shouldn't be considered full-screen for " + "unredirecting screen."}, + {"unredir-if-possible-delay" , required_argument, 309, NULL, "Delay before unredirecting the window, in milliseconds. Defaults to 0."}, + {"write-pid-path" , required_argument, 310, "PATH" , "Write process ID to a file."}, + {"vsync-use-glfinish" , no_argument , 311, NULL , NULL}, + {"xrender-sync-fence" , no_argument , 313, NULL , "Additionally use X Sync fence to sync clients' draw calls. Needed on " + "nvidia-drivers with GLX backend for some users."}, + {"show-all-xerrors" , no_argument , 314, NULL , NULL}, + {"no-fading-destroyed-argb" , no_argument , 315, NULL , "Do not fade destroyed ARGB windows with WM frame. Workaround bugs in Openbox, " + "Fluxbox, etc."}, + {"force-win-blend" , no_argument , 316, NULL , "Force all windows to be painted with blending. Useful if you have a custom " + "shader that could turn opaque pixels transparent."}, + {"glx-fshader-win" , required_argument, 317, NULL , NULL}, + {"version" , no_argument , 318, NULL , "Print version number and exit."}, + {"no-x-selection" , no_argument , 319, NULL , NULL}, + {"log-level" , required_argument, 321, NULL , "Log level, possible values are: trace, debug, info, warn, error"}, + {"log-file" , required_argument, 322, NULL , "Path to the log file."}, + {"use-damage" , no_argument , 323, NULL , "Render only the damaged (changed) part of the screen"}, + {"no-use-damage" , no_argument , 324, NULL , "Disable the use of damage information. This cause the whole screen to be" + "redrawn every time, instead of the part of the screen that has actually " + "changed. Potentially degrades the performance, but might fix some artifacts."}, + {"no-vsync" , no_argument , 325, NULL , "Disable VSync"}, + {"max-brightness" , required_argument, 326, NULL , "Dims windows which average brightness is above this threshold. Requires " + "--no-use-damage. (default: 1.0, meaning no dimming)"}, + {"transparent-clipping" , no_argument , 327, NULL , "Make transparent windows clip other windows like non-transparent windows do, " + "instead of blending on top of them"}, + {"transparent-clipping-exclude", required_argument, 338, "COND" , "Specify a list of conditions of windows that should never have " + "transparent clipping applied. Useful for screenshot tools, where you " + "need to be able to see through transparent parts of the window."}, + {"blur-method" , required_argument, 328, NULL , "The algorithm used for background bluring. Available choices are: 'none' to " + "disable, 'gaussian', 'box' or 'kernel' for custom convolution blur with " + "--blur-kern. Note: 'gaussian' and 'box' is not supported by --legacy-backends."}, + {"blur-size" , required_argument, 329, NULL , "The radius of the blur kernel for 'box' and 'gaussian' blur method."}, + {"blur-deviation" , required_argument, 330, NULL , "The standard deviation for the 'gaussian' blur method."}, + {"blur-strength" , required_argument, 331, NULL , "The strength level of the 'dual_kawase' blur method."}, + {"shadow-color" , required_argument, 332, NULL , "Color of shadow, as a hex RGB string (defaults to #000000)"}, + {"corner-radius" , required_argument, 333, NULL , "Sets the radius of rounded window corners. When > 0, the compositor will " + "round the corners of windows. (defaults to 0)."}, + {"rounded-corners-exclude" , required_argument, 334, "COND" , "Exclude conditions for rounded corners."}, + {"clip-shadow-above" , required_argument, 335, NULL , "Specify a list of conditions of windows to not paint a shadow over, such " + "as a dock window."}, + {"window-shader-fg" , required_argument, 336, "PATH" , "Specify GLSL fragment shader path for rendering window contents. Does not" + " work when `--legacy-backends` is enabled. See man page for more details."}, + {"window-shader-fg-rule" , required_argument, 337, "PATH:COND" , "Specify GLSL fragment shader path for rendering window contents using " + "patterns. Pattern should be in the format of SHADER_PATH:PATTERN, " + "similar to --opacity-rule. SHADER_PATH can be \"default\", in which case " + "the default shader will be used. Does not work when --legacy-backends is " + "enabled. See man page for more details"}, + {"legacy-backends" , no_argument , 733, NULL , "Use deprecated version of the backends."}, + {"monitor-repaint" , no_argument , 800, NULL , "Highlight the updated area of the screen. For debugging."}, + {"diagnostics" , no_argument , 801, NULL , "Print diagnostic information"}, + {"debug-mode" , no_argument , 802, NULL , "Render into a separate window, and don't take over the screen. Useful when " + "you want to attach a debugger to picom"}, + {"no-ewmh-fullscreen" , no_argument , 803, NULL , "Do not use EWMH to detect fullscreen windows. Reverts to checking if a " + "window is fullscreen based only on its size and coordinates."}, + {"animations", no_argument, 804, NULL, "Toggles Animations"}, + {"animation-stiffness", required_argument, 805, NULL, "stiffness"}, + {"animation-dampening", required_argument, 806, NULL, "dampening"}, + {"animation-window-mass", required_argument, 807, NULL, "window mass"}, + {"animation-clamping", no_argument, 808, NULL, "clamping"}, + {"animation-for-open-window", required_argument, 809, NULL, "open window animation"}, + {"animation-for-transient-window", required_argument, 810, NULL, "transient window animation"}, + {"animation-for-unmap-window", required_argument, 811, NULL, "unmap window animation"}, + {"corners-rule", required_argument, NULL, 812, "rounded corner rules"}, + {"blur-rule", required_argument, NULL, 813, "blur rules"}, + {"animation-open-exclude", required_argument, NULL, 814, "animation open exclude list"}, + {"animation-unmap-exclude", required_argument, NULL, 815, "animation unmap exclude list"}, +}; +// clang-format on + +static void setup_longopts(void) { + auto opts = ccalloc(ARR_SIZE(picom_options) + 1, struct option); + for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { + opts[i].name = picom_options[i].long_name; + opts[i].has_arg = picom_options[i].has_arg; + opts[i].flag = NULL; + opts[i].val = picom_options[i].val; + } + longopts = opts; +} + +void print_help(const char *help, size_t indent, size_t curr_indent, size_t line_wrap, + FILE *f) { + if (curr_indent > indent) { + fputs("\n", f); + curr_indent = 0; + } + + if (line_wrap - indent <= 1) { + line_wrap = indent + 2; + } + + size_t pos = 0; + size_t len = strlen(help); + while (pos < len) { + fprintf(f, "%*s", (int)(indent - curr_indent), ""); + curr_indent = 0; + size_t towrite = line_wrap - indent; + while (help[pos] == ' ') { + pos++; + } + if (pos + towrite > len) { + towrite = len - pos; + fwrite(help + pos, 1, towrite, f); + } else { + auto space_break = towrite; + while (space_break > 0 && help[pos + space_break - 1] != ' ') { + space_break--; + } + + bool print_hyphen = false; + if (space_break == 0) { + print_hyphen = true; + towrite--; + } else { + towrite = space_break; + } + + fwrite(help + pos, 1, towrite, f); + + if (print_hyphen) { + fputs("-", f); + } + } + + fputs("\n", f); + pos += towrite; + } +} + /** * Print usage text. */ static void usage(const char *argv0, int ret) { -#define WARNING_DISABLED " (DISABLED AT COMPILE TIME)" - static const char *usage_text = - "picom-allusive (" COMPTON_VERSION ")\n" - "Please report bugs to https://github.com/allusive-dev/picom-allusive\n\n" - "usage: %s [options]\n" - "Options:\n" - "\n" - "-r radius\n" - " The blur radius for shadows. (default 12)\n" - "\n" - "-o opacity\n" - " The translucency for shadows. (default .75)\n" - "\n" - "-l left-offset\n" - " The left offset for shadows. (default -15)\n" - "\n" - "-t top-offset\n" - " The top offset for shadows. (default -15)\n" - "\n" - "-I fade-in-step\n" - " Opacity change between steps while fading in. (default 0.028)\n" - "\n" - "-O fade-out-step\n" - " Opacity change between steps while fading out. (default 0.03)\n" - "\n" - "-D fade-delta-time\n" - " The time between steps in a fade in milliseconds. (default 10)\n" - "\n" - "-m opacity\n" - " The opacity for menus. (default 1.0)\n" - "\n" - "-c\n" - " Enabled client-side shadows on windows.\n" - "\n" - "-C\n" - " Avoid drawing shadows on dock/panel windows.\n" - "\n" - "-z\n" - " Zero the part of the shadow's mask behind the window.\n" - "\n" - "-f\n" - " Fade windows in/out when opening/closing and when opacity\n" - " changes, unless --no-fading-openclose is used.\n" - "\n" - "-F\n" - " Equals to -f. Deprecated.\n" - "\n" - "--animations\n" - " Run animations for window geometry changes (movement and scaling).\n" - "\n" - "--animation-for-open-window\n" - " Which animation to run when opening a window.\n" - " Must be one of `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " (default: none).\n" - "\n" - "--animation-for-transient-window\n" - " Which animation to run when opening a transient window.\n" - " Must be one of `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " (default: none).\n" - "\n" - "--animation-for-unmap-window\n" - " Which animation to run when hiding (e.g. minimize) a window.\n" - " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " `slide-in`, `slide-out`\n" - " (default: auto).\n" - "\n" - "--animation-for-workspace-switch-in\n" - " Which animation to run on switching workspace for windows\n" - " comming into view.\n" - " IMPORTANT: window manager must set _NET_CURRENT_DESKTOP\n" - " before doing the hide/show of windows\n" - " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " `slide-in`, `slide-out`\n" - " (default: auto).\n" - "\n" - "--animation-for-workspace-switch-out\n" - " Which animation to run on switching workspace for windows\n" - " going out of view.\n" - " IMPORTANT: window manager must set _NET_CURRENT_DESKTOP\n" - " before doing the hide/show of windows\n" - " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " `slide-in`, `slide-out`\n" - " (default: auto).\n" - "\n" - "--animation-stiffness\n" - " Stiffness (a.k.a. tension) parameter for animation (default: 200.0).\n" - "\n" - "--animation-dampening\n" - " Dampening (a.k.a. friction) parameter for animation (default: 25.0).\n" - "\n" - "--animation-window-mass\n" - " Mass parameter for animation (default: 1.0).\n" - "\n" - "--animation-delta\n" - " The time between steps in animation, in milliseconds. (> 0, defaults to 10).\n" - "\n" - "--animation-force-steps\n" - " Force animations to go step by step even if cpu usage is high \n" - " (default: false)\n" - "\n" - "--animation-clamping\n" - " Whether to clamp animations (default: true)\n" - "\n" - "-i opacity\n" - " Opacity of inactive windows. (0.1 - 1.0)\n" - "\n" - "-e opacity\n" - " Opacity of window titlebars and borders. (0.1 - 1.0)\n" - "\n" - "-G\n" - " Don't draw shadows on DND windows\n" - "\n" - "-b\n" - " Daemonize process.\n" - "\n" - "--show-all-xerrors\n" - " Show all X errors (for debugging).\n" - "\n" - "--config path\n" - " Look for configuration file at the path. Use /dev/null to avoid\n" - " loading configuration file." -#ifndef CONFIG_LIBCONFIG - WARNING_DISABLED -#endif - "\n\n" - "--write-pid-path path\n" - " Write process ID to a file.\n" - "\n" - "--shadow-color color\n" - " Color of shadow, as a hex RGB string (defaults to #000000)\n" - "\n" - "--shadow-red value\n" - " Red color value of shadow (0.0 - 1.0, defaults to 0).\n" - "\n" - "--shadow-green value\n" - " Green color value of shadow (0.0 - 1.0, defaults to 0).\n" - "\n" - "--shadow-blue value\n" - " Blue color value of shadow (0.0 - 1.0, defaults to 0).\n" - "\n" - "--inactive-opacity-override\n" - " Inactive opacity set by -i overrides value of _NET_WM_OPACITY.\n" - "\n" - "--inactive-dim value\n" - " Dim inactive windows. (0.0 - 1.0, defaults to 0)\n" - "\n" - "--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" - "--shadow-exclude condition\n" - " Exclude conditions for shadows.\n" - "\n" - "--fade-exclude condition\n" - " Exclude conditions for fading.\n" - "\n" - "--mark-ovredir-focused\n" - " Mark windows that have no WM frame as active.\n" - "\n" - "--no-fading-openclose\n" - " Do not fade on window open/close.\n" - "\n" - "--no-fading-destroyed-argb\n" - " Do not fade destroyed ARGB windows with WM frame. Workaround of bugs\n" - " in Openbox, Fluxbox, etc.\n" - "\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" - "\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" - "\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" - "\n" - "--refresh-rate val\n" - " Specify refresh rate of the screen. If not specified or 0, we\n" - " will try detecting this with X RandR extension.\n" - "\n" - "--vsync\n" - " Enable VSync\n" - "\n" - "--paint-on-overlay\n" - " Painting on X Composite overlay window.\n" - "\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" - "\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" - "\n" - "--unredir-if-possible-delay ms\n" - " Delay before unredirecting the window, in milliseconds.\n" - " Defaults to 0.\n" - "\n" - "--unredir-if-possible-exclude condition\n" - " Conditions of windows that shouldn't be considered full-screen\n" - " for unredirecting screen.\n" - "\n" - "--focus-exclude condition\n" - " Specify a list of conditions of windows that should always be\n" - " considered focused.\n" - "\n" - "--inactive-dim-fixed\n" - " Use fixed inactive dim value.\n" - "\n" - "--max-brightness\n" - " Dims windows which average brightness is above this threshold.\n" - " Requires --no-use-damage.\n" - " Default: 1.0 or no dimming.\n" - "\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" - "\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" - "\n" - "--blur-method\n" - " The algorithm used for background bluring. Available choices are:\n" - " 'none' to disable, 'gaussian', 'box' or 'kernel' for custom\n" - " convolution blur with --blur-kern.\n" - " Note: 'gaussian' and 'box' require --experimental-backends.\n" - "\n" - "--blur-size\n" - " The radius of the blur kernel for 'box' and 'gaussian' blur method.\n" - "\n" - "--blur-deviation\n" - " The standard deviation for the 'gaussian' blur method.\n" - "\n" - "--blur-strength\n" - " The strength level of the 'dual_kawase' blur method.\n" - "\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" - "\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" - "--blur-background-fixed\n" - " Use fixed blur strength instead of adjusting according to window\n" - " opacity.\n" - "\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" - "\n" - "--blur-background-exclude condition\n" - " Exclude conditions for background blur.\n" - "\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" - "\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" - "\n" - "--opacity-rule opacity:condition\n" - " Specify a list of opacity rules, in the format \"PERCENT:PATTERN\",\n" - " like \'50:name *= \"Firefox\"'. picom-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" - "\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" - "\n" - "--clip-shadow-above condition\n" - " Specify a list of conditions of windows to not paint a shadow over,\n" - " such as a dock window.\n" - "\n" - "--xinerama-shadow-crop\n" - " Crop shadow of a window fully on a particular Xinerama screen to the\n" - " screen.\n" - "\n" - "--backend backend\n" - " Choose backend. Possible choices are xrender, glx, and\n" - " xr_glx_hybrid." -#ifndef CONFIG_OPENGL - " (GLX BACKENDS DISABLED AT COMPILE TIME)" -#endif - "\n\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" - "\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" - "\n" - "--no-use-damage\n" - " Disable the use of damage information. This cause the whole screen to\n" - " be redrawn everytime, instead of the part of the screen that has\n" - " actually changed. Potentially degrades the performance, but might fix\n" - " some artifacts.\n" - "\n" - "--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.\n" - "\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" - "\n" - "--dbus\n" - " Enable remote control via D-Bus. See the D-BUS API section in the\n" - " man page for more details." -#ifndef CONFIG_DBUS - WARNING_DISABLED -#endif - "\n\n" - "--benchmark cycles\n" - " Benchmark mode. Repeatedly paint until reaching the specified cycles.\n" - "\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" - "\n" - "--monitor-repaint\n" - " Highlight the updated area of the screen. For debugging the xrender\n" - " backend only.\n" - "\n" - "--debug-mode\n" - " Render into a separate window, and don't take over the screen. Useful\n" - " when you want to attach a debugger to picom\n" - "\n" - "--no-ewmh-fullscreen\n" - " Do not use EWMH to detect fullscreen windows. Reverts to checking\n" - " if a window is fullscreen based only on its size and coordinates.\n" - "\n" - "--transparent-clipping\n" - " Make transparent windows clip other windows like non-transparent windows\n" - " do, instead of blending on top of them\n"; FILE *f = (ret ? stderr : stdout); - fprintf(f, usage_text, argv0); -#undef WARNING_DISABLED + fprintf(f, "picom (%s)\n", PICOM_VERSION); + fprintf(f, "Standalone X11 compositor\n"); + fprintf(f, "Please report bugs to https://github.com/yshui/picom\n\n"); + + fprintf(f, "Usage: %s [OPTION]...\n\n", argv0); + fprintf(f, "OPTIONS:\n"); + + int line_wrap = 80; + struct winsize window_size = {0}; + if (ioctl(fileno(f), TIOCGWINSZ, &window_size) != -1) { + line_wrap = window_size.ws_col; + } + + size_t help_indent = 0; + for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { + if (picom_options[i].help == NULL) { + // Hide options with no help message. + continue; + } + auto option_len = strlen(picom_options[i].long_name) + 2 + 4; + if (picom_options[i].arg_name) { + option_len += strlen(picom_options[i].arg_name) + 1; + } + if (option_len > help_indent && option_len < 30) { + help_indent = option_len; + } + } + help_indent += 6; + + for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { + if (picom_options[i].help == NULL) { + continue; + } + size_t option_len = 8; + fprintf(f, " "); + if ((picom_options[i].val > 'a' && picom_options[i].val < 'z') || + (picom_options[i].val > 'A' && picom_options[i].val < 'Z')) { + fprintf(f, "-%c, ", picom_options[i].val); + } else { + fprintf(f, " "); + } + fprintf(f, "--%s", picom_options[i].long_name); + option_len += strlen(picom_options[i].long_name) + 2; + if (picom_options[i].arg_name) { + fprintf(f, "=%s", picom_options[i].arg_name); + option_len += strlen(picom_options[i].arg_name) + 1; + } + fprintf(f, " "); + option_len += 2; + print_help(picom_options[i].help, help_indent, option_len, + (size_t)line_wrap, f); + } } -static const char *shortopts = "D:I:O:d:r:o:m:l:t:i:e:hscnfFCaSzGb"; -static const 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", optional_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}, - {"benchmark", required_argument, NULL, 293}, - {"benchmark-wid", required_argument, NULL, 294}, - {"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}, - {"log-level", required_argument, NULL, 321}, - {"log-file", required_argument, NULL, 322}, - {"use-damage", no_argument, NULL, 323}, - {"no-use-damage", no_argument, NULL, 324}, - {"no-vsync", no_argument, NULL, 325}, - {"max-brightness", required_argument, NULL, 326}, - {"transparent-clipping", no_argument, NULL, 327}, - {"blur-method", required_argument, NULL, 328}, - {"blur-size", required_argument, NULL, 329}, - {"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}, - {"clip-shadow-above", required_argument, NULL, 335}, - {"experimental-backends", no_argument, NULL, 733}, - {"monitor-repaint", no_argument, NULL, 800}, - {"diagnostics", no_argument, NULL, 801}, - {"debug-mode", no_argument, NULL, 802}, - {"no-ewmh-fullscreen", no_argument, NULL, 803}, - {"animations", no_argument, NULL, 804}, - {"animation-stiffness", required_argument, NULL, 805}, - {"animation-dampening", required_argument, NULL, 806}, - {"animation-window-mass", required_argument, NULL, 807}, - {"animation-clamping", no_argument, NULL, 808}, - {"animation-for-open-window", required_argument, NULL, 809}, - {"animation-for-transient-window", required_argument, NULL, 810}, - {"animation-open-exclude", required_argument, NULL, 830}, - {"animation-unmap-exclude", required_argument, NULL, 831}, - {"corners-rule", required_argument, NULL, 840}, - // Must terminate with a NULL entry - {NULL, 0, NULL, 0}, -}; +static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hscnfFCazGb"; /// Get config options that are needed to parse the rest of the options /// Return true if we should quit bool get_early_config(int argc, char *const *argv, char **config_file, bool *all_xerrors, bool *fork, int *exit_code) { + setup_longopts(); + int o = 0, longopt_idx = -1; // Pre-parse the commandline arguments to check for --config and invalid @@ -556,21 +335,11 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all } else if (o == 'b') { *fork = true; - } else if (o == 'd') { - log_error("-d is removed, please use the DISPLAY " - "environment variable"); - goto err; } else if (o == 314) { *all_xerrors = true; } else if (o == 318) { - printf("%s\n", COMPTON_VERSION); + printf("%s\n", PICOM_VERSION); return true; - } else if (o == 'S') { - log_error("-S is no longer available"); - goto err; - } else if (o == 320) { - log_error("--no-name-pixmap is no longer available"); - goto err; } else if (o == '?' || o == ':') { usage(argv[0], 1); goto err; @@ -639,9 +408,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // so assert(false) here assert(false); break; - case 'd': case 'b': - case 'S': case 314: case 320: // These options are handled by get_early_config() @@ -650,15 +417,10 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, case 'I': opt->fade_in_step = normalize_d(atof(optarg)); break; case 'O': opt->fade_out_step = normalize_d(atof(optarg)); break; case 'c': shadow_enable = true; break; - case 'C': - log_error("Option `--no-dock-shadow`/`-C` has been removed. Please" - " use the wintype option `shadow` of `dock` instead."); - failed = true; break; - case 'G': - log_error("Option `--no-dnd-shadow`/`-G` has been removed. Please " - "use the wintype option `shadow` of `dnd` instead."); - failed = true; break; case 'm':; + log_warn("--menu-opacity is deprecated, and will be removed." + "Please use the wintype option `opacity` of `popup_menu`" + "and `dropdown_menu` instead."); double tmp; tmp = normalize_d(atof(optarg)); winopt_mask[WINTYPE_DROPDOWN_MENU].opacity = true; @@ -730,35 +492,30 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, P_CASEBOOL(266, shadow_ignore_shaped); P_CASEBOOL(267, detect_rounded_corners); P_CASEBOOL(268, detect_client_opacity); - P_CASEINT(269, refresh_rate); + case 269: + log_warn("--refresh-rate has been deprecated, please remove it from" + "your command line options"); + break; case 270: if (optarg) { - opt->vsync = parse_vsync(optarg); - log_warn("--vsync doesn't take argument anymore. \"%s\" " - "is interpreted as \"%s\" for compatibility, but " - "this will stop working soon", - optarg, opt->vsync ? "true" : "false"); + bool parsed_vsync = parse_vsync(optarg); + log_error("--vsync doesn't take argument anymore. \"%s\" " + "should be changed to \"%s\"", + optarg, parsed_vsync ? "true" : "false"); + failed = true; } else { opt->vsync = true; } break; - case 271: - // --alpha-step - log_error("--alpha-step has been removed, we now tries to " - "make use of all alpha values"); - failed = true; break; - case 272: - log_error("--dbe has been removed"); - failed = true; break; - case 273: - log_error("--paint-on-overlay has been removed, the feature is enabled " - "whenever possible"); - failed = true; break; - P_CASEBOOL(274, sw_opti); + case 274: + log_warn("--sw-opti has been deprecated, please remove it from the " + "command line options"); + break; case 275: // --vsync-aggressive - log_warn("--vsync-aggressive has been deprecated, please remove it" + log_error("--vsync-aggressive has been removed, please remove it" " from the command line options"); + failed = true; break; P_CASEBOOL(276, use_ewmh_active_win); case 277: @@ -831,14 +588,14 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, if (strcmp(optarg, "undefined") != 0 && tmpval != 0) { // If not undefined, we will use damage and buffer-age to // limit the rendering area. - opt->use_damage = true; should_remove = false; } - log_warn("--glx-swap-method has been deprecated, your setting " + log_error("--glx-swap-method has been removed, your setting " "\"%s\" should be %s.", optarg, !should_remove ? "replaced by `--use-damage`" : "removed"); + failed = true; break; } case 300: @@ -856,8 +613,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, P_CASEINT(302, resize_damage); case 303: // --glx-use-gpushader4 - log_warn("--glx-use-gpushader4 is deprecated since v6." + log_error("--glx-use-gpushader4 has been removed." " Please remove it from command line options."); + failed = true; break; case 304: // --opacity-rule @@ -890,16 +648,35 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, } break; P_CASEBOOL(311, vsync_use_glfinish); - case 312: - // --xrender-sync - log_error("Please use --xrender-sync-fence instead of --xrender-sync"); - failed = true; break; P_CASEBOOL(313, xrender_sync_fence); P_CASEBOOL(315, no_fading_destroyed_argb); P_CASEBOOL(316, force_win_blend); case 317: opt->glx_fshader_win_str = strdup(optarg); break; + case 336: { + // --window-shader-fg + scoped_charp cwd = getcwd(NULL, 0); + opt->window_shader_fg = + locate_auxiliary_file("shaders", optarg, cwd); + if (!opt->window_shader_fg) { + exit(1); + } + break; + } + case 337: { + // --window-shader-fg-rule + scoped_charp cwd = getcwd(NULL, 0); + if (!parse_rule_window_shader(&opt->window_shader_fg_rules, optarg, cwd)) { + exit(1); + } + break; + } + case 338: { + // --transparent-clipping-exclude + condlst_add(&opt->transparent_clipping_blacklist, optarg); + break; + } case 321: { enum log_level tmp_level = string_to_log_level(optarg); if (tmp_level == LOG_LEVEL_INVALID) { @@ -911,13 +688,8 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, } P_CASEBOOL(319, no_x_selection); P_CASEBOOL(323, use_damage); - case 324: - opt->use_damage = false; - break; - case 325: - opt->vsync = false; - break; - + case 324: opt->use_damage = false; break; + case 325: opt->vsync = false; break; case 326: opt->max_brightness = atof(optarg); break; @@ -956,9 +728,11 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // --clip-shadow-above condlst_add(&opt->shadow_clip_list, optarg); break; - P_CASEBOOL(733, experimental_backends); + P_CASEBOOL(733, legacy_backends); P_CASEBOOL(800, monitor_repaint); - case 801: opt->print_diagnostics = true; break; + case 801: + opt->print_diagnostics = true; + break; P_CASEBOOL(802, debug_mode); P_CASEBOOL(803, no_ewmh_fullscreen); P_CASEBOOL(804, animations); @@ -988,17 +762,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, } break; } - case 830: - condlst_add(&opt->animation_open_blacklist, optarg); - break; - case 831: - condlst_add(&opt->animation_unmap_blacklist, optarg); - break; - case 840: - // --opacity-rule - if (!parse_rule_corners(&opt->corner_rules, optarg)) - exit(1); - break; case 810: { // --animation-for-transient-window enum open_window_animation animation = parse_open_window_animation(optarg); @@ -1009,6 +772,30 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, } break; } + case 811: { + // --animation-for-unmap-window + enum open_window_animation animation = parse_open_window_animation(optarg); + if (animation >= OPEN_WINDOW_ANIMATION_INVALID) { + log_warn("Invalid unmap-window animation %s, ignoring.", optarg); + } else { + opt->animation_for_unmap_window = animation; + } + break; + } + case 812: + // corner rules + if (!parse_rule_corners(&opt->corner_rules, optarg)) + exit(1); + break; + case 813: + condlst_add(&opt->blur_rules, optarg); + break; + case 814: + condlst_add(&opt->animation_open_blacklist, optarg); + break; + case 815: + condlst_add(&opt->animation_unmap_blacklist, optarg); + break; default: usage(argv[0], 1); break; #undef P_CASEBOOL } @@ -1028,29 +815,56 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, return false; } - if (opt->monitor_repaint && opt->backend != BKEND_XRENDER && - !opt->experimental_backends) { + if (opt->monitor_repaint && opt->backend != BKEND_XRENDER && opt->legacy_backends) { log_warn("--monitor-repaint has no effect when backend is not xrender"); } - if (opt->experimental_backends && !backend_list[opt->backend]) { - log_error("Backend \"%s\" is not available as part of the experimental " + if (opt->backend == BKEND_EGL) { + if (opt->legacy_backends) { + log_error("The egl backend is not supported with " + "--legacy-backends"); + return false; + } + log_warn("The egl backend is still experimental, use with care."); + } + + if (!opt->legacy_backends && !backend_list[opt->backend]) { + log_error("Backend \"%s\" is only available as part of the legacy " "backends.", BACKEND_STRS[opt->backend]); return false; } - if (opt->debug_mode && !opt->experimental_backends) { - log_error("Debug mode only works with the experimental backends."); + if (opt->debug_mode && opt->legacy_backends) { + log_error("Debug mode does not work with the legacy backends."); return false; } - if (opt->transparent_clipping && !opt->experimental_backends) { - log_error("Transparent clipping only works with the experimental " + if (opt->transparent_clipping && opt->legacy_backends) { + log_error("Transparent clipping does not work with the legacy " "backends"); return false; } + if (opt->glx_fshader_win_str && !opt->legacy_backends) { + log_warn("--glx-fshader-win has been replaced by " + "\"--window-shader-fg\" for the new backends."); + } + + if (opt->window_shader_fg || opt->window_shader_fg_rules) { + if (opt->legacy_backends || opt->backend != BKEND_GLX) { + log_warn("The new window shader interface does not work with the " + "legacy glx backend.%s", + (opt->backend == BKEND_GLX) ? " You may want to use " + "\"--glx-fshader-win\" " + "instead on the legacy " + "glx backend." + : ""); + opt->window_shader_fg = NULL; + c2_list_free(&opt->window_shader_fg_rules, free); + } + } + // Range checking and option assignments opt->fade_delta = max2(opt->fade_delta, 1); opt->shadow_radius = max2(opt->shadow_radius, 0); @@ -1060,7 +874,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, opt->inactive_dim = normalize_d(opt->inactive_dim); opt->frame_opacity = normalize_d(opt->frame_opacity); opt->shadow_opacity = normalize_d(opt->shadow_opacity); - opt->refresh_rate = normalize_i_range(opt->refresh_rate, 0, 300); opt->max_brightness = normalize_d(opt->max_brightness); if (opt->max_brightness < 1.0) { @@ -1070,8 +883,8 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, opt->max_brightness = 1.0; } - if (!opt->experimental_backends || opt->backend != BKEND_GLX) { - log_warn("--max-brightness requires the experimental glx " + if (opt->legacy_backends || opt->backend != BKEND_GLX) { + log_warn("--max-brightness requires the new glx " "backend. Falling back to 1.0"); opt->max_brightness = 1.0; } @@ -1114,10 +927,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, "capping to 20."); opt->blur_strength = 20; } - if (!opt->experimental_backends) { + if (opt->legacy_backends) { log_warn("Dual-kawase blur is not implemented by the legacy " - "backends, you must use the `experimental-backends` " - "option."); + "backends."); } } @@ -1130,12 +942,6 @@ 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->experimental_backends) { - log_warn("Rounded corner is only supported on legacy backends, it " - "will be disabled"); - opt->corner_radius = 0; - } - return true; } diff --git a/src/picom.c b/src/picom.c index 24d5ece..980655d 100644 --- a/src/picom.c +++ b/src/picom.c @@ -69,8 +69,6 @@ (session_t *)((char *)__mptr - offsetof(session_t, member)); \ }) -static const long SWOPTI_TOLERANCE = 3000; - static bool must_use redirect_start(session_t *ps); static void unredirect(session_t *ps); @@ -90,6 +88,7 @@ const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender", [BKEND_GLX] = "glx", [BKEND_XR_GLX_HYBRID] = "xr_glx_hybrid", [BKEND_DUMMY] = "dummy", + [BKEND_EGL] = "egl", NULL}; // clang-format on @@ -433,6 +432,14 @@ static void destroy_backend(session_t *ps) { free_paint(ps, &w->paint); } + HASH_ITER2(ps->shaders, shader) { + if (shader->backend_shader != NULL) { + ps->backend_data->ops->destroy_shader(ps->backend_data, + shader->backend_shader); + shader->backend_shader = NULL; + } + } + if (ps->backend_data && ps->root_image) { ps->backend_data->ops->release_image(ps->backend_data, ps->root_image); ps->root_image = NULL; @@ -445,6 +452,11 @@ static void destroy_backend(session_t *ps) { ps->backend_data, ps->backend_blur_context); ps->backend_blur_context = NULL; } + if (ps->shadow_context) { + ps->backend_data->ops->destroy_shadow_context(ps->backend_data, + ps->shadow_context); + ps->shadow_context = NULL; + } ps->backend_data->ops->deinit(ps->backend_data); ps->backend_data = NULL; } @@ -487,7 +499,7 @@ static bool initialize_blur(session_t *ps) { /// Init the backend and bind all the window pixmap to backend images static bool initialize_backend(session_t *ps) { - if (ps->o.experimental_backends) { + if (!ps->o.legacy_backends) { assert(!ps->backend_data); // Reinitialize win_data assert(backend_list[ps->o.backend]); @@ -498,13 +510,38 @@ static bool initialize_backend(session_t *ps) { return false; } ps->backend_data->ops = backend_list[ps->o.backend]; + ps->shadow_context = ps->backend_data->ops->create_shadow_context( + ps->backend_data, ps->o.shadow_radius); + if (!ps->shadow_context) { + log_fatal("Failed to initialize shadow context, aborting..."); + goto err; + } if (!initialize_blur(ps)) { log_fatal("Failed to prepare for background blur, aborting..."); - ps->backend_data->ops->deinit(ps->backend_data); - ps->backend_data = NULL; - quit(ps); - return false; + goto err; + } + + // Create shaders + HASH_ITER2(ps->shaders, shader) { + assert(shader->backend_shader == NULL); + shader->backend_shader = ps->backend_data->ops->create_shader( + ps->backend_data, shader->source); + if (shader->backend_shader == NULL) { + log_warn("Failed to create shader for shader file %s, " + "this shader will not be used", + shader->key); + } else { + if (ps->backend_data->ops->get_shader_attributes) { + shader->attributes = + ps->backend_data->ops->get_shader_attributes( + ps->backend_data, shader->backend_shader); + } else { + shader->attributes = 0; + } + log_debug("Shader %s has attributes %" PRIu64, + shader->key, shader->attributes); + } } // window_stack shouldn't include window that's @@ -527,6 +564,16 @@ static bool initialize_backend(session_t *ps) { // The old backends binds pixmap lazily, nothing to do here return true; +err: + if (ps->shadow_context) { + ps->backend_data->ops->destroy_shadow_context(ps->backend_data, + ps->shadow_context); + ps->shadow_context = NULL; + } + ps->backend_data->ops->deinit(ps->backend_data); + ps->backend_data = NULL; + quit(ps); + return false; } /// Handle configure event of the root window @@ -541,7 +588,7 @@ static void configure_root(session_t *ps) { bool has_root_change = false; if (ps->redirected) { // On root window changes - if (ps->o.experimental_backends) { + if (!ps->o.legacy_backends) { assert(ps->backend_data); has_root_change = ps->backend_data->ops->root_change != NULL; } else { @@ -585,7 +632,7 @@ static void configure_root(session_t *ps) { ps->damage = ps->damage_ring + ps->ndamage - 1; #ifdef CONFIG_OPENGL // GLX root change callback - if (BKEND_GLX == ps->o.backend && !ps->o.experimental_backends) { + if (BKEND_GLX == ps->o.backend && ps->o.legacy_backends) { glx_on_root_change(ps); } #endif @@ -619,14 +666,6 @@ static void handle_root_flags(session_t *ps) { 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) { - log_warn("Refresh rate detection failed. swopti will be " - "temporarily disabled"); - } - } ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE; } @@ -639,8 +678,7 @@ static void handle_root_flags(session_t *ps) { static struct managed_win * paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) { // XXX need better, more general name for `fade_running`. It really - // means if fade is still ongoing after the current frame is rendered. - // Same goes for `animation_running`. + // means if fade is still ongoing after the current frame is rendered struct managed_win *bottom = NULL; *fade_running = false; *animation_running = false; @@ -670,7 +708,9 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) { animation_delta = min2(animation_delta, ps->o.animation_delta/1000); } - // First, let's process fading + // First, let's process fading, and animated shaders + // TODO(yshui) check if a window is fully obscured, and if we don't need to + // process fading or animation for it. win_stack_foreach_managed_safe(w, &ps->window_stack) { const winmode_t mode_old = w->mode; const bool was_painted = w->to_paint; @@ -850,39 +890,42 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) { add_damage_from_win(ps, w); } - if (w->opacity != w->opacity_target) { - // Run fading - if (run_fade(ps, &w, steps)) { - *fade_running = true; - } + if (w->fg_shader && (w->fg_shader->attributes & SHADER_ATTRIBUTE_ANIMATED)) { + add_damage_from_win(ps, w); + *animation_running = true; + } - // Add window to damaged area if its opacity changes - // If was_painted == false, and to_paint is also false, we don't care - // If was_painted == false, but to_paint is true, damage will be added in - // the loop below - if (was_painted && w->opacity != opacity_old) { - add_damage_from_win(ps, w); - } + // Run fading + if (run_fade(ps, &w, steps)) { + *fade_running = true; + } - if (win_check_fade_finished(ps, w)) { - // the window has been destroyed because fading finished - continue; - } + // Add window to damaged area if its opacity changes + // If was_painted == false, and to_paint is also false, we don't care + // If was_painted == false, but to_paint is true, damage will be added in + // the loop below + if (was_painted && w->opacity != opacity_old) { + add_damage_from_win(ps, w); + } - if (win_has_frame(w)) { - w->frame_opacity = ps->o.frame_opacity; - } else { - w->frame_opacity = 1.0; - } + if (win_check_fade_finished(ps, w)) { + // the window has been destroyed because fading finished + continue; + } - // Update window mode - w->mode = win_calc_mode(w); + if (win_has_frame(w)) { + w->frame_opacity = ps->o.frame_opacity; + } else { + w->frame_opacity = 1.0; + } - // Destroy all reg_ignore above when frame opaque state changes on - // SOLID mode - if (was_painted && w->mode != mode_old) { - w->reg_ignore_valid = false; - } + // Update window mode + w->mode = win_calc_mode(w); + + // Destroy all reg_ignore above when frame opaque state changes on + // SOLID mode + if (was_painted && w->mode != mode_old) { + w->reg_ignore_valid = false; } } @@ -983,7 +1026,7 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) { // we add the window region to the ignored region // Otherwise last_reg_ignore shouldn't change if ((w->mode != WMODE_TRANS && !ps->o.force_win_blend) || - ps->o.transparent_clipping) { + (ps->o.transparent_clipping && !w->transparent_clipping_excluded)) { // w->mode == WMODE_SOLID or WMODE_FRAME_TRANS region_t *tmp = rc_region_new(); if (w->mode == WMODE_SOLID) { @@ -1098,9 +1141,13 @@ void root_damaged(session_t *ps) { if (pixmap != XCB_NONE) { ps->root_image = ps->backend_data->ops->bind_pixmap( ps->backend_data, pixmap, x_get_visual_info(ps->c, ps->vis), false); - ps->backend_data->ops->set_image_property( - ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE, - ps->root_image, (int[]){ps->root_width, ps->root_height}); + if (ps->root_image) { + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE, + ps->root_image, (int[]){ps->root_width, ps->root_height}); + } else { + log_error("Failed to bind root back pixmap"); + } } } @@ -1240,7 +1287,7 @@ static int register_cm(session_t *ps) { ps->c, xcb_change_property_checked( ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, get_atom(ps->atoms, "COMPTON_VERSION"), XCB_ATOM_STRING, 8, - (uint32_t)strlen(COMPTON_VERSION), COMPTON_VERSION)); + (uint32_t)strlen(PICOM_VERSION), PICOM_VERSION)); if (e) { log_error_x_error(e, "Failed to set COMPTON_VERSION."); free(e); @@ -1295,77 +1342,6 @@ static inline bool write_pid(session_t *ps) { } /** - * Update refresh rate info with X Randr extension. - */ -void update_refresh_rate(session_t *ps) { - xcb_randr_get_screen_info_reply_t *randr_info = xcb_randr_get_screen_info_reply( - ps->c, xcb_randr_get_screen_info(ps->c, ps->root), NULL); - - if (!randr_info) - return; - ps->refresh_rate = randr_info->rate; - free(randr_info); - - if (ps->refresh_rate) - ps->refresh_intv = US_PER_SEC / ps->refresh_rate; - else - ps->refresh_intv = 0; -} - -/** - * Initialize refresh-rated based software optimization. - * - * @return true for success, false otherwise - */ -static bool swopti_init(session_t *ps) { - log_warn("--sw-opti is going to be deprecated. If you get real benefits from " - "using " - "this option, please open an issue to let us know."); - // 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; - - // Auto-detect refresh rate otherwise - if (!ps->refresh_rate && ps->randr_exists) { - update_refresh_rate(ps); - } - - // 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 double swopti_handle_timeout(session_t *ps) { - if (!ps->refresh_intv) - return 0; - - // 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 = (get_time_timeval().tv_usec - ps->paint_tm_offset) % ps->refresh_intv; - // XXX this formula dones't work if refresh rate is not a whole number - if (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 0; - - // Add an offset so we wait until the next refresh after timeout - return (double)(ps->refresh_intv - offset) / 1e6; -} - -/** * Initialize X composite overlay window. */ static bool init_overlay(session_t *ps) { @@ -1456,10 +1432,10 @@ uint8_t session_redirection_mode(session_t *ps) { if (ps->o.debug_mode) { // If the backend is not rendering to the screen, we don't need to // take over the screen. - assert(ps->o.experimental_backends); + assert(!ps->o.legacy_backends); return XCB_COMPOSITE_REDIRECT_AUTOMATIC; } - if (ps->o.experimental_backends && !backend_list[ps->o.backend]->present) { + if (!ps->o.legacy_backends && !backend_list[ps->o.backend]->present) { // if the backend doesn't render anything, we don't need to take over the // screen. return XCB_COMPOSITE_REDIRECT_AUTOMATIC; @@ -1496,7 +1472,7 @@ static bool redirect_start(session_t *ps) { return false; } - if (ps->o.experimental_backends) { + if (!ps->o.legacy_backends) { assert(ps->backend_data); ps->ndamage = ps->backend_data->ops->max_buffer_age; } else { @@ -1738,6 +1714,7 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { ev_timer_set(&ps->fade_timer, fade_timeout(ps), 0); ev_timer_start(EV_A_ & ps->fade_timer); } + // Start/stop animation timer depends on whether windows are animating if (!animation_running && ev_is_active(&ps->animation_timer)) { ev_timer_stop(EV_A_ & ps->animation_timer); @@ -1751,7 +1728,7 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { static int paint = 0; log_trace("Render start, frame %d", paint); - if (ps->o.experimental_backends) { + if (!ps->o.legacy_backends) { paint_all_new(ps, bottom, false); } else { paint_all(ps, bottom, false); @@ -1780,57 +1757,17 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { } static void draw_callback(EV_P_ ev_idle *w, int revents) { - // This function is not used if we are using --swopti session_t *ps = session_ptr(w, draw_idle); draw_callback_impl(EV_A_ ps, revents); - // Don't do painting non-stop unless we are in benchmark mode - if (!ps->o.benchmark) { + // Don't do painting non-stop unless we are in benchmark mode, or if + // draw_callback_impl thinks we should continue painting. + if (!ps->o.benchmark && !ps->redraw_needed) { ev_idle_stop(EV_A_ & ps->draw_idle); } } -static void delayed_draw_timer_callback(EV_P_ ev_timer *w, int revents) { - session_t *ps = session_ptr(w, delayed_draw_timer); - draw_callback_impl(EV_A_ ps, revents); - - // We might have stopped the ev_idle in delayed_draw_callback, - // so we restart it if we are in benchmark mode - if (ps->o.benchmark) - ev_idle_start(EV_A_ & ps->draw_idle); -} - -static void delayed_draw_callback(EV_P_ ev_idle *w, int revents) { - // This function is only used if we are using --swopti - session_t *ps = session_ptr(w, draw_idle); - assert(ps->redraw_needed); - assert(!ev_is_active(&ps->delayed_draw_timer)); - - double delay = swopti_handle_timeout(ps); - if (delay < 1e-6) { - if (!ps->o.benchmark) { - ev_idle_stop(EV_A_ & ps->draw_idle); - } - return draw_callback_impl(EV_A_ ps, revents); - } - - // This is a little bit hacky. When we get to this point in code, we need - // to update the screen , but we will only be updating after a delay, So - // we want to stop the ev_idle, so this callback doesn't get call repeatedly - // during the delay, we also want queue_redraw to not restart the ev_idle. - // So we stop ev_idle and leave ps->redraw_needed to be true. (effectively, - // ps->redraw_needed means if redraw is needed or if draw is in progress). - // - // We do this anyway even if we are in benchmark mode. That means we will - // have to restart draw_idle after the draw actually happened when we are in - // benchmark mode. - ev_idle_stop(EV_A_ & ps->draw_idle); - - ev_timer_set(&ps->delayed_draw_timer, delay, 0); - ev_timer_start(EV_A_ & ps->delayed_draw_timer); -} - static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) { session_t *ps = (session_t *)w; xcb_generic_event_t *ev = xcb_poll_for_event(ps->c); @@ -1861,6 +1798,62 @@ static void config_file_change_cb(void *_ps) { reset_enable(ps->loop, NULL, 0); } +static bool load_shader_source(session_t *ps, const char *path) { + if (!path) { + // Using the default shader. + return false; + } + + log_info("Loading shader source from %s", path); + + struct shader_info *shader = NULL; + HASH_FIND_STR(ps->shaders, path, shader); + if (shader) { + log_debug("Shader already loaded, reusing"); + return false; + } + + shader = ccalloc(1, struct shader_info); + shader->key = strdup(path); + HASH_ADD_KEYPTR(hh, ps->shaders, shader->key, strlen(shader->key), shader); + + FILE *f = fopen(path, "r"); + if (!f) { + log_error("Failed to open custom shader file: %s", path); + goto err; + } + struct stat statbuf; + if (fstat(fileno(f), &statbuf) < 0) { + log_error("Failed to access custom shader file: %s", path); + goto err; + } + + auto num_bytes = (size_t)statbuf.st_size; + shader->source = ccalloc(num_bytes + 1, char); + auto read_bytes = fread(shader->source, sizeof(char), num_bytes, f); + if (read_bytes < num_bytes || ferror(f)) { + // This is a difficult to hit error case, review thoroughly. + log_error("Failed to read custom shader at %s. (read %lu bytes, expected " + "%lu bytes)", + path, read_bytes, num_bytes); + goto err; + } + return false; +err: + HASH_DEL(ps->shaders, shader); + if (f) { + fclose(f); + } + free(shader->source); + free(shader->key); + free(shader); + return true; +} + +static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data) { + return load_shader_source(data, c2_list_get_data(cond)); +} + /** * Initialize a session. * @@ -1912,11 +1905,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .black_picture = XCB_NONE, .cshadow_picture = XCB_NONE, .white_picture = XCB_NONE, - .gaussian_map = NULL, - - .refresh_rate = 0, - .refresh_intv = 0UL, - .paint_tm_offset = 0L, + .shadow_context = NULL, #ifdef CONFIG_VSYNC_DRM .drm_fd = -1, @@ -2093,6 +2082,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy, return NULL; } + if (ps->o.window_shader_fg) { + log_debug("Default window shader: \"%s\"", ps->o.window_shader_fg); + } + if (ps->o.logpath) { auto l = file_logger_new(ps->o.logpath); if (l) { @@ -2142,9 +2135,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy, c2_list_postprocess(ps, ps->o.fade_blacklist) && c2_list_postprocess(ps, ps->o.blur_background_blacklist) && c2_list_postprocess(ps, ps->o.invert_color_list) && + c2_list_postprocess(ps, ps->o.window_shader_fg_rules) && c2_list_postprocess(ps, ps->o.opacity_rules) && - c2_list_postprocess(ps, ps->o.corner_rules) && c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) && + c2_list_postprocess(ps, ps->o.corner_rules) && + c2_list_postprocess(ps, ps->o.blur_rules) && c2_list_postprocess(ps, ps->o.animation_open_blacklist) && c2_list_postprocess(ps, ps->o.animation_unmap_blacklist) && c2_list_postprocess(ps, ps->o.focus_blacklist))) { @@ -2152,8 +2147,27 @@ static session_t *session_init(int argc, char **argv, Display *dpy, "might not work"); } - ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius); - sum_kernel_preprocess(ps->gaussian_map); + // Load shader source file specified in the shader rules + if (c2_list_foreach(ps->o.window_shader_fg_rules, load_shader_source_for_condition, ps)) { + log_error("Failed to load shader source file for some of the window " + "shader rules"); + } + if (load_shader_source(ps, ps->o.window_shader_fg)) { + log_error("Failed to load window shader source file"); + } + + if (log_get_level_tls() <= LOG_LEVEL_DEBUG) { + HASH_ITER2(ps->shaders, shader) { + log_debug("Shader %s:", shader->key); + log_debug("%s", shader->source); + } + } + + if (ps->o.legacy_backends) { + ps->shadow_context = + (void *)gaussian_kernel_autodetect_deviation(ps->o.shadow_radius); + sum_kernel_preprocess((conv *)ps->shadow_context); + } rebuild_shadow_exclude_reg(ps); @@ -2224,11 +2238,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } // Query X RandR - if ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop) { + if (ps->o.xinerama_shadow_crop) { if (!ps->randr_exists) { - log_fatal("No XRandR extension. sw-opti, refresh-rate or " - "xinerama-shadow-crop " - "cannot be enabled."); + log_fatal("No XRandR extension. xinerama-shadow-crop cannot be " + "enabled."); goto err; } } @@ -2280,7 +2293,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, // The old backends doesn't have a automatic redirection mode log_info("The compositor is started in automatic redirection mode."); - assert(ps->o.experimental_backends); + assert(!ps->o.legacy_backends); if (backend_list[ps->o.backend]->present) { // If the backend has `present`, we couldn't be in automatic @@ -2296,7 +2309,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, apply_driver_workarounds(ps, ps->drivers); // Initialize filters, must be preceded by OpenGL context creation - if (!ps->o.experimental_backends && !init_render(ps)) { + if (ps->o.legacy_backends && !init_render(ps)) { log_fatal("Failed to initialize the backend"); exit(1); } @@ -2314,7 +2327,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, free(config_file_to_free); - if (bkend_use_glx(ps) && !ps->o.experimental_backends) { + if (bkend_use_glx(ps) && ps->o.legacy_backends) { auto gl_logger = gl_string_marker_logger_new(); if (gl_logger) { log_info("Enabling gl string marker"); @@ -2322,7 +2335,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } } - if (ps->o.experimental_backends) { + if (!ps->o.legacy_backends) { if (ps->o.monitor_repaint && !backend_list[ps->o.backend]->fill) { log_warn("--monitor-repaint is not supported by the backend, " "disabling"); @@ -2330,15 +2343,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } } - // Initialize software optimization - if (ps->o.sw_opti) - ps->o.sw_opti = swopti_init(ps); - // 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)) + if (ps->randr_exists && ps->o.xinerama_shadow_crop) { xcb_randr_select_input(ps->c, ps->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE); + } cxinerama_upd_scrs(ps); @@ -2359,14 +2368,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ev_io_init(&ps->xiow, x_event_callback, ConnectionNumber(ps->dpy), EV_READ); ev_io_start(ps->loop, &ps->xiow); ev_init(&ps->unredir_timer, tmout_unredir_callback); - if (ps->o.sw_opti) - ev_idle_init(&ps->draw_idle, delayed_draw_callback); - else - ev_idle_init(&ps->draw_idle, draw_callback); + ev_idle_init(&ps->draw_idle, draw_callback); ev_init(&ps->fade_timer, fade_timer_callback); ev_init(&ps->animation_timer, animation_timer_callback); - ev_init(&ps->delayed_draw_timer, delayed_draw_timer_callback); // Set up SIGUSR1 signal handler to reset program ev_signal_init(&ps->usr1_signal, reset_enable, SIGUSR1); @@ -2517,19 +2522,21 @@ static void session_destroy(session_t *ps) { list_init_head(&ps->window_stack); // Free blacklists - free_wincondlst(&ps->o.shadow_blacklist); - free_wincondlst(&ps->o.shadow_clip_list); - 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.opacity_rules); - free_wincondlst(&ps->o.corner_rules); - free_wincondlst(&ps->o.paint_blacklist); - free_wincondlst(&ps->o.unredir_if_possible_blacklist); - free_wincondlst(&ps->o.rounded_corners_blacklist); - free_wincondlst(&ps->o.animation_open_blacklist); - free_wincondlst(&ps->o.animation_unmap_blacklist); + c2_list_free(&ps->o.shadow_blacklist, NULL); + c2_list_free(&ps->o.shadow_clip_list, NULL); + c2_list_free(&ps->o.fade_blacklist, NULL); + c2_list_free(&ps->o.focus_blacklist, NULL); + c2_list_free(&ps->o.invert_color_list, NULL); + c2_list_free(&ps->o.blur_background_blacklist, NULL); + c2_list_free(&ps->o.opacity_rules, NULL); + c2_list_free(&ps->o.paint_blacklist, NULL); + c2_list_free(&ps->o.unredir_if_possible_blacklist, NULL); + c2_list_free(&ps->o.rounded_corners_blacklist, NULL); + c2_list_free(&ps->o.corner_rules, NULL); + c2_list_free(&ps->o.blur_rules, NULL); + c2_list_free(&ps->o.animation_open_blacklist, NULL); + c2_list_free(&ps->o.animation_unmap_blacklist, NULL); + c2_list_free(&ps->o.window_shader_fg_rules, free); // Free tracked atom list { @@ -2580,6 +2587,17 @@ static void session_destroy(session_t *ps) { free(ps->o.glx_fshader_win_str); free_xinerama_info(ps); + // Release custom window shaders + free(ps->o.window_shader_fg); + struct shader_info *shader, *tmp; + HASH_ITER(hh, ps->shaders, shader, tmp) { + HASH_DEL(ps->shaders, shader); + assert(shader->backend_shader == NULL); + free(shader->source); + free(shader->key); + free(shader); + } + #ifdef CONFIG_VSYNC_DRM // Close file opened for DRM VSync if (ps->drm_fd >= 0) { @@ -2615,7 +2633,7 @@ static void session_destroy(session_t *ps) { ps->damaged_region = XCB_NONE; } - if (ps->o.experimental_backends) { + if (!ps->o.legacy_backends) { // backend is deinitialized in unredirect() assert(ps->backend_data == NULL); } else { @@ -2632,7 +2650,9 @@ static void session_destroy(session_t *ps) { // Flush all events x_sync(ps->c); ev_io_stop(ps->loop, &ps->xiow); - free_conv(ps->gaussian_map); + if (ps->o.legacy_backends) { + free_conv((conv *)ps->shadow_context); + } destroy_atoms(ps->atoms); #ifdef DEBUG_XRC @@ -2658,9 +2678,6 @@ static void session_destroy(session_t *ps) { * @param ps current session */ static void session_run(session_t *ps) { - if (ps->o.sw_opti) - ps->paint_tm_offset = get_time_timeval().tv_usec; - // In benchmark mode, we want draw_idle handler to always be active if (ps->o.benchmark) { ev_idle_start(ps->loop, &ps->draw_idle); diff --git a/src/picom.h b/src/picom.h index 25f7580..7ee289b 100644 --- a/src/picom.h +++ b/src/picom.h @@ -40,8 +40,6 @@ uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode); void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce); -void update_refresh_rate(session_t *ps); - void root_damaged(session_t *ps); void cxinerama_upd_scrs(session_t *ps); @@ -86,14 +84,6 @@ static inline bool array_wid_exists(const xcb_window_t *arr, int count, xcb_wind return false; } -/** - * Destroy a condition list. - */ -static inline void free_wincondlst(c2_lptr_t **pcondlst) { - while ((*pcondlst = c2_free_lptr(*pcondlst))) - continue; -} - #ifndef CONFIG_OPENGL static inline void free_paint_glx(session_t *ps attr_unused, paint_t *p attr_unused) { } diff --git a/src/render.c b/src/render.c index ac9b40e..db627de 100644 --- a/src/render.c +++ b/src/render.c @@ -649,8 +649,9 @@ static bool get_root_tile(session_t *ps) { static void paint_root(session_t *ps, const region_t *reg_paint) { // If there is no root tile pixmap, try getting one. // If that fails, give up. - if (!ps->root_tile_paint.pixmap && !get_root_tile(ps)) + if (!ps->root_tile_paint.pixmap && !get_root_tile(ps)) { return; + } paint_region(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint, ps->root_tile_paint.pict); @@ -669,7 +670,7 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE; xcb_gcontext_t gc = XCB_NONE; - shadow_image = make_shadow(ps->c, ps->gaussian_map, opacity, width, height); + shadow_image = make_shadow(ps->c, (conv *)ps->shadow_context, opacity, width, height); if (!shadow_image) { log_error("failed to make shadow"); return XCB_NONE; @@ -689,8 +690,9 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit ps->c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL); shadow_picture_argb = x_create_picture_with_standard_and_pixmap( ps->c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL); - if (!shadow_picture || !shadow_picture_argb) + if (!shadow_picture || !shadow_picture_argb) { goto shadow_picture_err; + } gc = x_new_id(ps->c); xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL); diff --git a/src/string_utils.c b/src/string_utils.c index 65af0f2..ee5bb2d 100644 --- a/src/string_utils.c +++ b/src/string_utils.c @@ -127,3 +127,31 @@ TEST_CASE(strtod_simple) { TEST_EQUAL(result, 0.5); TEST_EQUAL(*end, '\0'); } + +const char *trim_both(const char *src, size_t *length) { + size_t i = 0; + while (isspace(src[i])) { + i++; + } + size_t j = strlen(src) - 1; + while (j > i && isspace(src[j])) { + j--; + } + *length = j - i + 1; + return src + i; +} + +TEST_CASE(trim_both) { + size_t length; + const char *str = trim_both(" \t\n\r\f", &length); + TEST_EQUAL(length, 0); + TEST_EQUAL(*str, '\0'); + + str = trim_both(" asdfas ", &length); + TEST_EQUAL(length, 6); + TEST_STRNEQUAL(str, "asdfas", length); + + str = trim_both(" asdf asdf ", &length); + TEST_EQUAL(length, 9); + TEST_STRNEQUAL(str, "asdf asdf", length); +} diff --git a/src/string_utils.h b/src/string_utils.h index 38febde..461173a 100644 --- a/src/string_utils.h +++ b/src/string_utils.h @@ -11,6 +11,7 @@ char *mstrjoin(const char *src1, const char *src2); char *mstrjoin3(const char *src1, const char *src2, const char *src3); void mstrextend(char **psrc1, const char *src2); +const char *trim_both(const char *src, size_t *length); /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*) double strtod_simple(const char *, const char **); diff --git a/src/utils.c b/src/utils.c index 8a27f39..c236a4a 100644 --- a/src/utils.c +++ b/src/utils.c @@ -36,8 +36,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int /// Calculates next closest power of two of 32bit integer n /// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 /// -int next_power_of_two(int n) -{ +int next_power_of_two(int n) { n--; n |= n >> 1; n |= n >> 2; diff --git a/src/utils.h b/src/utils.h index 6bb8643..a35bfa8 100644 --- a/src/utils.h +++ b/src/utils.h @@ -14,6 +14,8 @@ #include <test.h> +#include <time.h> + #include "compiler.h" #include "types.h" @@ -80,39 +82,39 @@ safe_isnan(double a) { #define to_int_checked(val) \ ({ \ - int64_t tmp = (val); \ - ASSERT_IN_RANGE(tmp, INT_MIN, INT_MAX); \ - (int)tmp; \ + int64_t __to_tmp = (val); \ + ASSERT_IN_RANGE(__to_tmp, INT_MIN, INT_MAX); \ + (int)__to_tmp; \ }) #define to_char_checked(val) \ ({ \ - int64_t tmp = (val); \ - ASSERT_IN_RANGE(tmp, CHAR_MIN, CHAR_MAX); \ - (char)tmp; \ + int64_t __to_tmp = (val); \ + ASSERT_IN_RANGE(__to_tmp, CHAR_MIN, CHAR_MAX); \ + (char)__to_tmp; \ }) #define to_u16_checked(val) \ ({ \ - auto tmp = (val); \ - ASSERT_IN_RANGE(tmp, 0, UINT16_MAX); \ - (uint16_t) tmp; \ + auto __to_tmp = (val); \ + ASSERT_IN_RANGE(__to_tmp, 0, UINT16_MAX); \ + (uint16_t) __to_tmp; \ }) #define to_i16_checked(val) \ ({ \ - int64_t tmp = (val); \ - ASSERT_IN_RANGE(tmp, INT16_MIN, INT16_MAX); \ - (int16_t) tmp; \ + int64_t __to_tmp = (val); \ + ASSERT_IN_RANGE(__to_tmp, INT16_MIN, INT16_MAX); \ + (int16_t) __to_tmp; \ }) #define to_u32_checked(val) \ ({ \ - auto tmp = (val); \ + auto __to_tmp = (val); \ int64_t max attr_unused = UINT32_MAX; /* silence clang tautological \ comparison warning*/ \ - ASSERT_IN_RANGE(tmp, 0, max); \ - (uint32_t) tmp; \ + ASSERT_IN_RANGE(__to_tmp, 0, max); \ + (uint32_t) __to_tmp; \ }) /** * Normalize an int value to a specific range. @@ -130,21 +132,9 @@ static inline int attr_const normalize_i_range(int i, int min, int max) { return i; } -/** - * Linearly interpolate from a range into another. - * - * @param a,b first range - * @param c,d second range - * @param value value to interpolate, should be in range [a,b] - * @return interpolated value in range [c,d] - */ -static inline int attr_const lerp_range(int a, int b, int c, int d, int value) { - ASSERT_IN_RANGE(value, a, b); - return (d-c)*(value-a)/(b-a) + c; -} - #define min2(a, b) ((a) > (b) ? (b) : (a)) #define max2(a, b) ((a) > (b) ? (a) : (b)) +#define min3(a, b, c) min2(a, min2(b, c)) /// clamp `val` into interval [min, max] #define clamp(val, min, max) max2(min2(val, max), min) @@ -283,6 +273,15 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr) void name##_ref(type *a); \ void name##_unref(type **a); +static inline void free_charpp(char **str) { + if (str) { + free(*str); + *str = NULL; + } +} + +/// An allocated char* that is automatically freed when it goes out of scope. +#define scoped_charp char *cleanup(free_charpp) /// /// Calculates next closest power of two of 32bit integer n @@ -290,5 +289,18 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr) /// int next_power_of_two(int n); +// Some versions of the Android libc do not have timespec_get(), use +// clock_gettime() instead. +#ifdef __ANDROID__ + +#ifndef TIME_UTC +#define TIME_UTC 1 +#endif + +static inline int timespec_get(struct timespec *ts, int base) { + assert(base == TIME_UTC); + return clock_gettime(CLOCK_REALTIME, ts); +} +#endif // vim: set noet sw=8 ts=8 : @@ -322,6 +322,13 @@ static inline void win_release_shadow(backend_t *base, struct managed_win *w) { } } +static inline void win_release_mask(backend_t *base, struct managed_win *w) { + if (w->mask_image) { + base->ops->release_image(base, w->mask_image); + w->mask_image = NULL; + } +} + static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w) { assert(!w->win_image); auto pixmap = x_new_id(b->c); @@ -346,14 +353,36 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w return true; } +bool win_bind_mask(struct backend_base *b, struct managed_win *w) { + assert(!w->mask_image); + auto reg_bound_local = win_get_bounding_shape_global_by_val(w); + pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); + w->mask_image = b->ops->make_mask( + b, (geometry_t){.width = w->widthb, .height = w->heightb}, ®_bound_local); + pixman_region32_fini(®_bound_local); + + if (!w->mask_image) { + return false; + } + b->ops->set_image_property(b, IMAGE_PROPERTY_CORNER_RADIUS, w->mask_image, + (double[]){w->corner_radius}); + return true; +} + bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c, - struct conv *kernel) { + struct backend_shadow_context *sctx) { assert(!w->shadow_image); assert(w->shadow); - w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, kernel, c.red, - c.green, c.blue, c.alpha); + if ((w->corner_radius == 0 && w->bounding_shaped == false) || + b->ops->shadow_from_mask == NULL) { + w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c); + } else { + win_bind_mask(b, w); + w->shadow_image = b->ops->shadow_from_mask(b, w->mask_image, sctx, c); + } if (!w->shadow_image) { - log_error("Failed to bind shadow image, shadow will be disabled for " + log_error("Failed to bind shadow image, shadow will be disabled " + "for " "%#010x (%s)", w->base.id, w->name); win_set_flags(w, WIN_FLAGS_SHADOW_NONE); @@ -367,10 +396,9 @@ bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color } void win_release_images(struct backend_base *backend, struct managed_win *w) { - // We don't want to decide what we should do if the image we want to release is - // stale (do we clear the stale flags or not?) - // But if we are not releasing any images anyway, we don't care about the stale - // flags. + // We don't want to decide what we should do if the image we want to + // release is stale (do we clear the stale flags or not?) But if we are + // not releasing any images anyway, we don't care about the stale flags. if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)); @@ -382,15 +410,19 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) { assert(!win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE)); win_release_shadow(backend, w); } + + win_release_mask(backend, w); } -/// Returns true if the `prop` property is stale, as well as clears the stale flag. +/// Returns true if the `prop` property is stale, as well as clears the stale +/// flag. static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop); -/// Returns true if any of the properties are stale, as well as clear all the stale flags. +/// Returns true if any of the properties are stale, as well as clear all the +/// stale flags. static void win_clear_all_properties_stale(struct managed_win *w); -/// Fetch new window properties from the X server, and run appropriate updates. Might set -/// WIN_FLAGS_FACTOR_CHANGED +/// Fetch new window properties from the X server, and run appropriate updates. +/// Might set WIN_FLAGS_FACTOR_CHANGED static void win_update_properties(session_t *ps, struct managed_win *w) { if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_TYPE)) { win_update_wintype(ps, w); @@ -576,10 +608,6 @@ static void init_animation_unmap(session_t *ps, struct managed_win *w) { animation = ps->o.wintype_option[w->window_type].animation_unmap; } - if (c2_match(ps, w, ps->o.animation_unmap_blacklist, NULL)) { - animation = OPEN_WINDOW_ANIMATION_NONE; - } - if (ps->root_desktop_switch_direction != 0) { if (ps->o.animation_for_workspace_switch_out == OPEN_WINDOW_ANIMATION_AUTO) animation = OPEN_WINDOW_ANIMATION_SLIDE_OUT; @@ -587,6 +615,10 @@ static void init_animation_unmap(session_t *ps, struct managed_win *w) { animation = ps->o.animation_for_workspace_switch_out; } + if (c2_match(ps, w, ps->o.animation_unmap_blacklist, NULL)) { + animation = OPEN_WINDOW_ANIMATION_NONE; + } + switch (animation) { case OPEN_WINDOW_ANIMATION_AUTO: case OPEN_WINDOW_ANIMATION_NONE: { // No animation @@ -672,8 +704,8 @@ static void init_animation_unmap(session_t *ps, struct managed_win *w) { /// Handle non-image flags. This phase might set IMAGES_STALE flags void win_process_update_flags(session_t *ps, struct managed_win *w) { - // Whether the window was visible before we process the mapped flag. i.e. is the - // window just mapped. + // Whether the window was visible before we process the mapped flag. i.e. + // is the window just mapped. bool was_visible = win_is_real_visible(w); log_trace("Processing flags for window %#010x (%s), was visible: %d", w->base.id, w->name, was_visible); @@ -688,8 +720,8 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { return; } - // Check client first, because later property updates need accurate client window - // information + // Check client first, because later property updates need accurate client + // window information if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) { win_recheck_client(ps, w); win_clear_flags(w, WIN_FLAGS_CLIENT_STALE); @@ -698,15 +730,15 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { bool damaged = false; if (win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) { if (was_visible) { - // Mark the old extents of this window as damaged. The new extents - // will be marked damaged below, after the window extents are - // updated. + // Mark the old extents of this window as damaged. The new + // extents will be marked damaged below, after the window + // extents are updated. // - // If the window is just mapped, we don't need to mark the old - // extent as damaged. (It's possible that the window was in fading - // and is interrupted by being mapped. In that case, the fading - // window will be added to damage by map_win_start, so we don't - // need to do it here) + // If the window is just mapped, we don't need to mark the + // old extent as damaged. (It's possible that the window + // was in fading and is interrupted by being mapped. In + // that case, the fading window will be added to damage by + // map_win_start, so we don't need to do it here) add_damage_from_win(ps, w); } @@ -800,7 +832,8 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE); } - // Factor change flags could be set by previous stages, so must be handled last + // Factor change flags could be set by previous stages, so must be handled + // last if (win_check_flags_all(w, WIN_FLAGS_FACTOR_CHANGED)) { win_on_factor_change(ps, w); win_clear_flags(w, WIN_FLAGS_FACTOR_CHANGED); @@ -832,9 +865,10 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) { } if (win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)) { - // Check to make sure the window is still mapped, otherwise we - // won't be able to rebind pixmap after releasing it, yet we might - // still need the pixmap for rendering. + // Check to make sure the window is still mapped, + // otherwise we won't be able to rebind pixmap after + // releasing it, yet we might still need the pixmap for + // rendering. assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING); if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { // Must release images first, otherwise breaks @@ -854,7 +888,7 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) { .green = ps->o.shadow_green, .blue = ps->o.shadow_blue, .alpha = ps->o.shadow_opacity}, - ps->gaussian_map); + ps->shadow_context); } } @@ -914,7 +948,8 @@ int win_update_name(session_t *ps, struct managed_win *w) { } if (!(wid_get_text_prop(ps, w->client_win, ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) { - log_debug("(%#010x): _NET_WM_NAME unset, falling back to WM_NAME.", + log_debug("(%#010x): _NET_WM_NAME unset, falling back to " + "WM_NAME.", w->client_win); if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_NAME, &strlst, &nstr)) { @@ -1036,8 +1071,8 @@ winmode_t win_calc_mode(const struct managed_win *w) { if (win_has_alpha(w)) { if (w->client_win == XCB_NONE) { - // This is a window not managed by the WM, and it has alpha, - // so it's transparent. No need to check WM frame. + // This is a window not managed by the WM, and it has + // alpha, so it's transparent. No need to check WM frame. return WMODE_TRANS; } // The WM window has alpha @@ -1047,12 +1082,12 @@ winmode_t win_calc_mode(const struct managed_win *w) { return WMODE_TRANS; } if (win_has_frame(w)) { - // The client window doesn't have alpha, but we have a WM frame - // window, which has alpha. + // The client window doesn't have alpha, but we have a WM + // frame window, which has alpha. return WMODE_FRAME_TRANS; } - // Although the WM window has alpha, the frame window has 0 size, so - // consider the window solid + // Although the WM window has alpha, the frame window has 0 size, + // so consider the window solid } if (w->frame_opacity != 1.0 && win_has_frame(w)) { @@ -1068,8 +1103,9 @@ winmode_t win_calc_mode(const struct managed_win *w) { * * The priority of opacity settings are: * - * inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if set) > - * opacity-rules (if matched) > window type default opacity > active/inactive opacity + * inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if + * set) > opacity-rules (if matched) > window type default opacity > + * active/inactive opacity * * @param ps current session * @param w struct _win object representing the window @@ -1097,7 +1133,8 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) { } else if (!safe_isnan(ps->o.wintype_option[w->window_type].opacity)) { opacity = ps->o.wintype_option[w->window_type].opacity; } else { - // Respect active_opacity only when the window is physically focused + // Respect active_opacity only when the window is physically + // focused if (win_is_focused_raw(ps, w)) opacity = ps->o.active_opacity; else if (!w->focused) @@ -1133,7 +1170,8 @@ bool win_should_dim(session_t *ps, const struct managed_win *w) { * Determine if a window should fade on opacity change. */ bool win_should_fade(session_t *ps, const struct managed_win *w) { - // To prevent it from being overwritten by last-paint value if the window is + // To prevent it from being overwritten by last-paint value if the window + // is if (w->fade_force != UNSET) { return w->fade_force; } @@ -1177,7 +1215,8 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new log_debug("Updating shadow property of window %#010x (%s) to %d", w->base.id, w->name, shadow_new); - // We don't handle property updates of non-visible windows until they are mapped. + // We don't handle property updates of non-visible windows until they are + // mapped. assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && w->state != WSTATE_UNMAPPING); @@ -1199,33 +1238,35 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new // Note: because the release and creation of the shadow images are // delayed. When multiple shadow changes happen in a row, without - // rendering phase between them, there could be a stale shadow image - // attached to the window even if w->shadow was previously false. And vice - // versa. So we check the STALE flag before asserting the existence of the - // shadow image. + // rendering phase between them, there could be a stale shadow + // image attached to the window even if w->shadow was previously + // false. And vice versa. So we check the STALE flag before + // asserting the existence of the shadow image. if (w->shadow) { // Mark the new extents as damaged if the shadow is added assert(!w->shadow_image || win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) || - !ps->o.experimental_backends); + ps->o.legacy_backends); pixman_region32_clear(&extents); win_extents(w, &extents); add_damage_from_win(ps, w); } else { - // Mark the old extents as damaged if the shadow is removed + // Mark the old extents as damaged if the shadow is + // removed assert(w->shadow_image || win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) || - !ps->o.experimental_backends); + ps->o.legacy_backends); add_damage(ps, &extents); } // Delayed update of shadow image // By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to - // re-create or release the shaodw in based on whether w->shadow is set. + // re-create or release the shaodw in based on whether w->shadow + // is set. win_set_flags(w, WIN_FLAGS_SHADOW_STALE); - // Only set pending_updates if we are redirected. Otherwise change of a - // shadow won't have influence on whether we should redirect. + // Only set pending_updates if we are redirected. Otherwise change + // of a shadow won't have influence on whether we should redirect. ps->pending_updates = true; } @@ -1268,7 +1309,7 @@ static void win_determine_shadow(session_t *ps, struct managed_win *w) { * things. */ void win_update_prop_shadow(session_t *ps, struct managed_win *w) { - long attr_shadow_old = w->prop_shadow; + long long attr_shadow_old = w->prop_shadow; win_update_prop_shadow_raw(ps, w); @@ -1357,8 +1398,22 @@ win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_backgrou w->blur_background = blur_background_new; - // This damage might not be absolutely necessary (e.g. when the window is opaque), - // but blur_background changes should be rare, so this should be fine. + // This damage might not be absolutely necessary (e.g. when the window is + // opaque), but blur_background changes should be rare, so this should be + // fine. + add_damage_from_win(ps, w); +} + +static void +win_set_fg_shader(session_t *ps, struct managed_win *w, struct shader_info *shader_new) { + if (w->fg_shader == shader_new) { + return; + } + + w->fg_shader = shader_new; + + // A different shader might change how the window is drawn, these changes + // should be rare however, so this should be fine. add_damage_from_win(ps, w); } @@ -1377,7 +1432,12 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w) log_debug("Blur background disabled by wintypes"); blur_background_new = false; } else if (c2_match(ps, w, ps->o.blur_background_blacklist, NULL)) { - log_debug("Blur background disabled by blur-background-exclude"); + log_debug("Blur background disabled by " + "blur-background-exclude"); + blur_background_new = false; + } else if (c2_match(ps, w, ps->o.blur_rules, NULL)) { + blur_background_new = true; + } else { blur_background_new = false; } } @@ -1393,7 +1453,7 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) w->corner_radius = 0; return; } - + void *val = NULL; // Don't round full screen windows & excluded windows @@ -1413,6 +1473,28 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) } /** + * Determine custom window shader to use for a window. + */ +static void win_determine_fg_shader(session_t *ps, struct managed_win *w) { + if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { + return; + } + + auto shader_new = ps->o.window_shader_fg; + void *val = NULL; + if (c2_match(ps, w, ps->o.window_shader_fg_rules, &val)) { + shader_new = val; + } + + struct shader_info *shader = NULL; + if (shader_new) { + HASH_FIND_STR(ps->shaders, shader_new, shader); + } + + win_set_fg_shader(ps, w, shader); +} + +/** * Update window opacity according to opacity rules. */ void win_update_opacity_rule(session_t *ps, struct managed_win *w) { @@ -1439,8 +1521,8 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) { */ void win_on_factor_change(session_t *ps, struct managed_win *w) { log_debug("Window %#010x (%s) factor change", w->base.id, w->name); - // Focus needs to be updated first, as other rules might depend on the focused - // state of the window + // Focus needs to be updated first, as other rules might depend on the + // focused state of the window win_update_focused(ps, w); win_determine_shadow(ps, w); @@ -1448,6 +1530,7 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) { win_determine_invert_color(ps, w); win_determine_blur_background(ps, w); win_determine_rounded_corners(ps, w); + win_determine_fg_shader(ps, w); w->mode = win_calc_mode(w); log_debug("Window mode changed to %d", w->mode); win_update_opacity_rule(ps, w); @@ -1461,6 +1544,9 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) { w->fade_excluded = c2_match(ps, w, ps->o.fade_blacklist, NULL); + w->transparent_clipping_excluded = + c2_match(ps, w, ps->o.transparent_clipping_blacklist, NULL); + win_update_opacity_target(ps, w); w->reg_ignore_valid = false; @@ -1477,14 +1563,14 @@ void win_on_win_size_change(session_t *ps, struct managed_win *w) { w->shadow_width = w->widthb + ps->o.shadow_radius * 2; w->shadow_height = w->heightb + ps->o.shadow_radius * 2; - // We don't handle property updates of non-visible windows until they are mapped. + // We don't handle property updates of non-visible windows until they are + // mapped. assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && w->state != WSTATE_UNMAPPING); // Invalidate the shadow we built - if (w->state != WSTATE_DESTROYING) - win_set_flags(w, WIN_FLAGS_IMAGES_STALE); - + win_set_flags(w, WIN_FLAGS_IMAGES_STALE); + win_release_mask(ps->backend_data, w); ps->pending_updates = true; free_paint(ps, &w->shadow_paint); } @@ -1707,21 +1793,21 @@ struct win *add_win_top(session_t *ps, xcb_window_t id) { return add_win(ps, id, &ps->window_stack); } -/// Insert a new window above window with id `below`, if there is no window, add to top -/// New window will be in unmapped state +/// Insert a new window above window with id `below`, if there is no window, add +/// to top New window will be in unmapped state struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) { struct win *w = NULL; HASH_FIND_INT(ps->windows, &below, w); if (!w) { if (!list_is_empty(&ps->window_stack)) { - // `below` window is not found even if the window stack is not - // empty + // `below` window is not found even if the window stack is + // not empty return NULL; } return add_win_top(ps, id); } else { - // we found something from the hash table, so if the stack is empty, - // we are in an inconsistent state. + // we found something from the hash table, so if the stack is + // empty, we are in an inconsistent state. assert(!list_is_empty(&ps->window_stack)); return add_win(ps, id, w->stack_neighbour.prev); } @@ -1743,19 +1829,20 @@ struct win *fill_win(session_t *ps, struct win *w) { .blur_background = false, .reg_ignore = NULL, // The following ones are updated for other reasons - .pixmap_damaged = false, // updated by damage events - .state = WSTATE_UNMAPPED, // updated by window state changes - .in_openclose = true, // set to false after first map is done, - // true here because window is just created + .pixmap_damaged = false, // updated by damage events + .state = WSTATE_UNMAPPED, // updated by window state changes + .in_openclose = true, // set to false after first map is done, + // true here because window is just created .animation_velocity_x = 0.0, // updated by window geometry changes .animation_velocity_y = 0.0, // updated by window geometry changes .animation_velocity_w = 0.0, // updated by window geometry changes .animation_velocity_h = 0.0, // updated by window geometry changes .animation_progress = 1.0, // updated by window geometry changes .animation_inv_og_distance = NAN, // updated by window geometry changes - .reg_ignore_valid = false, // set to true when damaged - .flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc - // change + .reg_ignore_valid = false, // set to true when damaged + .flags = WIN_FLAGS_IMAGES_NONE, // updated by + // property/attributes/etc + // change .stale_props = NULL, .stale_props_capacity = 0, @@ -1783,9 +1870,11 @@ struct win *fill_win(session_t *ps, struct win *w) { .win_image = NULL, .old_win_image = NULL, .shadow_image = NULL, + .mask_image = NULL, .prev_trans = NULL, .shadow = false, .clip_shadow_above = false, + .fg_shader = NULL, .xinerama_scr = -1, .mode = WMODE_TRANS, .ever_damaged = false, @@ -1807,6 +1896,7 @@ struct win *fill_win(session_t *ps, struct win *w) { .rounded_corners = false, .paint_excluded = false, .fade_excluded = false, + .transparent_clipping_excluded = false, .unredir_if_possible_excluded = false, .prop_shadow = -1, // following 4 are set in win_mark_client @@ -1834,8 +1924,9 @@ struct win *fill_win(session_t *ps, struct win *w) { auto duplicated_win = find_managed_win(ps, w->id); if (duplicated_win) { - log_debug("Window %#010x (recorded name: %s) added multiple times", w->id, - duplicated_win->name); + log_debug("Window %#010x (recorded name: %s) added multiple " + "times", + w->id, duplicated_win->name); return &duplicated_win->base; } @@ -1847,14 +1938,15 @@ struct win *fill_win(session_t *ps, struct win *w) { // Failed to get window attributes or geometry probably means // the window is gone already. Unviewable means the window is // already reparented elsewhere. - // BTW, we don't care about Input Only windows, except for stacking - // proposes, so we need to keep track of them still. + // BTW, we don't care about Input Only windows, except for + // stacking proposes, so we need to keep track of them still. free(a); return w; } if (a->_class == XCB_WINDOW_CLASS_INPUT_ONLY) { - // No need to manage this window, but we still keep it on the window stack + // No need to manage this window, but we still keep it on the + // window stack w->managed = false; free(a); return w; @@ -1924,8 +2016,8 @@ struct win *fill_win(session_t *ps, struct win *w) { assert(replaced == w); free(w); - // Set all the stale flags on this new window, so it's properties will get updated - // when it's mapped + // Set all the stale flags on this new window, so it's properties will get + // updated when it's mapped win_set_flags(new, WIN_FLAGS_CLIENT_STALE | WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE | WIN_FLAGS_PROPERTY_STALE | WIN_FLAGS_FACTOR_CHANGED); @@ -1961,8 +2053,8 @@ static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_wind // 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. + // Update the old and new window group and active_leader if the + // window could affect their state. xcb_window_t cache_leader = win_get_leader(ps, w); if (win_is_focused_raw(ps, w) && cache_leader_old != cache_leader) { ps->active_leader = cache_leader; @@ -2009,7 +2101,8 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int 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 the leader of this window isn't itself, look for its + // ancestors if (w->cache_leader && w->cache_leader != w->client_win) { auto wp = find_toplevel(ps, w->cache_leader); if (wp) { @@ -2157,7 +2250,8 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { w->bounding_shaped = win_bounding_shaped(ps, w->base.id); } - // We don't handle property updates of non-visible windows until they are mapped. + // We don't handle property updates of non-visible windows until they are + // mapped. assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && w->state != WSTATE_UNMAPPING); @@ -2198,8 +2292,8 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { // We think the top left of the border is the origin pixman_region32_translate(&br, w->g.border_width, w->g.border_width); - // Intersect the bounding region we got with the window rectangle, to - // make sure the bounding region is not bigger than the window + // Intersect the bounding region we got with the window rectangle, + // to make sure the bounding region is not bigger than the window // rectangle pixman_region32_intersect(&w->bounding_shape, &w->bounding_shape, &br); pixman_region32_fini(&br); @@ -2213,6 +2307,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { // Window shape changed, we should free old wpaint and shadow pict // log_trace("free out dated pict"); win_set_flags(w, WIN_FLAGS_IMAGES_STALE); + win_release_mask(ps->backend_data, w); ps->pending_updates = true; free_paint(ps, &w->paint); @@ -2255,7 +2350,8 @@ void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t for (int i = 0; i < 4; i++) { if (prop.c32[i] > (uint32_t)INT_MAX) { log_warn("Your window manager sets a absurd " - "_NET_FRAME_EXTENTS value (%u), ignoring it.", + "_NET_FRAME_EXTENTS value (%u), " + "ignoring it.", prop.c32[i]); memset(extents, 0, sizeof(extents)); break; @@ -2369,11 +2465,12 @@ static void destroy_win_finish(session_t *ps, struct win *w) { auto mw = (struct managed_win *)w; if (mw->state != WSTATE_UNMAPPED) { - // Only UNMAPPED state has window resources freed, otherwise - // we need to call unmap_win_finish to free them. - // XXX actually we unmap_win_finish only frees the rendering - // resources, we still need to call free_win_res. will fix - // later. + // Only UNMAPPED state has window resources freed, + // otherwise we need to call unmap_win_finish to free + // them. + // XXX actually we unmap_win_finish only frees the + // rendering resources, we still need to call free_win_res. + // will fix later. unmap_win_finish(ps, mw); } @@ -2382,6 +2479,7 @@ static void destroy_win_finish(session_t *ps, struct win *w) { assert(mw->shadow_image != NULL); win_release_shadow(ps->backend_data, mw); } + win_release_mask(ps->backend_data, mw); // Invalidate reg_ignore of windows below this one // TODO(yshui) what if next_w is not mapped?? @@ -2396,11 +2494,12 @@ static void destroy_win_finish(session_t *ps, struct win *w) { } if (mw == ps->active_win) { - // Usually, the window cannot be the focused at destruction. - // FocusOut should be generated before the window is destroyed. We - // do this check just to be completely sure we don't have dangling - // references. - log_debug("window %#010x (%s) is destroyed while being focused", + // Usually, the window cannot be the focused at + // destruction. FocusOut should be generated before the + // window is destroyed. We do this check just to be + // completely sure we don't have dangling references. + log_debug("window %#010x (%s) is destroyed while being " + "focused", w->id, mw->name); ps->active_win = NULL; } @@ -2433,11 +2532,13 @@ static inline void restack_win(session_t *ps, struct win *w, struct list_node *n } if (mw) { - // This invalidates all reg_ignore below the new stack position of `w` + // This invalidates all reg_ignore below the new stack position of + // `w` mw->reg_ignore_valid = false; rc_region_unref(&mw->reg_ignore); - // This invalidates all reg_ignore below the old stack position of `w` + // This invalidates all reg_ignore below the old stack position of + // `w` auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour); if (next_w) { next_w->reg_ignore_valid = false; @@ -2519,39 +2620,42 @@ bool destroy_win_start(session_t *ps, struct win *w) { log_debug("Destroying %#010x \"%s\", managed = %d", w->id, (w->managed ? mw->name : NULL), w->managed); - // Delete destroyed window from the hash table, even though the window might still - // be rendered for a while. We need to make sure future window with the same - // window id won't confuse us. Keep the window in the window stack if it's managed - // and mapped, since we might still need to render it (e.g. fading out). Window - // will be removed from the stack when it finishes destroying. + // Delete destroyed window from the hash table, even though the window + // might still be rendered for a while. We need to make sure future window + // with the same window id won't confuse us. Keep the window in the window + // stack if it's managed and mapped, since we might still need to render + // it (e.g. fading out). Window will be removed from the stack when it + // finishes destroying. HASH_DEL(ps->windows, w); if (!w->managed || mw->state == WSTATE_UNMAPPED) { - // Window is already unmapped, or is an unmanged window, just destroy it + // Window is already unmapped, or is an unmanged window, just + // destroy it destroy_win_finish(ps, w); return true; } if (w->managed) { // Clear IMAGES_STALE flags since the window is destroyed: Clear - // PIXMAP_STALE as there is no pixmap available anymore, so STALE doesn't - // make sense. - // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed window - // doesn't work leading to an inconsistent state where the shadow is - // refreshed but the flags are stuck in STALE. - // Do this before changing the window state to destroying + // PIXMAP_STALE as there is no pixmap available anymore, so STALE + // doesn't make sense. + // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed + // window doesn't work leading to an inconsistent state where the + // shadow is refreshed but the flags are stuck in STALE. Do this + // before changing the window state to destroying win_clear_flags(mw, WIN_FLAGS_IMAGES_STALE); - // If size/shape/position information is stale, win_process_update_flags - // will update them and add the new window extents to damage. Since the - // window has been destroyed, we cannot get the complete information at - // this point, so we just add what we currently have to the damage. + // If size/shape/position information is stale, + // win_process_update_flags will update them and add the new + // window extents to damage. Since the window has been destroyed, + // we cannot get the complete information at this point, so we + // just add what we currently have to the damage. if (win_check_flags_any(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) { add_damage_from_win(ps, mw); } - // Clear some flags about stale window information. Because now the window - // is destroyed, we can't update them anyway. + // Clear some flags about stale window information. Because now + // the window is destroyed, we can't update them anyway. win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE | WIN_FLAGS_PROPERTY_STALE | WIN_FLAGS_FACTOR_CHANGED | WIN_FLAGS_CLIENT_STALE); @@ -2598,7 +2702,8 @@ void unmap_win_start(session_t *ps, struct managed_win *w) { // Clear the pending map as this window is now unmapped win_clear_flags(w, WIN_FLAGS_MAPPED); } else { - log_warn("Trying to unmapping an already unmapped window %#010x " + log_warn("Trying to unmapping an already unmapped window " + "%#010x " "\"%s\"", w->base.id, w->name); assert(false); @@ -2648,10 +2753,10 @@ void unmap_win_start(session_t *ps, struct managed_win *w) { #endif if (!ps->redirected || !was_damaged) { - // If we are not redirected, we skip fading because we aren't rendering - // anything anyway. - // If the window wasn't ever damaged, it shouldn't be painted either. But - // a fading out window is always painted, so we have to skip fading here. + // If we are not redirected, we skip fading because we aren't + // rendering anything anyway. If the window wasn't ever damaged, + // it shouldn't be painted either. But a fading out window is + // always painted, so we have to skip fading here. CHECK(!win_skip_fading(ps, w)); } } @@ -2667,7 +2772,6 @@ bool win_check_fade_finished(session_t *ps, struct managed_win *w) { assert(w->opacity_target == w->opacity); return false; } - if (w->opacity == w->opacity_target) { switch (w->state) { case WSTATE_UNMAPPING: unmap_win_finish(ps, w); return false; @@ -2720,14 +2824,16 @@ void win_update_screen(int nscreens, region_t *screens, struct managed_win *w) { if (e->x1 <= w->g.x && e->y1 <= w->g.y && e->x2 >= w->g.x + w->widthb && e->y2 >= w->g.y + w->heightb) { w->xinerama_scr = i; - log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen %d " + log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen " + "%d " "(%dx%d+%dx%d)", w->base.id, w->name, w->g.x, w->g.y, w->widthb, w->heightb, i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1); return; } } - log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any screen", + log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any " + "screen", w->base.id, w->name, w->g.x, w->g.y, w->g.width, w->g.height); } @@ -2752,23 +2858,24 @@ void map_win_start(session_t *ps, struct managed_win *w) { if (w->state == WSTATE_UNMAPPING) { CHECK(!win_skip_fading(ps, w)); - // We skipped the unmapping process, the window was rendered, now it is - // not anymore. So we need to mark the then unmapping window as damaged. + // We skipped the unmapping process, the window was rendered, now + // it is not anymore. So we need to mark the then unmapping window + // as damaged. // - // Solves problem when, for example, a window is unmapped then mapped in a - // different location + // Solves problem when, for example, a window is unmapped then + // mapped in a different location add_damage_from_win(ps, w); assert(w); } assert(w->state == WSTATE_UNMAPPED); - // Rant: window size could change after we queried its geometry here and before - // we get its pixmap. Later, when we get back to the event processing loop, we - // will get the notification about size change from Xserver and try to refresh the - // pixmap, while the pixmap is actually already up-to-date (i.e. the notification - // is stale). There is basically no real way to prevent this, aside from grabbing - // the server. + // Rant: window size could change after we queried its geometry here and + // before we get its pixmap. Later, when we get back to the event + // processing loop, we will get the notification about size change from + // Xserver and try to refresh the pixmap, while the pixmap is actually + // already up-to-date (i.e. the notification is stale). There is basically + // no real way to prevent this, aside from grabbing the server. // XXX Can we assume map_state is always viewable? w->a.map_state = XCB_MAP_STATE_VIEWABLE; @@ -2788,9 +2895,9 @@ void map_win_start(session_t *ps, struct managed_win *w) { w->opacity, w->opacity_target); // Cannot set w->ever_damaged = false here, since window mapping could be - // delayed, so a damage event might have already arrived before this function - // is called. But this should be unnecessary in the first place, since - // ever_damaged is set to false in unmap_win_finish anyway. + // delayed, so a damage event might have already arrived before this + // function is called. But this should be unnecessary in the first place, + // since ever_damaged is set to false in unmap_win_finish anyway. // Sets the WIN_FLAGS_IMAGES_STALE flag so later in the critical section // the window's image will be bound @@ -2925,17 +3032,17 @@ struct managed_win *find_toplevel(session_t *ps, xcb_window_t id) { * @return struct _win object of the found window, NULL if not found */ struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid) { - // TODO(yshui) this should probably be an "update tree", then find_toplevel. - // current approach is a bit more "racy", as the server state might be ahead of - // our state + // TODO(yshui) this should probably be an "update tree", then + // find_toplevel. current approach is a bit more "racy", as the server + // state might be ahead of our state struct win *w = NULL; // We traverse through its ancestors to find out the frame - // Using find_win here because if we found a unmanaged window we know about, we - // can stop early. + // Using find_win here because if we found a unmanaged window we know + // about, we can stop early. while (wid && wid != ps->root && !(w = find_win(ps, wid))) { - // xcb_query_tree probably fails if you run picom when X is somehow - // initializing (like add it in .xinitrc). In this case + // xcb_query_tree probably fails if you run picom when X is + // somehow initializing (like add it in .xinitrc). In this case // just leave it alone. auto reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL); if (reply == NULL) { @@ -3101,7 +3208,8 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win * } /** - * Check if a window is focused, without using any focus rules or forced focus settings + * Check if a window is focused, without using any focus rules or forced focus + * settings */ bool win_is_focused_raw(const session_t *ps, const struct managed_win *w) { return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w; @@ -7,6 +7,8 @@ #include <xcb/render.h> #include <xcb/xcb.h> +#include <backend/backend.h> + #include "uthash_extra.h" // FIXME shouldn't need this @@ -105,6 +107,7 @@ struct managed_win { void *win_image; void *old_win_image; // Old window image for interpolating window contents during animations void *shadow_image; + void *mask_image; /// Pointer to the next higher window to paint. struct managed_win *prev_trans; /// Number of windows above this window @@ -140,7 +143,7 @@ struct managed_win { /// bitmap for properties which needs to be updated uint64_t *stale_props; /// number of uint64_ts that has been allocated for stale_props - uint64_t stale_props_capacity; + size_t stale_props_capacity; /// Bounding shape of the window. In local coordinates. /// See above about coordinate systems. @@ -171,6 +174,7 @@ struct managed_win { bool unredir_if_possible_excluded; /// Whether this window is in open/close state. bool in_openclose; + /// Whether this window was transient when animated on open bool animation_transient; /// Current position and destination, for animation @@ -225,7 +229,8 @@ struct managed_win { /// Previous window opacity. double opacity_target_old; /// true if window (or client window, for broken window managers - /// not transferring client window's _NET_WM_OPACITY value) has opacity prop + /// not transferring client window's _NET_WM_WINDOW_OPACITY value) has opacity + /// prop bool has_opacity_prop; /// Cached value of opacity window attribute. opacity_t opacity_prop; @@ -244,6 +249,9 @@ struct managed_win { /// Whether fading is excluded by the rules. Calculated. bool fade_excluded; + /// Whether transparent clipping is excluded by the rules. + bool transparent_clipping_excluded; + // Frame-opacity-related members /// Current window frame opacity. Affected by window opacity. double frame_opacity; @@ -269,7 +277,7 @@ struct managed_win { paint_t shadow_paint; /// The value of _COMPTON_SHADOW attribute of the window. Below 0 for /// none. - long prop_shadow; + long long prop_shadow; /// Do not paint shadow over this window. bool clip_shadow_above; @@ -286,6 +294,9 @@ struct managed_win { /// Whether to blur window background. bool blur_background; + /// The custom window shader to use when rendering. + struct shader_info *fg_shader; + #ifdef CONFIG_OPENGL /// Textures and FBO background blur use. glx_blur_cache_t glx_blur_cache; @@ -298,9 +309,10 @@ struct managed_win { /// section void win_process_update_flags(session_t *ps, struct managed_win *w); void win_process_image_flags(session_t *ps, struct managed_win *w); +bool win_bind_mask(struct backend_base *b, struct managed_win *w); /// Bind a shadow to the window, with color `c` and shadow kernel `kernel` bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c, - struct conv *kernel); + struct backend_shadow_context *kernel); /// Start the unmap of a window. We cannot unmap immediately since we might need to fade /// the window out. diff --git a/src/win_defs.h b/src/win_defs.h index e032bc7..bf2b7d8 100644 --- a/src/win_defs.h +++ b/src/win_defs.h @@ -96,7 +96,6 @@ enum win_flags { WIN_FLAGS_FACTOR_CHANGED = 1024, }; -static const uint64_t WIN_FLAGS_IMAGES_STALE = - WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE; +static const uint64_t WIN_FLAGS_IMAGES_STALE = WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE; #define WIN_FLAGS_IMAGES_NONE (WIN_FLAGS_PIXMAP_NONE | WIN_FLAGS_SHADOW_NONE) @@ -652,10 +652,6 @@ err: return false; } -// xcb-render specific macros -#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536) -#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) - /** * Convert a struct conv to a X picture convolution filter, normalizing the kernel * in the process. Allow the caller to specify the element at the center of the kernel, @@ -87,6 +87,10 @@ struct xvisual_info { #define log_fatal_x_error(e, fmt, ...) \ LOG(FATAL, fmt " (%s)", ##__VA_ARGS__, x_strerror(e)) +// xcb-render specific macros +#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536) +#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) + /// Wraps x_new_id. abort the program if x_new_id returns error static inline uint32_t x_new_id(xcb_connection_t *c) { auto ret = xcb_generate_id(c); |