aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorallusive-dev <[email protected]>2023-10-30 15:12:21 +1100
committerallusive-dev <[email protected]>2023-10-30 15:12:21 +1100
commitac33357e7ce7c474aeaffc92e381020289d767a2 (patch)
tree7f05fa79b3ccd7834f85cc65a07fbd4f8030eb94 /src
parentCreate FUNDING.yml (diff)
downloadcompfy-ac33357e7ce7c474aeaffc92e381020289d767a2.tar.xz
compfy-ac33357e7ce7c474aeaffc92e381020289d767a2.zip
Version 1.01.0.0
Diffstat (limited to 'src')
-rw-r--r--src/atom.c2
-rw-r--r--src/backend/backend.c118
-rw-r--r--src/backend/backend.h137
-rw-r--r--src/backend/backend_common.c62
-rw-r--r--src/backend/backend_common.h17
-rw-r--r--src/backend/dummy/dummy.c28
-rw-r--r--src/backend/gl/blur.c900
-rw-r--r--src/backend/gl/egl.c469
-rw-r--r--src/backend/gl/egl.h33
-rw-r--r--src/backend/gl/gl_common.c1479
-rw-r--r--src/backend/gl/gl_common.h100
-rw-r--r--src/backend/gl/glx.c51
-rw-r--r--src/backend/gl/glx.h1
-rw-r--r--src/backend/gl/shaders.c187
-rw-r--r--src/backend/meson.build2
-rw-r--r--src/backend/xrender/xrender.c382
-rw-r--r--src/c2.c48
-rw-r--r--src/c2.h20
-rw-r--r--src/cache.c2
-rw-r--r--src/common.h39
-rw-r--r--src/compiler.h8
-rw-r--r--src/config.c236
-rw-r--r--src/config.h45
-rw-r--r--src/config_libconfig.c268
-rw-r--r--src/dbus.c402
-rw-r--r--src/diagnostic.c8
-rw-r--r--src/event.c2
-rw-r--r--src/kernel.c19
-rw-r--r--src/kernel.h5
-rw-r--r--src/log.c2
-rw-r--r--src/meson.build2
-rw-r--r--src/options.c994
-rw-r--r--src/picom.c449
-rw-r--r--src/picom.h10
-rw-r--r--src/render.c8
-rw-r--r--src/string_utils.c28
-rw-r--r--src/string_utils.h1
-rw-r--r--src/utils.c3
-rw-r--r--src/utils.h68
-rw-r--r--src/win.c412
-rw-r--r--src/win.h20
-rw-r--r--src/win_defs.h3
-rw-r--r--src/x.c4
-rw-r--r--src/x.h4
44 files changed, 4575 insertions, 2503 deletions
diff --git a/src/atom.c b/src/atom.c
index 0272dc8..c5107ea 100644
--- a/src/atom.c
+++ b/src/atom.c
@@ -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(&region, &region, &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(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local,
- reg_visible, reg_paint);
+ reg_visible, reg_paint);
pixman_region32_translate(&reg_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(
&reg_visible_local, &reg_visible_local, &reg_bound_local);
- pixman_region32_fini(&reg_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
&reg_visible_local, (double[]){w->frame_opacity});
pixman_region32_fini(&reg_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(&reg_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},
&reg_paint, &reg_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,
&reg_paint_in_bound, &reg_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(&reg_blur, &reg_blur,
&reg_visible);
}
- ps->backend_data->ops->blur(ps->backend_data, blur_opacity,
- ps->backend_blur_context,
- &reg_blur, &reg_visible);
+ ps->backend_data->ops->blur(
+ ps->backend_data, blur_opacity, ps->backend_blur_context,
+ w->mask_image, window_coord, &reg_blur, &reg_visible);
pixman_region32_fini(&reg_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(&reg_shadow, &reg_shadow, &reg_paint);
- if (!ps->o.wintype_option[w->window_type].full_shadow) {
- pixman_region32_subtract(&reg_shadow, &reg_shadow, &reg_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(&reg_shadow, &reg_shadow,
+ &reg_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,
- &reg_shadow, &reg_visible);
+ ps->backend_data, w->shadow_image, shadow_coord,
+ inverted_mask, window_coord, &reg_shadow, &reg_visible);
+ if (inverted_mask) {
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_INVERTED,
+ inverted_mask, (bool[]){false});
+ }
pixman_region32_fini(&reg_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(&reg_shadow_clip, &reg_shadow_clip, &reg_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,
- &reg_paint_in_bound, &reg_visible);
+ window_coord, NULL, window_coord,
+ &reg_paint_in_bound, &reg_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(&reg, 0, 0, (unsigned int)width, (unsigned int)height);
+ void *mask = backend_data->ops->make_mask(
+ backend_data, (geometry_t){.width = width, .height = height}, &reg);
+ pixman_region32_fini(&reg);
+
+ 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(&reg_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(&reg_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(&reg_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(&reg_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(&reg_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(&reg_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(&reg_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}, &reg_blur, NULL,
+ source_texture,
+ (geometry_t){.width = new_inner->width, .height = new_inner->height},
+ fbo, gd->default_mask_texture);
+ pixman_region32_fini(&reg_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(&reg);
pixman_region32_intersect(&reg, (region_t *)reg_paint, (region_t *)reg_visible);
x_set_picture_clip_region(xd->base.c, result, 0, 0, &reg);
-
-#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), &reg);
+ x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x),
+ to_i16_checked(-dst.y), &reg);
// 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(&reg);
}
-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, &reg_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, &reg_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, &reg_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,
diff --git a/src/c2.c b/src/c2.c
index 3500f7b..80ecb24 100644
--- a/src/c2.c
+++ b/src/c2.c
@@ -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;
+}
diff --git a/src/c2.h b/src/c2.h
index d6b1d37..a7ddd39 100644
--- a/src/c2.h
+++ b/src/c2.h
@@ -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) {
diff --git a/src/dbus.c b/src/dbus.c
index 8d6094e..8b17b30 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -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
diff --git a/src/log.c b/src/log.c
index 0b663e7..c8b158b 100644
--- a/src/log.c
+++ b/src/log.c
@@ -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 :
diff --git a/src/win.c b/src/win.c
index 767efc1..116b2a2 100644
--- a/src/win.c
+++ b/src/win.c
@@ -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(&reg_bound_local, -w->g.x, -w->g.y);
+ w->mask_image = b->ops->make_mask(
+ b, (geometry_t){.width = w->widthb, .height = w->heightb}, &reg_bound_local);
+ pixman_region32_fini(&reg_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;
diff --git a/src/win.h b/src/win.h
index d3a74f9..366fd9e 100644
--- a/src/win.h
+++ b/src/win.h
@@ -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)
diff --git a/src/x.c b/src/x.c
index c146f48..795211d 100644
--- a/src/x.c
+++ b/src/x.c
@@ -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,
diff --git a/src/x.h b/src/x.h
index e01aa0a..3b8787c 100644
--- a/src/x.h
+++ b/src/x.h
@@ -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);