dwm picom added

This commit is contained in:
wt committed 2024-08-08 16:59:22 +07:00
1 parent 471b6062af
commit 964ff4b2c7
132 files changed
+32922

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
#include <string.h>
#include <xcb/xcb.h>
#include "atom.h"
#include "common.h"
#include "utils.h"
#include "log.h"
static inline void *atom_getter(void *ud, const char *atom_name, int *err) {
xcb_connection_t *c = ud;
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
c, xcb_intern_atom(c, 0, to_u16_checked(strlen(atom_name)), atom_name), NULL);
xcb_atom_t atom = XCB_NONE;
if (reply) {
log_debug("Atom %s is %d", atom_name, reply->atom);
atom = reply->atom;
free(reply);
} else {
log_error("Failed to intern atoms");
*err = 1;
}
return (void *)(intptr_t)atom;
}
/**
* Create a new atom structure and fetch all predefined atoms
*/
struct atom *init_atoms(xcb_connection_t *c) {
auto atoms = ccalloc(1, struct atom);
atoms->c = new_cache((void *)c, atom_getter, NULL);
#define ATOM_GET(x) atoms->a##x = (xcb_atom_t)(intptr_t)cache_get(atoms->c, #x, NULL)
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1);
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2);
#undef ATOM_GET
return atoms;
}
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <stdlib.h>
#include <xcb/xcb.h>
#include "meta.h"
#include "cache.h"
// clang-format off
// Splitted into 2 lists because of the limitation of our macros
#define ATOM_LIST1 \
_NET_WM_WINDOW_OPACITY, \
_NET_FRAME_EXTENTS, \
WM_STATE, \
_NET_WM_NAME, \
_NET_WM_PID, \
WM_NAME, \
WM_CLASS, \
WM_ICON_NAME, \
WM_TRANSIENT_FOR, \
WM_WINDOW_ROLE, \
WM_CLIENT_LEADER, \
WM_CLIENT_MACHINE, \
_NET_ACTIVE_WINDOW, \
_COMPTON_SHADOW, \
_NET_WM_WINDOW_TYPE, \
_NET_CURRENT_DESKTOP
#define ATOM_LIST2 \
_NET_WM_WINDOW_TYPE_DESKTOP, \
_NET_WM_WINDOW_TYPE_DOCK, \
_NET_WM_WINDOW_TYPE_TOOLBAR, \
_NET_WM_WINDOW_TYPE_MENU, \
_NET_WM_WINDOW_TYPE_UTILITY, \
_NET_WM_WINDOW_TYPE_SPLASH, \
_NET_WM_WINDOW_TYPE_DIALOG, \
_NET_WM_WINDOW_TYPE_NORMAL, \
_NET_WM_WINDOW_TYPE_DROPDOWN_MENU, \
_NET_WM_WINDOW_TYPE_POPUP_MENU, \
_NET_WM_WINDOW_TYPE_TOOLTIP, \
_NET_WM_WINDOW_TYPE_NOTIFICATION, \
_NET_WM_WINDOW_TYPE_COMBO, \
_NET_WM_WINDOW_TYPE_DND, \
_NET_WM_STATE, \
_NET_WM_STATE_FULLSCREEN, \
_NET_WM_BYPASS_COMPOSITOR, \
UTF8_STRING, \
C_STRING
// clang-format on
#define ATOM_DEF(x) xcb_atom_t a##x
struct atom {
struct cache *c;
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1);
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2);
};
struct atom *init_atoms(xcb_connection_t *);
static inline xcb_atom_t get_atom(struct atom *a, const char *key) {
return (xcb_atom_t)(intptr_t)cache_get(a->c, key, NULL);
}
static inline void destroy_atoms(struct atom *a) {
cache_free(a->c);
free(a);
}
+540
View File
@@ -0,0 +1,540 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <xcb/sync.h>
#include <xcb/xcb.h>
#include "backend/backend.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "log.h"
#include "region.h"
#include "types.h"
#include "win.h"
#include "x.h"
extern struct backend_operations xrender_ops, dummy_ops;
#ifdef CONFIG_OPENGL
extern struct backend_operations glx_ops;
#endif
struct backend_operations *backend_list[NUM_BKEND] = {
[BKEND_XRENDER] = &xrender_ops,
[BKEND_DUMMY] = &dummy_ops,
#ifdef CONFIG_OPENGL
[BKEND_GLX] = &glx_ops,
#endif
};
/**
* @param all_damage if true ignore damage and repaint the whole screen
*/
region_t get_damage(session_t *ps, bool all_damage) {
region_t region;
auto buffer_age_fn = ps->backend_data->ops->buffer_age;
int buffer_age = buffer_age_fn ? buffer_age_fn(ps->backend_data) : -1;
if (all_damage) {
buffer_age = -1;
}
pixman_region32_init(&region);
if (buffer_age == -1 || buffer_age > ps->ndamage) {
pixman_region32_copy(&region, &ps->screen_reg);
} 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: %td", i, curr);
dump_region(&ps->damage_ring[curr]);
pixman_region32_union(&region, &region, &ps->damage_ring[curr]);
}
pixman_region32_intersect(&region, &region, &ps->screen_reg);
}
return region;
}
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,
region_t *reg_paint, region_t *reg_paint_in_bound) {
// For window image processing, we don't have to limit the process
// region to damage for correctness. (see <damager-note> for
// details)
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// 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;
{
// The bounding shape, in window local coordinates
region_t reg_bound_local;
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
pixman_region32_init(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local, reg_visible, reg_paint);
pixman_region32_translate(&reg_visible_local, -w->g.x, -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(ps->backend_data, win_image,
&reg_visible_local);
auto reg_frame = win_get_region_frame_local_by_val(w);
double alpha = additional_alpha * w->opacity;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img, &alpha);
ps->backend_data->ops->image_op(ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img,
&reg_frame, &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);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
}
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);
}
/// 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 "
"disabled from now on.");
xcb_sync_destroy_fence(ps->c, ps->sync_fence);
ps->sync_fence = XCB_NONE;
ps->o.xrender_sync_fence = false;
ps->xsync_exists = false;
}
}
// All painting will be limited to the damage, if _some_ of
// the paints bleed out of the damage region, it will destroy
// part of the image we want to reuse
region_t reg_damage;
if (!ignore_damage) {
reg_damage = get_damage(ps, ps->o.monitor_repaint || !ps->o.use_damage);
} else {
pixman_region32_init(&reg_damage);
pixman_region32_copy(&reg_damage, &ps->screen_reg);
}
if (!pixman_region32_not_empty(&reg_damage)) {
pixman_region32_fini(&reg_damage);
return;
}
#ifdef DEBUG_REPAINT
static struct timespec last_paint = {0};
#endif
// <damage-note>
// If use_damage is enabled, we MUST make sure only the damaged regions of the
// screen are ever touched by the compositor. The reason is that at the beginning
// of each render, we clear the damaged regions with the wallpaper, and nothing
// else. If later during the render we changed anything outside the damaged
// region, that won't be cleared by the next render, and will thus accumulate.
// (e.g. if shadow is drawn outside the damaged region, it will become thicker and
// thicker over time.)
/// The adjusted damaged regions
region_t reg_paint;
assert(ps->o.blur_method != BLUR_METHOD_INVALID);
if (ps->o.blur_method != BLUR_METHOD_NONE && ps->backend_data->ops->get_blur_size) {
int blur_width, blur_height;
ps->backend_data->ops->get_blur_size(ps->backend_blur_context,
&blur_width, &blur_height);
// The region of screen a given window influences will be smeared
// out by blur. With more windows on top of the given window, the
// influences region will be smeared out more.
//
// Also, blurring requires data slightly outside the area that needs
// to be blurred. The more semi-transparent windows are stacked on top
// of each other, the larger the area will be.
//
// Instead of accurately calculate how much bigger the damage
// region will be because of blur, we assume the worst case here.
// That is, the damaged window is at the bottom of the stack, and
// all other windows have semi-transparent background
int resize_factor = 1;
if (t) {
resize_factor = t->stacking_rank;
}
resize_region_in_place(&reg_damage, blur_width * resize_factor,
blur_height * resize_factor);
reg_paint = resize_region(&reg_damage, blur_width * resize_factor,
blur_height * resize_factor);
pixman_region32_intersect(&reg_paint, &reg_paint, &ps->screen_reg);
pixman_region32_intersect(&reg_damage, &reg_damage, &ps->screen_reg);
} else {
pixman_region32_init(&reg_paint);
pixman_region32_copy(&reg_paint, &reg_damage);
}
// A hint to backend, the region that will be visible on screen
// backend can optimize based on this info
region_t reg_visible;
pixman_region32_init(&reg_visible);
pixman_region32_copy(&reg_visible, &ps->screen_reg);
if (t && !ps->o.transparent_clipping) {
// Calculate the region upon which the root window (wallpaper) is to be
// painted based on the ignore region of the lowest window, if available
//
// NOTE If transparent_clipping is enabled, transparent windows are
// included in the reg_ignore, but we still want to have the wallpaper
// beneath them, so we don't use reg_ignore for wallpaper in that case.
pixman_region32_subtract(&reg_visible, &reg_visible, t->reg_ignore);
}
// Region on screen we don't want any shadows on
region_t reg_shadow_clip;
pixman_region32_init(&reg_shadow_clip);
if (ps->backend_data->ops->prepare) {
ps->backend_data->ops->prepare(ps->backend_data, &reg_paint);
}
if (ps->root_image) {
ps->backend_data->ops->compose(ps->backend_data, ps->root_image, 0, 0,
ps->root_width, ps->root_height,
&reg_paint, &reg_visible);
} else {
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
&reg_paint);
}
// Windows are sorted from bottom to top
// Each window has a reg_ignore, which is the region obscured by all the windows
// on top of that window. This is used to reduce the number of pixels painted.
//
// Whether this is beneficial is to be determined XXX
for (auto w = t; w; w = w->prev_trans) {
pixman_region32_subtract(&reg_visible, &ps->screen_reg, w->reg_ignore);
assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR));
assert(!(w->flags & WIN_FLAGS_PIXMAP_STALE));
assert(!(w->flags & WIN_FLAGS_PIXMAP_NONE));
// 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);
// The clip region for the current window, in global/target coordinates
// reg_paint_in_bound \in reg_paint
region_t reg_paint_in_bound;
pixman_region32_init(&reg_paint_in_bound);
pixman_region32_intersect(&reg_paint_in_bound, &reg_bound, &reg_paint);
if (ps->o.transparent_clipping) {
// <transparent-clipping-note>
// If transparent_clipping is enabled, we need to be SURE that
// things are not drawn inside reg_ignore, because otherwise they
// will appear underneath transparent windows.
// So here we have make sure reg_paint_in_bound \in reg_visible
// There are a few other places below where this is needed as
// well.
pixman_region32_intersect(&reg_paint_in_bound,
&reg_paint_in_bound, &reg_visible);
}
// Blur window background
/* TODO(yshui) since the backend might change the content of the window
* (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;
if (w->blur_background &&
(ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
(ps->o.blur_background_frame && real_win_mode == WMODE_FRAME_TRANS))) {
// Minimize the region we try to blur, if the window
// itself is not opaque, only the frame is.
double blur_opacity = 1;
if (w->opacity < (1.0 / MAX_ALPHA)) {
// Hide blur for fully transparent windows.
blur_opacity = 0;
} else if (w->state == WSTATE_MAPPING) {
// Gradually increase the blur intensity during
// fading in.
assert(w->opacity <= w->opacity_target);
blur_opacity = w->opacity / w->opacity_target;
} else if (w->state == WSTATE_UNMAPPING ||
w->state == WSTATE_DESTROYING) {
// Gradually decrease the blur intensity during
// fading out.
assert(w->opacity <= w->opacity_target_old);
blur_opacity = w->opacity / w->opacity_target_old;
} else if (w->state == WSTATE_FADING) {
if (w->opacity < w->opacity_target &&
w->opacity_target_old < (1.0 / MAX_ALPHA)) {
// Gradually increase the blur intensity during
// fading in.
assert(w->opacity <= w->opacity_target);
blur_opacity = w->opacity / w->opacity_target;
} else if (w->opacity > w->opacity_target &&
w->opacity_target < (1.0 / MAX_ALPHA)) {
// Gradually decrease the blur intensity during
// fading out.
assert(w->opacity <= w->opacity_target_old);
blur_opacity = w->opacity / w->opacity_target_old;
}
}
assert(blur_opacity >= 0 && blur_opacity <= 1);
if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) {
// 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,
&reg_paint_in_bound, &reg_visible);
} else {
// Window itself is solid, we only need to blur the frame
// region
// Readability assertions
assert(ps->o.blur_background_frame);
assert(real_win_mode == WMODE_FRAME_TRANS);
auto reg_blur = win_get_region_frame_local_by_val(w);
pixman_region32_translate(&reg_blur, w->g.x, w->g.y);
// make sure reg_blur \in reg_paint
pixman_region32_intersect(&reg_blur, &reg_blur, &reg_paint);
if (ps->o.transparent_clipping) {
// ref: <transparent-clipping-note>
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);
pixman_region32_fini(&reg_blur);
}
}
// Draw shadow on target
if (w->shadow) {
assert(!(w->flags & WIN_FLAGS_SHADOW_NONE));
// Clip region for the shadow
// 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_no_corner);
}
// Mask out the region we don't want shadow on
if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) {
pixman_region32_subtract(&reg_shadow, &reg_shadow,
&ps->shadow_exclude_reg);
}
if (pixman_region32_not_empty(&reg_shadow_clip)) {
pixman_region32_subtract(&reg_shadow, &reg_shadow,
&reg_shadow_clip);
}
if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 &&
w->xinerama_scr < ps->xinerama_nscrs) {
// There can be a window where number of screens is
// updated, but the screen number attached to the windows
// have not.
//
// Window screen number will be updated eventually, so
// here we just check to make sure we don't access out of
// bounds.
pixman_region32_intersect(
&reg_shadow, &reg_shadow,
&ps->xinerama_scr_regs[w->xinerama_scr]);
}
if (ps->o.transparent_clipping) {
// ref: <transparent-clipping-note>
pixman_region32_intersect(&reg_shadow, &reg_shadow,
&reg_visible);
}
assert(w->shadow_image);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, w->shadow_image,
&w->opacity);
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);
pixman_region32_fini(&reg_shadow);
}
// Update image properties
{
double dim_opacity = 0.0;
if (w->dim) {
dim_opacity = ps->o.inactive_dim;
if (!ps->o.inactive_dim_fixed) {
dim_opacity *= w->opacity;
}
}
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_MAX_BRIGHTNESS, w->win_image,
&ps->o.max_brightness);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_INVERTED, w->win_image,
&w->invert_color);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_DIM_LEVEL, w->win_image,
&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);
if (w->old_win_image) {
// TODO(dccsillag): explain why the following is
// "necessary"
double zero = 0.0;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
w->old_win_image, &zero);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS,
w->old_win_image, &zero);
}
}
}
if (w->opacity * MAX_ALPHA < 1) {
// We don't need to paint the window body itself if it's
// completely transparent.
goto skip;
}
if (w->clip_shadow_above) {
// Add window bounds to shadow-clip region
pixman_region32_union(&reg_shadow_clip, &reg_shadow_clip, &reg_bound);
} else {
// Remove overlapping window bounds from shadow-clip region
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;
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);
} else {
process_window_for_painting(
ps, w, w->win_image, 1.0, &reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
if (w->old_win_image) {
bool resizing =
w->g.width != w->pending_g.width ||
w->g.height != w->pending_g.height;
// Only do this if size changes as otherwise moving
// transparent windows will flicker
if (resizing) {
assert(w->old_win_image);
process_window_for_painting(ps, w, w->old_win_image,
1.0 - w->animation_progress,
&reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
}
}
}
skip:
pixman_region32_fini(&reg_bound);
pixman_region32_fini(&reg_bound_no_corner);
pixman_region32_fini(&reg_paint_in_bound);
}
pixman_region32_fini(&reg_paint);
pixman_region32_fini(&reg_shadow_clip);
if (ps->o.monitor_repaint) {
const struct color DEBUG_COLOR = {0.5, 0, 0, 0.5};
auto reg_damage_debug = get_damage(ps, false);
ps->backend_data->ops->fill(ps->backend_data, DEBUG_COLOR, &reg_damage_debug);
pixman_region32_fini(&reg_damage_debug);
}
// Move the head of the damage ring
ps->damage = ps->damage - 1;
if (ps->damage < ps->damage_ring) {
ps->damage = ps->damage_ring + ps->ndamage - 1;
}
pixman_region32_clear(ps->damage);
if (ps->backend_data->ops->present) {
// Present the rendered scene
// Vsync is done here
ps->backend_data->ops->present(ps->backend_data, &reg_damage);
}
pixman_region32_fini(&reg_damage);
#ifdef DEBUG_REPAINT
struct timespec now = get_time_timespec();
struct timespec diff = {0};
timespec_subtract(&diff, &now, &last_paint);
log_trace("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec);
last_paint = now;
log_trace("paint:");
for (win *w = t; w; w = w->prev_trans)
log_trace(" %#010lx", w->id);
#endif
}
// vim: set noet sw=8 ts=8 :
+294
View File
@@ -0,0 +1,294 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018, Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <stdbool.h>
#include "compiler.h"
#include "config.h"
#include "driver.h"
#include "kernel.h"
#include "region.h"
#include "types.h"
#include "x.h"
typedef struct session session_t;
struct managed_win;
struct ev_loop;
struct backend_operations;
typedef struct backend_base {
struct backend_operations *ops;
xcb_connection_t *c;
xcb_window_t root;
struct ev_loop *loop;
/// Whether the backend can accept new render request at the moment
bool busy;
// ...
} backend_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 could be applied in any order)
enum image_properties {
// Whether the color of the image is inverted
// 1 boolean, default: false
IMAGE_PROPERTY_INVERTED,
// How much the image is dimmed
// 1 double, default: 0
IMAGE_PROPERTY_DIM_LEVEL,
// Image opacity, i.e. an alpha value multiplied to the alpha channel
// 1 double, default: 1
IMAGE_PROPERTY_OPACITY,
// The effective size of the image, the image will be tiled to fit.
// 2 int, default: the actual size of the image
IMAGE_PROPERTY_EFFECTIVE_SIZE,
// Limit how bright image can be. The image brightness is estimated by averaging
// the pixels in the image, and dimming will be applied to scale the average
// 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,
};
enum image_operations {
// Multiply the alpha channel by the argument
IMAGE_OP_APPLY_ALPHA,
};
struct gaussian_blur_args {
int size;
double deviation;
};
struct box_blur_args {
int size;
};
struct kernel_blur_args {
struct conv **kernels;
int kernel_count;
};
struct dual_kawase_blur_args {
int size;
int strength;
};
struct backend_operations {
// =========== Initialization ===========
/// Initialize the backend, prepare for rendering to the target window.
/// Here is how you should choose target window:
/// 1) if ps->overlay is not XCB_NONE, use that
/// 2) use ps->root otherwise
// TODO(yshui) make the target window a parameter
backend_t *(*init)(session_t *)attr_nonnull(1);
void (*deinit)(backend_t *backend_data) attr_nonnull(1);
/// Called when rendering will be stopped for an unknown amount of
/// time (e.g. when screen is unredirected). Free some resources.
///
/// Optional, not yet used
void (*pause)(backend_t *backend_data, session_t *ps);
/// Called before rendering is resumed
///
/// Optional, not yet used
void (*resume)(backend_t *backend_data, session_t *ps);
/// Called when root property changed, returns the new
/// backend_data. Even if the backend_data changed, all
/// the existing image data returned by this backend should
/// remain valid.
///
/// Optional
void *(*root_change)(backend_t *backend_data, session_t *ps);
// =========== Rendering ============
// NOTE: general idea about reg_paint/reg_op vs reg_visible is that reg_visible is
// merely a hint. Ignoring reg_visible entirely don't affect the correctness of
// the operation performed. OTOH reg_paint/reg_op is part of the parameters of the
// operation, and must be honored in order to complete the operation correctly.
// NOTE: due to complications introduced by use-damage and blur, the rendering API
// is a bit weird. The idea is, `compose` and `blur` have to update a temporary
// buffer, because `blur` requires data from an area slightly larger than the area
// that will be visible. So the area outside the visible area has to be rendered,
// but we have to discard the result (because the result of blurring that area
// will be wrong). That's why we cannot render into the back buffer directly.
// After rendering is done, `present` is called to update a portion of the actual
// back buffer, then present it to the target (or update the target directly,
// if not back buffered).
/// Called before when a new frame starts.
///
/// Optional
void (*prepare)(backend_t *backend_data, const region_t *reg_damage);
/**
* 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
*/
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);
/// 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);
/// 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
/// target window directly).
///
/// Optional, if NULL, indicates the backend doesn't have render output
///
/// @param region part of the target that should be updated
void (*present)(backend_t *backend_data, const region_t *region) attr_nonnull(1, 2);
/**
* Bind a X pixmap to the backend's internal image data structure.
*
* @param backend_data backend data
* @param pixmap X pixmap to bind
* @param fmt information of the pixmap's visual
* @param owned whether the ownership of the pixmap is transfered to the backend
* @return backend internal data structure bound with this pixmap
*/
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
/// Default implementation: default_backend_render_shadow
void *(*render_shadow)(backend_t *backend_data, int width, int height,
const conv *kernel, double r, double g, double b, double a);
// ============ Resource management ===========
/// Free resources associated with an image data structure
void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2);
// =========== Query ===========
/// Return if image is not completely opaque.
///
/// This function is needed because some backend might change the content of the
/// window (e.g. when using a custom shader with the glx backend), so only the
/// backend knows if an image is transparent.
bool (*is_image_transparent)(backend_t *backend_data, void *image_data)
attr_nonnull(1, 2);
/// Get the age of the buffer content we are currently rendering ontop
/// of. The buffer that has just been `present`ed has a buffer age of 1.
/// Everytime `present` is called, buffers get older. Return -1 if the
/// buffer is empty.
///
/// Optional
int (*buffer_age)(backend_t *backend_data);
/// The maximum number buffer_age might return.
int max_buffer_age;
// =========== Post-processing ============
/* TODO(yshui) Consider preserving the order of image ops.
* Currently in both backends, the image ops are applied lazily when needed.
* However neither backends preserve the order of image ops, they just applied all
* pending lazy ops in a pre-determined fixed order, regardless in which order
* they were originally applied. This might lead to inconsistencies.*/
/**
* Change image properties
*
* @param backend_data backend data
* @param prop the property to change
* @param image_data an image data structure returned by the backend
* @param args property value
* @return whether the operation is successful
*/
bool (*set_image_property)(backend_t *backend_data, enum image_properties prop,
void *image_data, void *args);
/**
* Manipulate an image. Image properties are untouched.
*
* @param backend_data backend data
* @param op the operation to perform
* @param image_data an image data structure returned by the backend
* @param reg_op the clip region, define the part of the image to be
* operated on.
* @param reg_visible define the part of the image that will eventually
* be visible on target. this is a hint to the backend
* for optimization purposes.
* @param args extra arguments, operation specific
* @return whether the operation is successful
*/
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);
/// 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
void *(*clone_image)(backend_t *base, const void *image_data,
const region_t *reg_visible);
/// Create a blur context that can be used to call `blur`
void *(*create_blur_context)(backend_t *base, enum blur_method, void *args);
/// Destroy a blur context
void (*destroy_blur_context)(backend_t *base, void *ctx);
/// Get how many pixels outside of the blur area is needed for blur
void (*get_blur_size)(void *blur_context, int *width, int *height);
// =========== Hooks ============
/// Let the backend hook into the event handling queue
/// Not implemented yet
void (*set_ready_callback)(backend_t *, backend_ready_callback_t cb);
/// Called right after the core has handled its events.
/// Not implemented yet
void (*handle_events)(backend_t *);
// =========== Misc ============
/// Return the driver that is been used by the backend
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[];
void paint_all_new(session_t *ps, struct managed_win *const t, bool ignore_damage)
attr_nonnull(1);
+483
View File
@@ -0,0 +1,483 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <math.h>
#include <string.h>
#include <xcb/render.h>
#include <xcb/xcb_image.h>
#include <xcb/xcb_renderutil.h>
#include "backend/backend.h"
#include "backend/backend_common.h"
#include "common.h"
#include "config.h"
#include "kernel.h"
#include "log.h"
#include "utils.h"
#include "win.h"
#include "x.h"
/**
* Generate a 1x1 <code>Picture</code> of a particular color.
*/
xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool argb,
double a, double r, double g, double b) {
xcb_pixmap_t pixmap;
xcb_render_picture_t picture;
xcb_render_create_picture_value_list_t pa;
xcb_render_color_t col;
xcb_rectangle_t rect;
pixmap = x_create_pixmap(c, argb ? 32 : 8, d, 1, 1);
if (!pixmap)
return XCB_NONE;
pa.repeat = 1;
picture = x_create_picture_with_standard_and_pixmap(
c, argb ? XCB_PICT_STANDARD_ARGB_32 : XCB_PICT_STANDARD_A_8, pixmap,
XCB_RENDER_CP_REPEAT, &pa);
if (!picture) {
xcb_free_pixmap(c, pixmap);
return XCB_NONE;
}
col.alpha = (uint16_t)(a * 0xffff);
col.red = (uint16_t)(r * 0xffff);
col.green = (uint16_t)(g * 0xffff);
col.blue = (uint16_t)(b * 0xffff);
rect.x = 0;
rect.y = 0;
rect.width = 1;
rect.height = 1;
xcb_render_fill_rectangles(c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect);
xcb_free_pixmap(c, pixmap);
return picture;
}
xcb_image_t *
make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height) {
/*
* We classify shadows into 4 kinds of regions
* r = shadow radius
* (0, 0) is the top left of the window itself
* -r r width-r width+r
* -r +-----+---------+-----+
* | 1 | 2 | 1 |
* r +-----+---------+-----+
* | 2 | 3 | 2 |
* height-r +-----+---------+-----+
* | 1 | 2 | 1 |
* height+r +-----+---------+-----+
*/
xcb_image_t *ximage;
const double *shadow_sum = kernel->rsum;
assert(shadow_sum);
// We only support square kernels for shadow
assert(kernel->w == kernel->h);
int d = kernel->w;
int r = d / 2;
int swidth = width + r * 2, sheight = height + r * 2;
assert(d % 2 == 1);
assert(d > 0);
ximage = xcb_image_create_native(c, to_u16_checked(swidth), to_u16_checked(sheight),
XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL);
if (!ximage) {
log_error("failed to create an X image");
return 0;
}
unsigned char *data = ximage->data;
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) {
for (int y = 0; y < sheight; y++) {
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 * opacity);
}
}
return ximage;
}
if (height < r * 2) {
// Implies width >= r * 2
// If the window height is smaller than the kernel, we divide
// the window like this:
// -r r width-r width+r
// +------+-------------+------+
// | | | |
// +------+-------------+------+
for (int y = 0; y < sheight; y++) {
for (int x = 0; x < r * 2; x++) {
double sum = sum_kernel_normalized(kernel, d - x - 1,
d - y - 1, d, height) *
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 * opacity;
memset(&data[y * sstride + r * 2], (uint8_t)sum,
(size_t)(width - 2 * r));
}
return ximage;
}
if (width < r * 2) {
// Similarly, for width smaller than kernel
for (int y = 0; y < r * 2; y++) {
for (int x = 0; x < swidth; x++) {
double sum = sum_kernel_normalized(kernel, d - x - 1,
d - y - 1, width, d) *
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 * opacity;
for (int y = r * 2; y < height; y++) {
data[y * sstride + x] = (uint8_t)sum;
}
}
return ximage;
}
// Implies: width >= r * 2 && height >= r * 2
// Fill part 3
for (int y = r; y < height + r; y++) {
memset(data + sstride * y + r, (uint8_t)(255 * opacity), (size_t)width);
}
// Part 1
for (int y = 0; y < r * 2; y++) {
for (int x = 0; x < r * 2; x++) {
double tmpsum = shadow_sum[y * d + x] * opacity * 255.0;
data[y * sstride + x] = (uint8_t)tmpsum;
data[(sheight - y - 1) * sstride + x] = (uint8_t)tmpsum;
data[(sheight - y - 1) * sstride + (swidth - x - 1)] = (uint8_t)tmpsum;
data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum;
}
}
// Part 2, top/bottom
for (int y = 0; y < r * 2; y++) {
double tmpsum = shadow_sum[d * y + d - 1] * opacity * 255.0;
memset(&data[y * sstride + r * 2], (uint8_t)tmpsum, (size_t)(width - r * 2));
memset(&data[(sheight - y - 1) * sstride + r * 2], (uint8_t)tmpsum,
(size_t)(width - r * 2));
}
// Part 2, left/right
for (int x = 0; x < r * 2; x++) {
double tmpsum = shadow_sum[d * (d - 1) + x] * opacity * 255.0;
for (int y = r * 2; y < height; y++) {
data[y * sstride + x] = (uint8_t)tmpsum;
data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum;
}
}
return ximage;
}
/**
* Generate shadow <code>Picture</code> for a window.
*/
bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const int width,
const int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
xcb_pixmap_t *pixmap, xcb_render_picture_t *pict) {
xcb_image_t *shadow_image = NULL;
xcb_pixmap_t shadow_pixmap = XCB_NONE, shadow_pixmap_argb = XCB_NONE;
xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE;
xcb_gcontext_t gc = XCB_NONE;
shadow_image = make_shadow(c, kernel, opacity, width, height);
if (!shadow_image) {
log_error("Failed to make shadow");
return false;
}
shadow_pixmap = x_create_pixmap(c, 8, d, shadow_image->width, shadow_image->height);
shadow_pixmap_argb =
x_create_pixmap(c, 32, d, shadow_image->width, shadow_image->height);
if (!shadow_pixmap || !shadow_pixmap_argb) {
log_error("Failed to create shadow pixmaps");
goto shadow_picture_err;
}
shadow_picture = x_create_picture_with_standard_and_pixmap(
c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL);
shadow_picture_argb = x_create_picture_with_standard_and_pixmap(
c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL);
if (!shadow_picture || !shadow_picture_argb) {
goto shadow_picture_err;
}
gc = x_new_id(c);
xcb_create_gc(c, gc, shadow_pixmap, 0, NULL);
// We need to make room for protocol metadata in the request. The metadata should
// be 24 bytes plus padding, let's be generous and give it 1kb
auto maximum_image_size = xcb_get_maximum_request_length(c) * 4 - 1024;
auto maximum_row =
to_u16_checked(clamp(maximum_image_size / shadow_image->stride, 0, UINT16_MAX));
if (maximum_row <= 0) {
// TODO(yshui) Upload image with XShm
log_error("X server request size limit is too restrictive, or the shadow "
"image is too wide for us to send a single row of the shadow "
"image. Shadow size: %dx%d",
width, height);
goto shadow_picture_err;
}
for (uint32_t row = 0; row < shadow_image->height; row += maximum_row) {
auto batch_height = maximum_row;
if (batch_height > shadow_image->height - row) {
batch_height = to_u16_checked(shadow_image->height - row);
}
uint32_t offset = row * shadow_image->stride / sizeof(*shadow_image->data);
xcb_put_image(c, (uint8_t)shadow_image->format, shadow_pixmap, gc,
shadow_image->width, batch_height, 0, to_i16_checked(row),
0, shadow_image->depth, shadow_image->stride * batch_height,
shadow_image->data + offset);
}
xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture,
shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width,
shadow_image->height);
*pixmap = shadow_pixmap_argb;
*pict = shadow_picture_argb;
xcb_free_gc(c, gc);
xcb_image_destroy(shadow_image);
xcb_free_pixmap(c, shadow_pixmap);
xcb_render_free_picture(c, shadow_picture);
return true;
shadow_picture_err:
if (shadow_image) {
xcb_image_destroy(shadow_image);
}
if (shadow_pixmap) {
xcb_free_pixmap(c, shadow_pixmap);
}
if (shadow_pixmap_argb) {
xcb_free_pixmap(c, shadow_pixmap_argb);
}
if (shadow_picture) {
xcb_render_free_picture(c, shadow_picture);
}
if (shadow_picture_argb) {
xcb_render_free_picture(c, shadow_picture_argb);
}
if (gc) {
xcb_free_gc(c, gc);
}
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),
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)) {
return NULL;
}
auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32);
void *ret = backend_data->ops->bind_pixmap(
backend_data, shadow, x_get_visual_info(backend_data->c, visual), true);
xcb_render_free_picture(backend_data->c, pict);
return ret;
}
static struct conv **generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) {
int r = args->size * 2 + 1;
assert(r > 0);
auto ret = ccalloc(2, struct conv *);
ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
ret[0]->w = r;
ret[0]->h = 1;
ret[1]->w = 1;
ret[1]->h = r;
for (int i = 0; i < r; i++) {
ret[0]->data[i] = 1;
ret[1]->data[i] = 1;
}
*kernel_count = 2;
return ret;
}
static struct conv **
generate_gaussian_blur_kernel(struct gaussian_blur_args *args, int *kernel_count) {
int r = args->size * 2 + 1;
assert(r > 0);
auto ret = ccalloc(2, struct conv *);
ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
ret[0]->w = r;
ret[0]->h = 1;
ret[1]->w = 1;
ret[1]->h = r;
for (int i = 0; i <= args->size; i++) {
ret[0]->data[i] = ret[0]->data[r - i - 1] =
1.0 / (sqrt(2.0 * M_PI) * args->deviation) *
exp(-(args->size - i) * (args->size - i) /
(2 * args->deviation * args->deviation));
ret[1]->data[i] = ret[1]->data[r - i - 1] = ret[0]->data[i];
}
*kernel_count = 2;
return ret;
}
/// Generate blur kernels for gaussian and box blur methods. Generated kernel is not
/// normalized, and the center element will always be 1.
struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count) {
switch (method) {
case BLUR_METHOD_BOX: return generate_box_blur_kernel(args, kernel_count);
case BLUR_METHOD_GAUSSIAN:
return generate_gaussian_blur_kernel(args, kernel_count);
default: break;
}
return NULL;
}
/// Generate kernel parameters for dual-kawase blur method. Falls back on approximating
/// standard gauss radius if strength is zero or below.
struct dual_kawase_params *generate_dual_kawase_params(void *args) {
struct dual_kawase_blur_args *blur_args = args;
static const struct {
int iterations; /// Number of down- and upsample iterations
float offset; /// Sample offset in half-pixels
int min_radius; /// Approximate gauss-blur with at least this
/// radius and std-deviation
} strength_levels[20] = {
{.iterations = 1, .offset = 1.25f, .min_radius = 1}, // LVL 1
{.iterations = 1, .offset = 2.25f, .min_radius = 6}, // LVL 2
{.iterations = 2, .offset = 2.00f, .min_radius = 11}, // LVL 3
{.iterations = 2, .offset = 3.00f, .min_radius = 17}, // LVL 4
{.iterations = 2, .offset = 4.25f, .min_radius = 24}, // LVL 5
{.iterations = 3, .offset = 2.50f, .min_radius = 32}, // LVL 6
{.iterations = 3, .offset = 3.25f, .min_radius = 40}, // LVL 7
{.iterations = 3, .offset = 4.25f, .min_radius = 51}, // LVL 8
{.iterations = 3, .offset = 5.50f, .min_radius = 67}, // LVL 9
{.iterations = 4, .offset = 3.25f, .min_radius = 83}, // LVL 10
{.iterations = 4, .offset = 4.00f, .min_radius = 101}, // LVL 11
{.iterations = 4, .offset = 5.00f, .min_radius = 123}, // LVL 12
{.iterations = 4, .offset = 6.00f, .min_radius = 148}, // LVL 13
{.iterations = 4, .offset = 7.25f, .min_radius = 178}, // LVL 14
{.iterations = 4, .offset = 8.25f, .min_radius = 208}, // LVL 15
{.iterations = 5, .offset = 4.50f, .min_radius = 236}, // LVL 16
{.iterations = 5, .offset = 5.25f, .min_radius = 269}, // LVL 17
{.iterations = 5, .offset = 6.25f, .min_radius = 309}, // LVL 18
{.iterations = 5, .offset = 7.25f, .min_radius = 357}, // LVL 19
{.iterations = 5, .offset = 8.50f, .min_radius = 417}, // LVL 20
};
auto params = ccalloc(1, struct dual_kawase_params);
params->iterations = 0;
params->offset = 1.0f;
if (blur_args->strength <= 0 && blur_args->size) {
// find highest level that approximates blur-strength with the selected
// gaussian blur-radius
int lvl = 1;
while (strength_levels[lvl - 1].min_radius < blur_args->size && lvl < 20) {
++lvl;
}
blur_args->strength = lvl;
}
if (blur_args->strength <= 0) {
// default value
blur_args->strength = 5;
}
assert(blur_args->strength > 0 && blur_args->strength <= 20);
params->iterations = strength_levels[blur_args->strength - 1].iterations;
params->offset = strength_levels[blur_args->strength - 1].offset;
// Expand sample area to cover the smallest texture / highest selected iteration:
// - Smallest texture dimensions are halved `iterations`-times
// - Upsample needs pixels two-times `offset` away from the border
// - Plus one for interpolation differences
params->expand = (1 << params->iterations) * 2 * (int)ceil(params->offset) + 1;
return params;
}
void *default_clone_image(backend_t *base attr_unused, const void *image_data,
const region_t *reg_visible attr_unused) {
auto new_img = ccalloc(1, struct backend_image);
*new_img = *(struct backend_image *)image_data;
new_img->inner->refcount++;
return new_img;
}
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
void *image_data, void *arg) {
struct backend_image *tex = image_data;
int *iargs = arg;
bool *bargs = arg;
double *dargs = arg;
switch (op) {
case IMAGE_PROPERTY_INVERTED: tex->color_inverted = bargs[0]; break;
case IMAGE_PROPERTY_DIM_LEVEL: tex->dim = dargs[0]; break;
case IMAGE_PROPERTY_OPACITY: tex->opacity = dargs[0]; break;
case IMAGE_PROPERTY_EFFECTIVE_SIZE:
// texture is already set to repeat, so nothing else we need to do
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;
}
return true;
}
bool default_is_image_transparent(backend_t *base attr_unused, void *image_data) {
struct backend_image *img = image_data;
return img->opacity < 1 || img->inner->has_alpha;
}
struct backend_image *default_new_backend_image(int w, int h) {
auto ret = ccalloc(1, struct backend_image);
ret->opacity = 1;
ret->dim = 0;
ret->max_brightness = 1;
ret->eheight = h;
ret->ewidth = w;
ret->color_inverted = false;
ret->corner_radius = 0;
return ret;
}
void init_backend_base(struct backend_base *base, session_t *ps) {
base->c = ps->c;
base->loop = ps->loop;
base->root = ps->root;
base->busy = false;
base->ops = NULL;
}
+80
View File
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <xcb/render.h>
#include <xcb/xcb_image.h>
#include <stdbool.h>
#include "backend.h"
#include "config.h"
#include "region.h"
typedef struct session session_t;
typedef struct win win;
typedef struct conv conv;
typedef struct backend_base backend_t;
struct backend_operations;
struct dual_kawase_params {
/// Number of downsample passes
int iterations;
/// Pixel offset for down- and upsample
float offset;
/// Save area around blur target (@ref resize_width, @ref resize_height)
int expand;
};
struct backend_image_inner_base {
int refcount;
bool has_alpha;
};
struct backend_image {
// Backend dependent inner image data
struct backend_image_inner_base *inner;
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,
int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
xcb_pixmap_t *pixmap, xcb_render_picture_t *pict);
xcb_render_picture_t solid_picture(xcb_connection_t *, xcb_drawable_t, bool argb,
double a, double r, double g, double b);
xcb_image_t *
make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height);
/// The default implementation of `is_win_transparent`, it simply looks at win::mode. So
/// this is not suitable for backends that alter the content of windows
bool default_is_win_transparent(void *, win *, void *);
/// The default implementation of `is_frame_transparent`, it uses win::frame_opacity. Same
/// 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,
const conv *kernel, double r, double g, double b, double a);
void init_backend_base(struct backend_base *base, session_t *ps);
struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count);
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);
struct backend_image *default_new_backend_image(int w, int h);
+82
View File
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <stdlib.h>
#include <string.h>
#include <xcb/randr.h>
#include <xcb/xcb.h>
#include "backend/backend.h"
#include "backend/driver.h"
#include "common.h"
#include "compiler.h"
#include "log.h"
/// Apply driver specified global workarounds. It's safe to call this multiple times.
void apply_driver_workarounds(struct session *ps, enum driver driver) {
if (driver & DRIVER_NVIDIA) {
// setenv("__GL_YIELD", "usleep", true);
setenv("__GL_MaxFramesAllowed", "1", true);
ps->o.xrender_sync_fence = true;
}
}
enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {
enum driver ret = 0;
// First we try doing backend agnostic detection using RANDR
// There's no way to query the X server about what driver is loaded, so RANDR is
// our best shot.
auto randr_version = xcb_randr_query_version_reply(
c, xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION),
NULL);
if (randr_version &&
(randr_version->major_version > 1 || randr_version->minor_version >= 4)) {
auto r = xcb_randr_get_providers_reply(
c, xcb_randr_get_providers(c, window), NULL);
if (r == NULL) {
log_warn("Failed to get RANDR providers");
free(randr_version);
return 0;
}
auto providers = xcb_randr_get_providers_providers(r);
for (auto i = 0; i < xcb_randr_get_providers_providers_length(r); i++) {
auto r2 = xcb_randr_get_provider_info_reply(
c, xcb_randr_get_provider_info(c, providers[i], r->timestamp), NULL);
if (r2 == NULL) {
continue;
}
if (r2->num_outputs == 0) {
free(r2);
continue;
}
auto name_len = xcb_randr_get_provider_info_name_length(r2);
assert(name_len >= 0);
auto name =
strndup(xcb_randr_get_provider_info_name(r2), (size_t)name_len);
if (strcasestr(name, "modesetting") != NULL) {
ret |= DRIVER_MODESETTING;
} else if (strcasestr(name, "Radeon") != NULL) {
// Be conservative, add both radeon drivers
ret |= DRIVER_AMDGPU | DRIVER_RADEON;
} else if (strcasestr(name, "NVIDIA") != NULL) {
ret |= DRIVER_NVIDIA;
} else if (strcasestr(name, "nouveau") != NULL) {
ret |= DRIVER_NOUVEAU;
} else if (strcasestr(name, "Intel") != NULL) {
ret |= DRIVER_INTEL;
}
free(name);
free(r2);
}
free(r);
}
free(randr_version);
// If the backend supports driver detection, use that as well
if (backend_data && backend_data->ops->detect_driver) {
ret |= backend_data->ops->detect_driver(backend_data);
}
return ret;
}
+62
View File
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <stddef.h>
#include <stdio.h>
#include <xcb/xcb.h>
#include "utils.h"
struct session;
struct backend_base;
// A list of known driver quirks:
// * NVIDIA driver doesn't like seeing the same pixmap under different
// ids, so avoid naming the pixmap again when it didn't actually change.
/// A list of possible drivers.
/// The driver situation is a bit complicated. There are two drivers we care about: the
/// DDX, and the OpenGL driver. They are usually paired, but not always, since there is
/// also the generic modesetting driver.
/// This enum represents _both_ drivers.
enum driver {
DRIVER_AMDGPU = 1, // AMDGPU for DDX, radeonsi for OpenGL
DRIVER_RADEON = 2, // ATI for DDX, mesa r600 for OpenGL
DRIVER_FGLRX = 4,
DRIVER_NVIDIA = 8,
DRIVER_NOUVEAU = 16,
DRIVER_INTEL = 32,
DRIVER_MODESETTING = 64,
};
static const char *driver_names[] = {
"AMDGPU", "Radeon", "fglrx", "NVIDIA", "nouveau", "Intel", "modesetting",
};
/// Return a list of all drivers currently in use by the X server.
/// Note, this is a best-effort test, so no guarantee all drivers will be detected.
enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_t);
/// Apply driver specified global workarounds. It's safe to call this multiple times.
void apply_driver_workarounds(struct session *ps, enum driver);
// Print driver names to stdout, for diagnostics
static inline void print_drivers(enum driver drivers) {
const char *seen_drivers[ARR_SIZE(driver_names)];
int driver_count = 0;
for (size_t i = 0; i < ARR_SIZE(driver_names); i++) {
if (drivers & (1ul << i)) {
seen_drivers[driver_count++] = driver_names[i];
}
}
if (driver_count > 0) {
printf("%s", seen_drivers[0]);
for (int i = 1; i < driver_count; i++) {
printf(", %s", seen_drivers[i]);
}
}
printf("\n");
}
+174
View File
@@ -0,0 +1,174 @@
#include <uthash.h>
#include <xcb/xcb.h>
#include "backend/backend.h"
#include "backend/backend_common.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "log.h"
#include "region.h"
#include "types.h"
#include "uthash_extra.h"
#include "utils.h"
#include "x.h"
struct dummy_image {
xcb_pixmap_t pixmap;
bool transparent;
int *refcount;
UT_hash_handle hh;
};
struct dummy_data {
struct backend_base base;
struct dummy_image *images;
};
struct backend_base *dummy_init(struct session *ps attr_unused) {
auto ret = (struct backend_base *)ccalloc(1, struct dummy_data);
ret->c = ps->c;
ret->loop = ps->loop;
ret->root = ps->root;
ret->busy = false;
return ret;
}
void dummy_deinit(struct backend_base *data) {
auto dummy = (struct dummy_data *)data;
HASH_ITER2(dummy->images, img) {
log_warn("Backend image for pixmap %#010x is not freed", img->pixmap);
HASH_DEL(dummy->images, img);
free(img->refcount);
free(img);
}
free(dummy);
}
static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) {
auto dummy = (struct dummy_data *)base;
struct dummy_image *tmp = NULL;
HASH_FIND_INT(dummy->images, &img->pixmap, tmp);
if (!tmp) {
log_warn("Using an invalid (possibly freed) image");
assert(false);
}
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) {
dummy_check_image(base, image);
}
void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused,
const region_t *clip attr_unused) {
}
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,
const region_t *reg_visible attr_unused) {
return true;
}
void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned attr_unused) {
auto dummy = (struct dummy_data *)base;
struct dummy_image *img = NULL;
HASH_FIND_INT(dummy->images, &pixmap, img);
if (img) {
(*img->refcount)++;
return img;
}
img = ccalloc(1, struct dummy_image);
img->pixmap = pixmap;
img->transparent = fmt.alpha_size != 0;
img->refcount = ccalloc(1, int);
*img->refcount = 1;
HASH_ADD_INT(dummy->images, pixmap, img);
return (void *)img;
}
void dummy_release_image(backend_t *base, void *image) {
auto dummy = (struct dummy_data *)base;
auto img = (struct dummy_image *)image;
assert(*img->refcount > 0);
(*img->refcount)--;
if (*img->refcount == 0) {
HASH_DEL(dummy->images, img);
free(img->refcount);
free(img);
}
}
bool dummy_is_image_transparent(struct backend_base *base, void *image) {
auto img = (struct dummy_image *)image;
dummy_check_image(base, img);
return img->transparent;
}
int dummy_buffer_age(struct backend_base *base attr_unused) {
return 2;
}
bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused,
void *image, const region_t *reg_op attr_unused,
const region_t *reg_visible attr_unused, void *args attr_unused) {
dummy_check_image(base, image);
return true;
}
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);
return true;
}
void *dummy_clone_image(struct backend_base *base, const void *image,
const region_t *reg_visible attr_unused) {
auto img = (const struct dummy_image *)image;
dummy_check_image(base, img);
(*img->refcount)++;
return (void *)img;
}
void *dummy_create_blur_context(struct backend_base *base attr_unused,
enum blur_method method attr_unused, void *args attr_unused) {
static int dummy_context;
return &dummy_context;
}
void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx attr_unused) {
}
void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) {
// These numbers are arbitrary, to make sure the reisze_region code path is
// covered.
*width = 5;
*height = 5;
}
struct backend_operations dummy_ops = {
.init = dummy_init,
.deinit = dummy_deinit,
.compose = dummy_compose,
.fill = dummy_fill,
.blur = dummy_blur,
.bind_pixmap = dummy_bind_pixmap,
.render_shadow = default_backend_render_shadow,
.release_image = dummy_release_image,
.is_image_transparent = dummy_is_image_transparent,
.buffer_age = dummy_buffer_age,
.max_buffer_age = 5,
.image_op = dummy_image_op,
.clone_image = dummy_clone_image,
.set_image_property = dummy_set_image_property,
.create_blur_context = dummy_create_blur_context,
.destroy_blur_context = dummy_destroy_blur_context,
.get_blur_size = dummy_get_blur_size,
};
File diff suppressed because it is too large. Load diff
+238
View File
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdbool.h>
#include <string.h>
#include "backend/backend.h"
#include "log.h"
#include "region.h"
#define CASESTRRET(s) \
case s: return #s
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 {
GLuint prog;
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;
} gl_win_shader_t;
// Program and uniforms for brightness shader
typedef struct {
GLuint prog;
} gl_brightness_shader_t;
// Program and uniforms for blur shader
typedef struct {
GLuint prog;
GLint uniform_pixel_norm;
GLint uniform_opacity;
GLint texorig_loc;
GLint scale_loc;
} gl_blur_shader_t;
typedef struct {
GLuint prog;
GLint color_loc;
} gl_fill_shader_t;
/// @brief Wrapper of a binded GLX texture.
struct gl_texture {
int refcount;
bool has_alpha;
GLuint texture;
int width, height;
bool y_inverted;
// Textures for auxiliary uses.
GLuint auxiliary_texture[2];
void *user_data;
};
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;
// Height and width of the root window
int height, width;
gl_win_shader_t win_shader;
gl_brightness_shader_t brightness_shader;
gl_fill_shader_t fill_shader;
GLuint back_texture, back_fbo;
GLuint present_prog;
/// 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);
/// Release the user data attached to a gl_texture
void (*release_user_data)(backend_t *base, struct gl_texture *);
struct log_target *logger;
};
typedef struct session session_t;
#define GL_PROG_MAIN_INIT \
{ .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, }
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);
/**
* @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_resize(struct gl_data *, int width, int height);
bool gl_init(struct gl_data *gd, session_t *);
void gl_deinit(struct gl_data *gd);
GLuint gl_new_texture(GLenum target);
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_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);
void *gl_create_blur_context(backend_t *base, enum blur_method, void *args);
void gl_destroy_blur_context(backend_t *base, void *ctx);
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);
}
/**
* Get a textual representation of an OpenGL error.
*/
static inline const char *gl_get_err_str(GLenum err) {
switch (err) {
CASESTRRET(GL_NO_ERROR);
CASESTRRET(GL_INVALID_ENUM);
CASESTRRET(GL_INVALID_VALUE);
CASESTRRET(GL_INVALID_OPERATION);
CASESTRRET(GL_INVALID_FRAMEBUFFER_OPERATION);
CASESTRRET(GL_OUT_OF_MEMORY);
CASESTRRET(GL_STACK_UNDERFLOW);
CASESTRRET(GL_STACK_OVERFLOW);
CASESTRRET(GL_FRAMEBUFFER_UNDEFINED);
CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER);
CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER);
CASESTRRET(GL_FRAMEBUFFER_UNSUPPORTED);
CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE);
CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS);
}
return NULL;
}
/**
* Check for GLX error.
*
* http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/
*/
static inline void gl_check_err_(const char *func, int line) {
GLenum err = GL_NO_ERROR;
while (GL_NO_ERROR != (err = glGetError())) {
const char *errtext = gl_get_err_str(err);
if (errtext) {
log_printf(tls_logger, LOG_LEVEL_ERROR, func,
"GLX error at line %d: %s", line, errtext);
} else {
log_printf(tls_logger, LOG_LEVEL_ERROR, func,
"GLX error at line %d: %d", line, err);
}
}
}
static inline void gl_clear_err(void) {
while (glGetError() != GL_NO_ERROR)
;
}
#define gl_check_err() gl_check_err_(__func__, __LINE__)
/**
* Check for GL framebuffer completeness.
*/
static inline bool gl_check_fb_complete_(const char *func, int line, GLenum fb) {
GLenum status = glCheckFramebufferStatus(fb);
if (status == GL_FRAMEBUFFER_COMPLETE) {
return true;
}
const char *stattext = gl_get_err_str(status);
if (stattext) {
log_printf(tls_logger, LOG_LEVEL_ERROR, func,
"Framebuffer attachment failed at line %d: %s", line, stattext);
} else {
log_printf(tls_logger, LOG_LEVEL_ERROR, func,
"Framebuffer attachment failed at line %d: %d", line, status);
}
return false;
}
#define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb))
/**
* Check if a GLX extension exists.
*/
static inline bool gl_has_extension(const char *ext) {
int nexts = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &nexts);
for (int i = 0; i < nexts || !nexts; i++) {
const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i);
if (exti == NULL) {
break;
}
if (strcmp(ext, exti) == 0) {
return true;
}
}
gl_clear_err();
log_info("Missing GL extension %s.", ext);
return false;
}
+654
View File
@@ -0,0 +1,654 @@
// SPDX-License-Identifier: MIT
/*
* Compton - a compositor for X11
*
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2011-2013, Christopher Jeffrey
* Copyright (c) 2019 Yuxuan Shui <yshuiv7@gmail.com>
* See LICENSE-mit for more information.
*
*/
#include <X11/Xlib-xcb.h>
#include <assert.h>
#include <limits.h>
#include <pixman.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/composite.h>
#include <xcb/xcb.h>
#include "backend/backend.h"
#include "backend/backend_common.h"
#include "backend/gl/gl_common.h"
#include "backend/gl/glx.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "log.h"
#include "picom.h"
#include "region.h"
#include "utils.h"
#include "win.h"
#include "x.h"
struct _glx_pixmap {
GLXPixmap glpixmap;
xcb_pixmap_t pixmap;
bool owned;
};
struct _glx_data {
struct gl_data gl;
Display *display;
int screen;
xcb_window_t target_win;
GLXContext ctx;
};
#define glXGetFBConfigAttribChecked(a, b, attr, c) \
do { \
if (glXGetFBConfigAttrib(a, b, attr, c)) { \
log_info("Cannot get FBConfig attribute " #attr); \
continue; \
} \
} while (0)
struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvisual_info m) {
log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth %d", m.red_size,
m.blue_size, m.green_size, m.alpha_size, m.visual_depth);
int ncfg;
// clang-format off
GLXFBConfig *cfg =
glXChooseFBConfig(dpy, screen, (int[]){
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
GLX_X_RENDERABLE, true,
GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, (GLint)GLX_DONT_CARE,
GLX_BUFFER_SIZE, m.red_size + m.green_size +
m.blue_size + m.alpha_size,
GLX_RED_SIZE, m.red_size,
GLX_BLUE_SIZE, m.blue_size,
GLX_GREEN_SIZE, m.green_size,
GLX_ALPHA_SIZE, m.alpha_size,
GLX_STENCIL_SIZE, 0,
GLX_DEPTH_SIZE, 0,
0
}, &ncfg);
// clang-format on
int texture_tgts, y_inverted, texture_fmt;
bool found = false;
int min_cost = INT_MAX;
GLXFBConfig ret;
for (int i = 0; i < ncfg; i++) {
int depthbuf, stencil, doublebuf, bufsize;
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BUFFER_SIZE, &bufsize);
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DEPTH_SIZE, &depthbuf);
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_STENCIL_SIZE, &stencil);
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DOUBLEBUFFER, &doublebuf);
if (depthbuf + stencil + bufsize * (doublebuf + 1) >= min_cost) {
continue;
}
int red, green, blue;
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_RED_SIZE, &red);
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BLUE_SIZE, &blue);
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_GREEN_SIZE, &green);
if (red != m.red_size || green != m.green_size || blue != m.blue_size) {
// Color size doesn't match, this cannot work
continue;
}
int rgb, rgba;
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &rgb);
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &rgba);
if (!rgb && !rgba) {
log_info("FBConfig is neither RGBA nor RGB, we cannot "
"handle this setup.");
continue;
}
int visual;
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_VISUAL_ID, &visual);
if (m.visual_depth != -1 &&
x_get_visual_depth(XGetXCBConnection(dpy), (xcb_visualid_t)visual) !=
m.visual_depth) {
// FBConfig and the correspondent X Visual might not have the same
// depth. (e.g. 32 bit FBConfig with a 24 bit Visual). This is
// quite common, seen in both open source and proprietary drivers.
//
// If the FBConfig has a matching depth but its visual doesn't, we
// still cannot use it.
continue;
}
// All check passed, we are using this one.
found = true;
ret = cfg[i];
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT,
&texture_tgts);
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_Y_INVERTED_EXT, &y_inverted);
// Prefer the texture format with matching alpha, with the other one as
// fallback
if (m.alpha_size) {
texture_fmt = rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT
: GLX_TEXTURE_FORMAT_RGB_EXT;
} else {
texture_fmt =
rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT;
}
min_cost = depthbuf + stencil + bufsize * (doublebuf + 1);
}
free(cfg);
if (!found) {
return NULL;
}
auto info = cmalloc(struct glx_fbconfig_info);
info->cfg = ret;
info->texture_tgts = texture_tgts;
info->texture_fmt = texture_fmt;
info->y_inverted = y_inverted;
return info;
}
/**
* Free a glx_texture_t.
*/
static void glx_release_image(backend_t *base, struct gl_texture *tex) {
struct _glx_data *gd = (void *)base;
struct _glx_pixmap *p = tex->user_data;
// Release binding
if (p->glpixmap && tex->texture) {
glBindTexture(GL_TEXTURE_2D, tex->texture);
glXReleaseTexImageEXT(gd->display, p->glpixmap, GLX_FRONT_LEFT_EXT);
glBindTexture(GL_TEXTURE_2D, 0);
}
// Free GLX Pixmap
if (p->glpixmap) {
glXDestroyPixmap(gd->display, p->glpixmap);
p->glpixmap = 0;
}
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 glx_deinit(backend_t *base) {
struct _glx_data *gd = (void *)base;
gl_deinit(&gd->gl);
// Destroy GLX context
if (gd->ctx) {
glXMakeCurrent(gd->display, None, NULL);
glXDestroyContext(gd->display, gd->ctx);
gd->ctx = 0;
}
free(gd);
}
static void *glx_decouple_user_data(backend_t *base attr_unused, void *ud attr_unused) {
auto ret = cmalloc(struct _glx_pixmap);
ret->owned = false;
ret->glpixmap = 0;
ret->pixmap = 0;
return ret;
}
static bool glx_set_swap_interval(int interval, Display *dpy, GLXDrawable drawable) {
bool vsync_enabled = false;
if (glxext.has_GLX_MESA_swap_control) {
vsync_enabled = (glXSwapIntervalMESA((uint)interval) == 0);
}
if (!vsync_enabled && glxext.has_GLX_SGI_swap_control) {
vsync_enabled = (glXSwapIntervalSGI(interval) == 0);
}
if (!vsync_enabled && glxext.has_GLX_EXT_swap_control) {
// glXSwapIntervalEXT doesn't return if it's successful
glXSwapIntervalEXT(dpy, drawable, interval);
vsync_enabled = true;
}
return vsync_enabled;
}
/**
* Initialize OpenGL.
*/
static backend_t *glx_init(session_t *ps) {
bool success = false;
glxext_init(ps->dpy, ps->scr);
auto gd = ccalloc(1, struct _glx_data);
init_backend_base(&gd->gl.base, ps);
gd->display = ps->dpy;
gd->screen = ps->scr;
gd->target_win = session_get_target_window(ps);
XVisualInfo *pvis = NULL;
// Check for GLX extension
if (!ps->glx_exists) {
log_error("No GLX extension.");
goto end;
}
// Get XVisualInfo
int nitems = 0;
XVisualInfo vreq = {.visualid = ps->vis};
pvis = XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems);
if (!pvis) {
log_error("Failed to acquire XVisualInfo for current visual.");
goto end;
}
// Ensure the visual is double-buffered
int value = 0;
if (glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) {
log_error("Root visual is not a GL visual.");
goto end;
}
if (glXGetConfig(ps->dpy, pvis, GLX_STENCIL_SIZE, &value) || !value) {
log_error("Root visual lacks stencil buffer.");
goto end;
}
if (glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) {
log_error("Root visual is not a double buffered GL visual.");
goto end;
}
if (glXGetConfig(ps->dpy, pvis, GLX_RGBA, &value) || !value) {
log_error("Root visual is a color index visual, not supported");
goto end;
}
if (!glxext.has_GLX_EXT_texture_from_pixmap) {
log_error("GLX_EXT_texture_from_pixmap is not supported by your driver");
goto end;
}
if (!glxext.has_GLX_ARB_create_context) {
log_error("GLX_ARB_create_context is not supported by your driver");
goto end;
}
// Find a fbconfig with visualid matching the one from the target win, so we can
// be sure that the fbconfig is compatible with our target window.
int ncfgs;
GLXFBConfig *cfg = glXGetFBConfigs(gd->display, gd->screen, &ncfgs);
bool found = false;
for (int i = 0; i < ncfgs; i++) {
int visualid;
glXGetFBConfigAttribChecked(gd->display, cfg[i], GLX_VISUAL_ID, &visualid);
if ((VisualID)visualid != pvis->visualid) {
continue;
}
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) {
log_error("Failed to get GLX context.");
goto end;
}
found = true;
break;
}
if (!found) {
log_error("Couldn't find a suitable fbconfig for the target window");
goto end;
}
// Attach GLX context
GLXDrawable tgt = gd->target_win;
if (!glXMakeCurrent(ps->dpy, tgt, 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;
}
gd->gl.decouple_texture_user_data = glx_decouple_user_data;
gd->gl.release_user_data = glx_release_image;
if (ps->o.vsync) {
if (!glx_set_swap_interval(1, ps->dpy, tgt)) {
log_error("Failed to enable vsync.");
}
} else {
glx_set_swap_interval(0, ps->dpy, tgt);
}
success = true;
end:
if (pvis) {
XFree(pvis);
}
if (!success) {
glx_deinit(&gd->gl.base);
return NULL;
}
return &gd->gl.base;
}
static void *
glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
struct _glx_data *gd = (void *)base;
struct _glx_pixmap *glxpixmap = NULL;
// Retrieve pixmap parameters, if they aren't provided
if (fmt.visual_depth > OPENGL_MAX_DEPTH) {
log_error("Requested depth %d higher than max possible depth %d.",
fmt.visual_depth, OPENGL_MAX_DEPTH);
return false;
}
if (fmt.visual_depth < 0) {
log_error("Pixmap %#010x with invalid depth %d", pixmap, fmt.visual_depth);
return false;
}
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);
auto fbcfg = glx_find_fbconfig(gd->display, gd->screen, fmt);
if (!fbcfg) {
log_error("Couldn't find FBConfig with requested visual %x", fmt.visual);
goto err;
}
// Choose a suitable texture target for our pixmap.
// Refer to GLX_EXT_texture_om_pixmap spec to see what are the mean
// of the bits in texture_tgts
if (!(fbcfg->texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) {
log_error("Cannot bind pixmap to GL_TEXTURE_2D, giving up");
goto err;
}
log_debug("depth %d, rgba %d", fmt.visual_depth,
(fbcfg->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT));
GLint attrs[] = {
GLX_TEXTURE_FORMAT_EXT,
fbcfg->texture_fmt,
GLX_TEXTURE_TARGET_EXT,
GLX_TEXTURE_2D_EXT,
0,
};
inner->y_inverted = fbcfg->y_inverted;
glxpixmap = cmalloc(struct _glx_pixmap);
glxpixmap->pixmap = pixmap;
glxpixmap->glpixmap = glXCreatePixmap(gd->display, fbcfg->cfg, pixmap, attrs);
glxpixmap->owned = owned;
free(fbcfg);
if (!glxpixmap->glpixmap) {
log_error("Failed to create glpixmap for pixmap %#010x", pixmap);
goto err;
}
log_trace("GLXPixmap %#010lx", glxpixmap->glpixmap);
// Create texture
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);
glBindTexture(GL_TEXTURE_2D, 0);
gl_check_err();
return wd;
err:
if (glxpixmap && glxpixmap->glpixmap) {
glXDestroyPixmap(gd->display, glxpixmap->glpixmap);
}
free(glxpixmap);
if (owned) {
xcb_free_pixmap(base->c, pixmap);
}
free(wd);
return NULL;
}
static void glx_present(backend_t *base, const region_t *region attr_unused) {
struct _glx_data *gd = (void *)base;
gl_present(base, region);
glXSwapBuffers(gd->display, gd->target_win);
if (!gd->gl.is_nvidia) {
glFinish();
}
}
static int glx_buffer_age(backend_t *base) {
if (!glxext.has_GLX_EXT_buffer_age) {
return -1;
}
struct _glx_data *gd = (void *)base;
unsigned int val;
glXQueryDrawable(gd->display, gd->target_win, GLX_BACK_BUFFER_AGE_EXT, &val);
return (int)val ?: -1;
}
static void glx_diagnostics(backend_t *base) {
struct _glx_data *gd = (void *)base;
bool warn_software_rendering = false;
const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"};
auto glx_vendor = glXGetClientString(gd->display, GLX_VENDOR);
printf("* Driver vendors:\n");
printf(" * GLX: %s\n", glx_vendor);
printf(" * GL: %s\n", glGetString(GL_VENDOR));
auto gl_renderer = (const char *)glGetString(GL_RENDERER);
printf("* GL renderer: %s\n", gl_renderer);
if (strcmp(glx_vendor, "Mesa Project and SGI")) {
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;
}
}
}
#ifdef GLX_MESA_query_renderer
if (glxext.has_GLX_MESA_query_renderer) {
unsigned int accelerated = 0;
glXQueryCurrentRendererIntegerMESA(GLX_RENDERER_ACCELERATED_MESA, &accelerated);
printf("* Accelerated: %d\n", accelerated);
// Trust GLX_MESA_query_renderer when it's available
warn_software_rendering = (accelerated == 0);
}
#endif
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 glx_ops = {
.init = glx_init,
.deinit = glx_deinit,
.bind_pixmap = glx_bind_pixmap,
.release_image = gl_release_image,
.compose = gl_compose,
.image_op = gl_image_op,
.set_image_property = default_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,
.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,
.max_buffer_age = 5, // Why?
};
/**
* Check if a GLX extension exists.
*/
static inline bool glx_has_extension(Display *dpy, int screen, const char *ext) {
const char *glx_exts = glXQueryExtensionsString(dpy, screen);
if (!glx_exts) {
log_error("Failed get GLX extension list.");
return false;
}
auto inlen = strlen(ext);
const char *curr = glx_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 GLX extension %s.", ext);
} else {
log_info("Found GLX extension %s.", ext);
}
return match;
}
struct glxext_info glxext = {0};
PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
#ifdef GLX_MESA_query_renderer
PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
#endif
void glxext_init(Display *dpy, int screen) {
if (glxext.initialized) {
return;
}
glxext.initialized = true;
#define check_ext(name) glxext.has_##name = glx_has_extension(dpy, screen, #name)
check_ext(GLX_SGI_video_sync);
check_ext(GLX_SGI_swap_control);
check_ext(GLX_OML_sync_control);
check_ext(GLX_MESA_swap_control);
check_ext(GLX_EXT_swap_control);
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
#undef check_ext
#define lookup(name) (name = (__typeof__(name))glXGetProcAddress((GLubyte *)#name))
// Checking if the returned function pointer is NULL is not really necessary,
// or maybe not even useful, since glXGetProcAddress might always return
// something. We are doing it just for completeness' sake.
if (!lookup(glXGetVideoSyncSGI) || !lookup(glXWaitVideoSyncSGI)) {
glxext.has_GLX_SGI_video_sync = false;
}
if (!lookup(glXSwapIntervalEXT)) {
glxext.has_GLX_EXT_swap_control = false;
}
if (!lookup(glXSwapIntervalMESA)) {
glxext.has_GLX_MESA_swap_control = false;
}
if (!lookup(glXSwapIntervalSGI)) {
glxext.has_GLX_SGI_swap_control = false;
}
if (!lookup(glXWaitForMscOML) || !lookup(glXGetSyncValuesOML)) {
glxext.has_GLX_OML_sync_control = false;
}
if (!lookup(glXBindTexImageEXT) || !lookup(glXReleaseTexImageEXT)) {
glxext.has_GLX_EXT_texture_from_pixmap = false;
}
if (!lookup(glXCreateContextAttribsARB)) {
glxext.has_GLX_ARB_create_context = false;
}
#ifdef GLX_MESA_query_renderer
if (!lookup(glXQueryCurrentRendererIntegerMESA)) {
glxext.has_GLX_MESA_query_renderer = false;
}
#endif
#undef lookup
}
+78
View File
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <stdbool.h>
// Older version of glx.h defines function prototypes for these extensions...
// Rename them to avoid conflicts
#define glXSwapIntervalMESA glXSwapIntervalMESA_
#define glXBindTexImageEXT glXBindTexImageEXT_
#define glXReleaseTexImageEXT glXReleaseTexImageEXT
#include <GL/glx.h>
#undef glXSwapIntervalMESA
#undef glXBindTexImageEXT
#undef glXReleaseTexImageEXT
#include <X11/Xlib.h>
#include <xcb/xcb.h>
#include <xcb/render.h>
#include "log.h"
#include "compiler.h"
#include "utils.h"
#include "x.h"
struct glx_fbconfig_info {
GLXFBConfig cfg;
int texture_tgts;
int texture_fmt;
int y_inverted;
};
/// The search criteria for glx_find_fbconfig
struct glx_fbconfig_criteria {
/// Bit width of the red component
int red_size;
/// Bit width of the green component
int green_size;
/// Bit width of the blue component
int blue_size;
/// Bit width of the alpha component
int alpha_size;
/// The depth of X visual
int visual_depth;
};
struct glx_fbconfig_info *glx_find_fbconfig(Display *, int screen, struct xvisual_info);
struct glxext_info {
bool initialized;
bool has_GLX_SGI_video_sync;
bool has_GLX_SGI_swap_control;
bool has_GLX_OML_sync_control;
bool has_GLX_MESA_swap_control;
bool has_GLX_EXT_swap_control;
bool has_GLX_EXT_texture_from_pixmap;
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;
extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
#ifdef GLX_MESA_query_renderer
extern PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
#endif
void glxext_init(Display *, int screen);
+7
View File
@@ -0,0 +1,7 @@
# enable xrender
srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backend.c', 'driver.c') ]
# enable opengl
if get_option('opengl')
srcs += [ files('gl/gl_common.c', 'gl/glx.c') ]
endif
+889
View File
@@ -0,0 +1,889 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/composite.h>
#include <xcb/present.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb.h>
#include "backend/backend.h"
#include "backend/backend_common.h"
#include "common.h"
#include "config.h"
#include "kernel.h"
#include "log.h"
#include "picom.h"
#include "region.h"
#include "types.h"
#include "utils.h"
#include "win.h"
#include "x.h"
typedef struct _xrender_data {
backend_t base;
/// If vsync is enabled and supported by the current system
bool vsync;
xcb_visualid_t default_visual;
/// Target window
xcb_window_t target_win;
/// Painting target, it is either the root or the overlay
xcb_render_picture_t target;
/// Back buffers. Double buffer, with 1 for temporary render use
xcb_render_picture_t back[3];
/// The back buffer that is for temporary use
/// Age of each back buffer.
int buffer_age[3];
/// The back buffer we should be painting into
int curr_back;
/// The corresponding pixmap to the back buffer
xcb_pixmap_t back_pixmap[3];
/// Pictures of pixel of different alpha value, used as a mask to
/// paint transparent images
xcb_render_picture_t alpha_pict[256];
// XXX don't know if these are really needed
/// 1x1 white picture
xcb_render_picture_t white_pixel;
/// 1x1 black picture
xcb_render_picture_t black_pixel;
/// Width and height of the target pixmap
int target_width, target_height;
xcb_special_event_t *present_event;
} xrender_data;
struct _xrender_blur_context {
enum blur_method method;
/// Blur kernels converted to X format
struct x_convolution_kernel **x_blur_kernel;
int resize_width, resize_height;
/// Number of blur kernels
int x_blur_kernel_count;
};
struct _xrender_image_data_inner {
// struct backend_image_inner_base
int refcount;
bool has_alpha;
// Pixmap that the client window draws to,
// it will contain the content of client window.
xcb_pixmap_t pixmap;
// A Picture links to the Pixmap
xcb_render_picture_t pict;
int width, height;
xcb_visualid_t visual;
uint8_t depth;
// Whether we own this image, e.g. we allocated it;
// or not, e.g. this is a named pixmap of a X window.
bool owned;
};
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 void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg,
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) {
const struct backend_image *img = &xrimg->base;
auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
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 xcb_render_color_t dim_color = {
.red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)};
// Clip region of rendered_pict might be set during rendering, clear it to
// make sure we get everything into the buffer
x_clear_picture_clip_region(xd->base.c, inner->pict);
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->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.
auto tmp_pict =
x_create_picture_with_visual(xd->base.c, xd->base.root, inner->width,
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);
// Copy source -> tmp
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, inner->pict,
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,
tmp_pict, XCB_NONE, tmp_pict2, 0, 0,
0, 0, 0, 0, tmpw, tmph);
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, tmp_pict,
0, 0, 0, 0, 0, 0, tmpw, tmph);
xcb_render_composite(
xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict2,
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
xcb_render_free_picture(xd->base.c, tmp_pict2);
} else {
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, tmp_pict,
0, 0, 0, 0, 0, 0, tmpw, tmph);
}
}
if (img->dim != 0) {
// Dim the actually content of window
xcb_rectangle_t rect = {
.x = 0,
.y = 0,
.width = tmpw,
.height = tmph,
};
xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
tmp_pict, dim_color, 1, &rect);
}
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);
}
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);
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);
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);
}
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),
.width = tmpew,
.height = tmpeh,
};
xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
result, dim_color, 1, &rect);
}
}
}
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,
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]);
}
static void fill(backend_t *base, struct color c, const region_t *clip) {
struct _xrender_data *xd = (void *)base;
const rect_t *extent = pixman_region32_extents((region_t *)clip);
x_set_picture_clip_region(base->c, xd->back[2], 0, 0, clip);
// color is in X fixed point representation
xcb_render_fill_rectangles(
base->c, XCB_RENDER_PICT_OP_OVER, xd->back[2],
(xcb_render_color_t){.red = (uint16_t)(c.red * 0xffff),
.green = (uint16_t)(c.green * 0xffff),
.blue = (uint16_t)(c.blue * 0xffff),
.alpha = (uint16_t)(c.alpha * 0xffff)},
1,
(xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1),
.y = to_i16_checked(extent->y1),
.width = to_u16_checked(extent->x2 - extent->x1),
.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) {
struct _xrender_blur_context *bctx = ctx_;
if (bctx->method == BLUR_METHOD_NONE) {
return true;
}
struct _xrender_data *xd = (void *)backend_data;
xcb_connection_t *c = xd->base.c;
region_t reg_op;
pixman_region32_init(&reg_op);
pixman_region32_intersect(&reg_op, (region_t *)reg_blur, (region_t *)reg_visible);
if (!pixman_region32_not_empty(&reg_op)) {
pixman_region32_fini(&reg_op);
return true;
}
region_t reg_op_resized =
resize_region(&reg_op, bctx->resize_width, bctx->resize_height);
const pixman_box32_t *extent_resized = pixman_region32_extents(&reg_op_resized);
const auto height_resized = to_u16_checked(extent_resized->y2 - extent_resized->y1);
const auto width_resized = to_u16_checked(extent_resized->x2 - extent_resized->x1);
static const char *filter0 = "Nearest"; // The "null" filter
static const char *filter = "convolution";
// Create a buffer for storing blurred picture, make it just big enough
// for the blur region
const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
const xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_PAD};
xcb_render_picture_t tmp_picture[2] = {
x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized,
xd->default_visual, pic_attrs_mask, &pic_attrs),
x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized,
xd->default_visual, pic_attrs_mask, &pic_attrs)};
if (!tmp_picture[0] || !tmp_picture[1]) {
log_error("Failed to build intermediate Picture.");
pixman_region32_fini(&reg_op);
pixman_region32_fini(&reg_op_resized);
return false;
}
region_t clip;
pixman_region32_init(&clip);
pixman_region32_copy(&clip, &reg_op_resized);
pixman_region32_translate(&clip, -extent_resized->x1, -extent_resized->y1);
x_set_picture_clip_region(c, tmp_picture[0], 0, 0, &clip);
x_set_picture_clip_region(c, tmp_picture[1], 0, 0, &clip);
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)];
int current = 0;
x_set_picture_clip_region(c, src_pict, 0, 0, &reg_op_resized);
// For more than 1 pass, we do:
// back -(pass 1)-> tmp0 -(pass 2)-> tmp1 ...
// -(pass n-1)-> tmp0 or tmp1 -(pass n)-> back
// For 1 pass, we do
// back -(pass 1)-> tmp0 -(copy)-> target_buffer
int i;
for (i = 0; i < bctx->x_blur_kernel_count; i++) {
// Copy from source picture to destination. The filter must
// be applied on source picture, to get the nearby pixels outside the
// window.
xcb_render_set_picture_filter(c, src_pict, to_u16_checked(strlen(filter)),
filter,
to_u32_checked(bctx->x_blur_kernel[i]->size),
bctx->x_blur_kernel[i]->kernel);
if (i == 0) {
// First pass, back buffer -> tmp picture
// (we do this even if this is also the last pass, because we
// cannot do back buffer -> back buffer)
xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE,
dst_pict, to_i16_checked(extent_resized->x1),
to_i16_checked(extent_resized->y1), 0, 0, 0,
0, width_resized, height_resized);
} else if (i < bctx->x_blur_kernel_count - 1) {
// This is not the last pass or the first pass,
// tmp picture 1 -> tmp picture 2
xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict,
XCB_NONE, dst_pict, 0, 0, 0, 0, 0, 0,
width_resized, height_resized);
} 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);
}
// reset filter
xcb_render_set_picture_filter(
c, src_pict, to_u16_checked(strlen(filter0)), filter0, 0, NULL);
src_pict = tmp_picture[current];
dst_pict = tmp_picture[!current];
current = !current;
}
// There is only 1 pass
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),
to_i16_checked(extent_resized->y1), width_resized, height_resized);
}
xcb_render_free_picture(c, tmp_picture[0]);
xcb_render_free_picture(c, tmp_picture[1]);
pixman_region32_fini(&reg_op);
pixman_region32_fini(&reg_op_resized);
return true;
}
static void *
bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
xcb_generic_error_t *e;
auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), &e);
if (!r) {
log_error("Invalid pixmap: %#010x", pixmap);
x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code);
return NULL;
}
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->base.ewidth = r->width;
inner->height = img->base.eheight = r->height;
inner->pixmap = pixmap;
inner->has_alpha = fmt.alpha_size != 0;
inner->pict =
x_create_picture_with_visual_and_pixmap(base->c, fmt.visual, pixmap, 0, NULL);
inner->owned = owned;
inner->visual = fmt.visual;
inner->refcount = 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) {
free(inner);
free(img);
return NULL;
}
return img;
}
static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) {
xcb_render_free_picture(base->c, inner->pict);
if (inner->owned) {
xcb_free_pixmap(base->c, inner->pixmap);
}
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 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);
}
static void deinit(backend_t *backend_data) {
struct _xrender_data *xd = (void *)backend_data;
for (int i = 0; i < 256; i++) {
xcb_render_free_picture(xd->base.c, xd->alpha_pict[i]);
}
xcb_render_free_picture(xd->base.c, xd->target);
for (int i = 0; i < 2; i++) {
xcb_render_free_picture(xd->base.c, xd->back[i]);
xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]);
}
if (xd->present_event) {
xcb_unregister_for_special_event(xd->base.c, xd->present_event);
}
xcb_render_free_picture(xd->base.c, xd->white_pixel);
xcb_render_free_picture(xd->base.c, xd->black_pixel);
free(xd);
}
static void present(backend_t *base, const region_t *region) {
struct _xrender_data *xd = (void *)base;
const rect_t *extent = pixman_region32_extents((region_t *)region);
int16_t orig_x = to_i16_checked(extent->x1), orig_y = to_i16_checked(extent->y1);
uint16_t region_width = to_u16_checked(extent->x2 - extent->x1),
region_height = to_u16_checked(extent->y2 - extent->y1);
// compose() sets clip region on the back buffer, so clear it first
x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]);
// limit the region of update
x_set_picture_clip_region(base->c, xd->back[2], 0, 0, region);
if (xd->vsync) {
// Update the back buffer first, then present
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0,
0, orig_x, orig_y, region_width, region_height);
// Make sure we got reply from PresentPixmap before waiting for events,
// to avoid deadlock
auto e = xcb_request_check(
base->c, xcb_present_pixmap_checked(
xd->base.c, xd->target_win,
xd->back_pixmap[xd->curr_back], 0, XCB_NONE, XCB_NONE, 0,
0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
if (e) {
log_error("Failed to present pixmap");
free(e);
return;
}
// TODO(yshui) don't block wait for present completion
xcb_present_generic_event_t *pev =
(void *)xcb_wait_for_special_event(base->c, xd->present_event);
if (!pev) {
// We don't know what happened, maybe X died
// But reset buffer age, so in case we do recover, we will
// render correctly.
xd->buffer_age[0] = xd->buffer_age[1] = -1;
return;
}
assert(pev->evtype == XCB_PRESENT_COMPLETE_NOTIFY);
xcb_present_complete_notify_event_t *pcev = (void *)pev;
// log_trace("Present complete: %d %ld", pcev->mode, pcev->msc);
xd->buffer_age[xd->curr_back] = 1;
// buffer_age < 0 means that back buffer is empty
if (xd->buffer_age[1 - xd->curr_back] > 0) {
xd->buffer_age[1 - xd->curr_back]++;
}
if (pcev->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) {
// We cannot use the pixmap we used anymore
xd->curr_back = 1 - xd->curr_back;
}
free(pev);
} else {
// No vsync needed, draw into the target picture directly
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
XCB_NONE, xd->target, orig_x, orig_y, 0, 0, orig_x,
orig_y, region_width, region_height);
}
}
static int buffer_age(backend_t *backend_data) {
struct _xrender_data *xd = (void *)backend_data;
if (!xd->vsync) {
// Only the target picture really holds the screen content, and its
// content is always up to date. So buffer age is always 1.
return 1;
}
return xd->buffer_age[xd->curr_back];
}
static struct _xrender_image_data_inner *
new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
auto new_inner = ccalloc(1, struct _xrender_image_data_inner);
new_inner->pixmap = x_create_pixmap(base->c, depth, base->root, w, h);
if (new_inner->pixmap == XCB_NONE) {
log_error("Failed to create pixmap for copy");
free(new_inner);
return NULL;
}
new_inner->pict = x_create_picture_with_visual_and_pixmap(
base->c, visual, new_inner->pixmap, 0, NULL);
if (new_inner->pict == XCB_NONE) {
log_error("Failed to create picture for copy");
xcb_free_pixmap(base->c, new_inner->pixmap);
free(new_inner);
return NULL;
}
new_inner->width = w;
new_inner->height = h;
new_inner->visual = visual;
new_inner->depth = depth;
new_inner->refcount = 1;
new_inner->owned = true;
return new_inner;
}
static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
if (img->inner->refcount == 1) {
return true;
}
auto inner = (struct _xrender_image_data_inner *)img->inner;
// Force new pixmap to a 32-bit ARGB visual to allow for transparent frames around
// non-transparent windows
auto visual = (inner->depth == 32)
? inner->visual
: x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32);
auto inner2 = new_inner(base, inner->width, inner->height, visual, 32);
if (!inner2) {
return false;
}
x_set_picture_clip_region(base->c, inner->pict, 0, 0, reg);
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
inner2->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width),
to_u16_checked(inner->height));
img->inner = (struct backend_image_inner_base *)inner2;
inner->refcount--;
return true;
}
static bool image_op(backend_t *base, enum image_operations op, void *image,
const region_t *reg_op, const region_t *reg_visible, void *arg) {
struct _xrender_data *xd = (void *)base;
struct backend_image *img = image;
region_t reg;
double *dargs = arg;
pixman_region32_init(&reg);
pixman_region32_intersect(&reg, (region_t *)reg_op, (region_t *)reg_visible);
switch (op) {
case IMAGE_OP_APPLY_ALPHA:
assert(reg_op);
if (!pixman_region32_not_empty(&reg)) {
break;
}
if (dargs[0] == 1) {
break;
}
if (!decouple_image(base, img, reg_visible)) {
pixman_region32_fini(&reg);
return false;
}
auto inner = (struct _xrender_image_data_inner *)img->inner;
auto alpha_pict = xd->alpha_pict[(int)((1 - dargs[0]) * MAX_ALPHA)];
x_set_picture_clip_region(base->c, inner->pict, 0, 0, &reg);
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OUT_REVERSE, alpha_pict,
XCB_NONE, inner->pict, 0, 0, 0, 0, 0, 0,
to_u16_checked(inner->width),
to_u16_checked(inner->height));
inner->has_alpha = true;
break;
}
pixman_region32_fini(&reg);
return true;
}
static void *
create_blur_context(backend_t *base attr_unused, enum blur_method method, void *args) {
auto ret = ccalloc(1, struct _xrender_blur_context);
if (!method || method >= BLUR_METHOD_INVALID) {
ret->method = BLUR_METHOD_NONE;
return ret;
}
if (method == BLUR_METHOD_DUAL_KAWASE) {
log_warn("Blur method 'dual_kawase' is not compatible with the 'xrender' "
"backend.");
ret->method = BLUR_METHOD_NONE;
return ret;
}
ret->method = BLUR_METHOD_KERNEL;
struct conv **kernels;
int kernel_count;
if (method == BLUR_METHOD_KERNEL) {
kernels = ((struct kernel_blur_args *)args)->kernels;
kernel_count = ((struct kernel_blur_args *)args)->kernel_count;
} else {
kernels = generate_blur_kernel(method, args, &kernel_count);
}
ret->x_blur_kernel = ccalloc(kernel_count, struct x_convolution_kernel *);
for (int i = 0; i < kernel_count; i++) {
int center = kernels[i]->h * kernels[i]->w / 2;
x_create_convolution_kernel(kernels[i], kernels[i]->data[center],
&ret->x_blur_kernel[i]);
ret->resize_width += kernels[i]->w / 2;
ret->resize_height += kernels[i]->h / 2;
}
ret->x_blur_kernel_count = kernel_count;
if (method != BLUR_METHOD_KERNEL) {
// Kernels generated by generate_blur_kernel, so we need to free them.
for (int i = 0; i < kernel_count; i++) {
free(kernels[i]);
}
free(kernels);
}
return ret;
}
static void destroy_blur_context(backend_t *base attr_unused, void *ctx_) {
struct _xrender_blur_context *ctx = ctx_;
for (int i = 0; i < ctx->x_blur_kernel_count; i++) {
free(ctx->x_blur_kernel[i]);
}
free(ctx->x_blur_kernel);
free(ctx);
}
static void get_blur_size(void *blur_context, int *width, int *height) {
struct _xrender_blur_context *ctx = blur_context;
*width = ctx->resize_width;
*height = ctx->resize_height;
}
static backend_t *backend_xrender_init(session_t *ps) {
auto xd = ccalloc(1, struct _xrender_data);
init_backend_base(&xd->base, ps);
for (int i = 0; i <= MAX_ALPHA; ++i) {
double o = (double)i / (double)MAX_ALPHA;
xd->alpha_pict[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0);
assert(xd->alpha_pict[i] != XCB_NONE);
}
xd->target_width = ps->root_width;
xd->target_height = ps->root_height;
xd->default_visual = ps->vis;
xd->black_pixel = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0);
xd->white_pixel = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1);
xd->target_win = session_get_target_window(ps);
xcb_render_create_picture_value_list_t pa = {
.subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS,
};
xd->target = x_create_picture_with_visual_and_pixmap(
ps->c, ps->vis, xd->target_win, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis);
if (!pictfmt) {
log_fatal("Default visual is invalid");
abort();
}
xd->vsync = ps->o.vsync;
if (ps->present_exists) {
auto eid = x_new_id(ps->c);
auto e =
xcb_request_check(ps->c, xcb_present_select_input_checked(
ps->c, eid, xd->target_win,
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY));
if (e) {
log_error("Cannot select present input, vsync will be disabled");
xd->vsync = false;
free(e);
}
xd->present_event =
xcb_register_for_special_xge(ps->c, &xcb_present_id, eid, NULL);
if (!xd->present_event) {
log_error("Cannot register for special XGE, vsync will be "
"disabled");
xd->vsync = false;
}
} else {
xd->vsync = false;
}
// We might need to do double buffering for vsync, and buffer 0 and 1 are for
// double buffering.
int first_buffer_index = xd->vsync ? 0 : 2;
for (int i = first_buffer_index; i < 3; i++) {
xd->back_pixmap[i] = x_create_pixmap(ps->c, pictfmt->depth, ps->root,
to_u16_checked(ps->root_width),
to_u16_checked(ps->root_height));
const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
const xcb_render_create_picture_value_list_t pic_attrs = {
.repeat = XCB_RENDER_REPEAT_PAD};
xd->back[i] = x_create_picture_with_pictfmt_and_pixmap(
ps->c, pictfmt, xd->back_pixmap[i], pic_attrs_mask, &pic_attrs);
xd->buffer_age[i] = -1;
if (xd->back_pixmap[i] == XCB_NONE || xd->back[i] == XCB_NONE) {
log_error("Cannot create pixmap for rendering");
goto err;
}
}
xd->curr_back = 0;
return &xd->base;
err:
deinit(&xd->base);
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,
.blur = blur,
.present = present,
.compose = compose,
.fill = fill,
.bind_pixmap = bind_pixmap,
.release_image = release_image,
.render_shadow = default_backend_render_shadow,
//.prepare_win = prepare_win,
//.release_win = release_win,
.is_image_transparent = default_is_image_transparent,
.buffer_age = buffer_age,
.max_buffer_age = 2,
.image_op = image_op,
.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,
};
// vim: set noet sw=8 ts=8:
+1674
View File
File diff suppressed because it is too large. Load diff
+26
View File
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
/*
* Compton - a compositor for X11
*
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2011-2013, Christopher Jeffrey
* See LICENSE-mit for more information.
*
*/
#pragma once
#include <stdbool.h>
typedef struct _c2_lptr c2_lptr_t;
typedef struct session session_t;
struct managed_win;
c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data);
c2_lptr_t *c2_free_lptr(c2_lptr_t *lp);
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);
+95
View File
@@ -0,0 +1,95 @@
#include <uthash.h>
#include "compiler.h"
#include "utils.h"
#include "cache.h"
struct cache_entry {
char *key;
void *value;
UT_hash_handle hh;
};
struct cache {
cache_getter_t getter;
cache_free_t free;
void *user_data;
struct cache_entry *entries;
};
void cache_set(struct cache *c, const char *key, void *data) {
struct cache_entry *e = NULL;
HASH_FIND_STR(c->entries, key, e);
CHECK(!e);
e = ccalloc(1, struct cache_entry);
e->key = strdup(key);
e->value = data;
HASH_ADD_STR(c->entries, key, e);
}
void *cache_get(struct cache *c, const char *key, int *err) {
struct cache_entry *e;
HASH_FIND_STR(c->entries, key, e);
if (e) {
return e->value;
}
int tmperr;
if (!err) {
err = &tmperr;
}
*err = 0;
e = ccalloc(1, struct cache_entry);
e->key = strdup(key);
e->value = c->getter(c->user_data, key, err);
if (*err) {
free(e->key);
free(e);
return NULL;
}
HASH_ADD_STR(c->entries, key, e);
return e->value;
}
static inline void _cache_invalidate(struct cache *c, struct cache_entry *e) {
if (c->free) {
c->free(c->user_data, e->value);
}
free(e->key);
HASH_DEL(c->entries, e);
free(e);
}
void cache_invalidate(struct cache *c, const char *key) {
struct cache_entry *e;
HASH_FIND_STR(c->entries, key, e);
if (e) {
_cache_invalidate(c, e);
}
}
void cache_invalidate_all(struct cache *c) {
struct cache_entry *e, *tmpe;
HASH_ITER(hh, c->entries, e, tmpe) {
_cache_invalidate(c, e);
}
}
void *cache_free(struct cache *c) {
void *ret = c->user_data;
cache_invalidate_all(c);
free(c);
return ret;
}
struct cache *new_cache(void *ud, cache_getter_t getter, cache_free_t f) {
auto c = ccalloc(1, struct cache);
c->user_data = ud;
c->getter = getter;
c->free = f;
return c;
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
struct cache;
typedef void *(*cache_getter_t)(void *user_data, const char *key, int *err);
typedef void (*cache_free_t)(void *user_data, void *data);
/// Create a cache with `getter`, and a free function `f` which is used to free the cache
/// value when they are invalidated.
///
/// `user_data` will be passed to `getter` and `f` when they are called.
struct cache *new_cache(void *user_data, cache_getter_t getter, cache_free_t f);
/// Fetch a value from the cache. If the value doesn't present in the cache yet, the
/// getter will be called, and the returned value will be stored into the cache.
void *cache_get(struct cache *, const char *key, int *err);
/// Invalidate a value in the cache.
void cache_invalidate(struct cache *, const char *key);
/// Invalidate all values in the cache.
void cache_invalidate_all(struct cache *);
/// Invalidate all values in the cache and free it. Returns the user data passed to
/// `new_cache`
void *cache_free(struct cache *);
/// Insert a key-value pair into the cache. Only used for internal testing. Takes
/// ownership of `data`
///
/// If `key` already exists in the cache, this function will abort the program.
void cache_set(struct cache *c, const char *key, void *data);
+534
View File
@@ -0,0 +1,534 @@
// SPDX-License-Identifier: MIT
/*
* Compton - a compositor for X11
*
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2011-2013, Christopher Jeffrey
* Copyright (c) 2018, Yuxuan Shui <yshuiv7@gmail.com>
*
* See LICENSE-mit for more information.
*
*/
#pragma once
// === Options ===
// Debug options, enable them using -D in CFLAGS
// #define DEBUG_REPAINT 1
// #define DEBUG_EVENTS 1
// #define DEBUG_RESTACK 1
// #define DEBUG_WINMATCH 1
// #define DEBUG_C2 1
// #define DEBUG_GLX_DEBUG_CONTEXT 1
#define MAX_ALPHA (255)
// === Includes ===
// For some special functions
#include <assert.h>
#include <stdbool.h>
#include <sys/time.h>
#include <time.h>
#include <X11/Xlib.h>
#include <ev.h>
#include <pixman.h>
#include <xcb/xproto.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include "uthash_extra.h"
#ifdef CONFIG_OPENGL
#include "backend/gl/glx.h"
#endif
// X resource checker
#ifdef DEBUG_XRC
#include "xrescheck.h"
#endif
// FIXME This list of includes should get shorter
#include "backend/backend.h"
#include "backend/driver.h"
#include "compiler.h"
#include "config.h"
#include "region.h"
#include "types.h"
#include "utils.h"
#include "list.h"
#include "render.h"
#include "win_defs.h"
#include "x.h"
// === Constants ===0
#define NS_PER_SEC 1000000000L
#define US_PER_SEC 1000000L
#define MS_PER_SEC 1000
/// @brief Maximum OpenGL FBConfig depth.
#define OPENGL_MAX_DEPTH 32
/// @brief Maximum OpenGL buffer age.
#define CGLX_MAX_BUFFER_AGE 5
// Window flags
// === Types ===
typedef struct glx_fbconfig glx_fbconfig_t;
struct glx_session;
struct atom;
struct conv;
typedef struct _ignore {
struct _ignore *next;
unsigned long sequence;
} ignore_t;
#ifdef CONFIG_OPENGL
#ifdef DEBUG_GLX_DEBUG_CONTEXT
typedef GLXContext (*f_glXCreateContextAttribsARB)(Display *dpy, GLXFBConfig config,
GLXContext share_context, Bool direct,
const int *attrib_list);
typedef void (*GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar *message, GLvoid *userParam);
typedef void (*f_DebugMessageCallback)(GLDEBUGPROC, void *userParam);
#endif
typedef struct glx_prog_main {
/// GLSL program.
GLuint prog;
/// Location of uniform "opacity" in window GLSL program.
GLint unifm_opacity;
/// Location of uniform "invert_color" in blur GLSL program.
GLint unifm_invert_color;
/// Location of uniform "tex" in window GLSL program.
GLint unifm_tex;
/// Location of uniform "time" in window GLSL program.
GLint unifm_time;
} glx_prog_main_t;
#define GLX_PROG_MAIN_INIT \
{ \
.prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, \
.unifm_tex = -1, .unifm_time = -1 \
}
#else
struct glx_prog_main {};
#endif
#define PAINT_INIT \
{ .pixmap = XCB_NONE, .pict = XCB_NONE }
/// Linked list type of atoms.
typedef struct _latom {
xcb_atom_t atom;
struct _latom *next;
} latom_t;
/// Structure containing all necessary data for a session.
typedef struct session {
// === Event handlers ===
/// ev_io for X connection
ev_io xiow;
/// Timeout for delayed unredirection.
ev_timer unredir_timer;
/// Timer for fading
ev_timer fade_timer;
/// Timer for animations
ev_timer animation_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,
/// 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;
/// Signal handler for SIGUSR1
ev_signal usr1_signal;
/// Signal handler for SIGINT
ev_signal int_signal;
/// backend data
backend_t *backend_data;
/// backend blur context
void *backend_blur_context;
/// graphic drivers used
enum driver drivers;
/// file watch handle
void *file_watch_handle;
/// libev mainloop
struct ev_loop *loop;
// === Display related ===
/// Whether the X server is grabbed by us
bool server_grabbed;
/// Display in use.
Display *dpy;
/// Previous handler of X errors
XErrorHandler previous_xerror_handler;
/// Default screen.
int scr;
/// XCB connection.
xcb_connection_t *c;
/// Default visual.
xcb_visualid_t vis;
/// Default depth.
int depth;
/// Root window.
xcb_window_t root;
/// Height of root window.
int root_height;
/// Width of root window.
int root_width;
/// Current desktop number of root window
int root_desktop_num;
/// Desktop switch direction
int root_desktop_switch_direction;
// Damage of root window.
// Damage root_damage;
/// X Composite overlay window.
xcb_window_t overlay;
/// The target window for debug mode
xcb_window_t debug_window;
/// Whether the root tile is filled by us.
bool root_tile_fill;
/// Picture of the root window background.
paint_t root_tile_paint;
/// The backend data the root pixmap bound to
void *root_image;
/// A region of the size of the screen.
region_t screen_reg;
/// Picture of root window. Destination of painting in no-DBE painting
/// mode.
xcb_render_picture_t root_picture;
/// A Picture acting as the painting target.
xcb_render_picture_t tgt_picture;
/// Temporary buffer to paint to before sending to display.
paint_t tgt_buffer;
/// Window ID of the window we register as a symbol.
xcb_window_t reg_win;
#ifdef CONFIG_OPENGL
/// Pointer to GLX data.
struct glx_session *psglx;
/// Custom GLX program used for painting window.
// XXX should be in struct glx_session
glx_prog_main_t glx_prog_win;
struct glx_fbconfig_info *argb_fbconfig;
#endif
/// Sync fence to sync draw operations
xcb_sync_fence_t sync_fence;
/// Whether we are rendering the first frame after screen is redirected
bool first_frame;
// === Operation related ===
/// Flags related to the root window
uint64_t root_flags;
/// Program options.
options_t o;
/// Whether we have hit unredirection timeout.
bool tmout_unredir_hit;
/// Whether we need to redraw the screen
bool redraw_needed;
/// Cache a xfixes region so we don't need to allocate it everytime.
/// A workaround for yshui/picom#301
xcb_xfixes_region_t damaged_region;
/// The region needs to painted on next paint.
region_t *damage;
/// The region damaged on the last paint.
region_t *damage_ring;
/// Number of damage regions we track
int ndamage;
/// Whether all windows are currently redirected.
bool redirected;
/// Pre-generated alpha pictures.
xcb_render_picture_t *alpha_picts;
/// Time of last fading. In milliseconds.
long long fade_time;
/// Time of last window animation step. In milliseconds.
long animation_time; // TODO(dccsillag) turn into `long long`, like fade_time
/// Head pointer of the error ignore linked list.
ignore_t *ignore_head;
/// Pointer to the <code>next</code> member of tail element of the error
/// ignore linked list.
ignore_t **ignore_tail;
// Cached blur convolution kernels.
struct x_convolution_kernel **blur_kerns_cache;
/// If we should quit
bool quit:1;
// TODO(yshui) use separate flags for dfferent kinds of updates so we don't
// waste our time.
/// Whether there are pending updates, like window creation, etc.
bool pending_updates:1;
// === Expose event related ===
/// Pointer to an array of <code>XRectangle</code>-s of exposed region.
/// XXX why do we need this array?
rect_t *expose_rects;
/// Number of <code>XRectangle</code>-s in <code>expose_rects</code>.
int size_expose;
/// Index of the next free slot in <code>expose_rects</code>.
int n_expose;
// === Window related ===
/// A hash table of all windows.
struct win *windows;
/// Windows in their stacking order
struct list_node window_stack;
/// Pointer to <code>win</code> of current active window. Used by
/// EWMH <code>_NET_ACTIVE_WINDOW</code> focus detection. In theory,
/// it's more reliable to store the window ID directly here, just in
/// case the WM does something extraordinary, but caching the pointer
/// means another layer of complexity.
struct managed_win *active_win;
/// Window ID of leader window of currently active window. Used for
/// subsidiary window detection.
xcb_window_t active_leader;
// === Shadow/dimming related ===
/// 1x1 black Picture.
xcb_render_picture_t black_picture;
/// 1x1 Picture of the shadow color.
xcb_render_picture_t cshadow_picture;
/// 1x1 white Picture.
xcb_render_picture_t white_picture;
/// Gaussian map of shadow.
struct conv *gaussian_map;
// for shadow precomputation
/// A region in which shadow is not painted on.
region_t shadow_exclude_reg;
// === Software-optimization-related ===
/// Nanosecond offset of the first painting.
long paint_tm_offset;
#ifdef CONFIG_VSYNC_DRM
// === DRM VSync related ===
/// File descriptor of DRI device file. Used for DRM VSync.
int drm_fd;
#endif
// === X extension related ===
/// Event base number for X Fixes extension.
int xfixes_event;
/// Error base number for X Fixes extension.
int xfixes_error;
/// Event base number for X Damage extension.
int damage_event;
/// Error base number for X Damage extension.
int damage_error;
/// Event base number for X Render extension.
int render_event;
/// Error base number for X Render extension.
int render_error;
/// Event base number for X Composite extension.
int composite_event;
/// Error base number for X Composite extension.
int composite_error;
/// Major opcode for X Composite extension.
int composite_opcode;
/// Whether X Shape extension exists.
bool shape_exists;
/// Event base number for X Shape extension.
int shape_event;
/// Error base number for X Shape extension.
int shape_error;
/// Whether X RandR extension exists.
bool randr_exists;
/// Event base number for X RandR extension.
int randr_event;
/// Error base number for X RandR extension.
int randr_error;
/// Whether X Present extension exists.
bool present_exists;
/// Whether X GLX extension exists.
bool glx_exists;
/// Event base number for X GLX extension.
int glx_event;
/// Error base number for X GLX extension.
int glx_error;
/// Whether X Xinerama extension exists.
bool xinerama_exists;
/// Xinerama screen regions.
region_t *xinerama_scr_regs;
/// Number of Xinerama screens.
int xinerama_nscrs;
/// Whether X Sync extension exists.
bool xsync_exists;
/// Event base number for X Sync extension.
int xsync_event;
/// Error base number for X Sync extension.
int xsync_error;
/// Whether X Render convolution filter exists.
bool xrfilter_convolution_exists;
// === Atoms ===
struct atom *atoms;
/// Array of atoms of all possible window types.
xcb_atom_t atoms_wintypes[NUM_WINTYPES];
/// Linked list of additional atoms to track.
latom_t *track_atom_lst;
#ifdef CONFIG_DBUS
// === DBus related ===
void *dbus_data;
#endif
int (*vsync_wait)(session_t *);
} session_t;
/// Enumeration for window event hints.
typedef enum { WIN_EVMODE_UNKNOWN, WIN_EVMODE_FRAME, WIN_EVMODE_CLIENT } win_evmode_t;
extern const char *const WINTYPES[NUM_WINTYPES];
extern session_t *ps_g;
void ev_xcb_error(session_t *ps, xcb_generic_error_t *err);
// === Functions ===
/**
* Subtracting two struct timespec values.
*
* Taken from glibc manual.
*
* Subtract the `struct timespec' values X and Y,
* storing the result in RESULT.
* Return 1 if the difference is negative, otherwise 0.
*/
static inline int
timespec_subtract(struct timespec *result, struct timespec *x, struct timespec *y) {
/* Perform the carry for the later subtraction by updating y. */
if (x->tv_nsec < y->tv_nsec) {
long nsec = (y->tv_nsec - x->tv_nsec) / NS_PER_SEC + 1;
y->tv_nsec -= NS_PER_SEC * nsec;
y->tv_sec += nsec;
}
if (x->tv_nsec - y->tv_nsec > NS_PER_SEC) {
long nsec = (x->tv_nsec - y->tv_nsec) / NS_PER_SEC;
y->tv_nsec += NS_PER_SEC * nsec;
y->tv_sec -= nsec;
}
/* Compute the time remaining to wait.
tv_nsec is certainly positive. */
result->tv_sec = x->tv_sec - y->tv_sec;
result->tv_nsec = x->tv_nsec - y->tv_nsec;
/* Return 1 if result is negative. */
return x->tv_sec < y->tv_sec;
}
/**
* Get current time in struct timeval.
*/
static inline struct timeval get_time_timeval(void) {
struct timeval tv = {0, 0};
gettimeofday(&tv, NULL);
// Return a time of all 0 if the call fails
return tv;
}
/**
* Get current time in struct timespec.
*
* Note its starting time is unspecified.
*/
static inline struct timespec get_time_timespec(void) {
struct timespec tm = {0, 0};
clock_gettime(CLOCK_MONOTONIC, &tm);
// Return a time of all 0 if the call fails
return tm;
}
/**
* Return the painting target window.
*/
static inline xcb_window_t get_tgt_window(session_t *ps) {
return ps->overlay != XCB_NONE ? ps->overlay : ps->root;
}
/**
* Check if current backend uses GLX.
*/
static inline bool bkend_use_glx(session_t *ps) {
return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend;
}
static void set_ignore(session_t *ps, unsigned long sequence) {
if (ps->o.show_all_xerrors)
return;
auto i = cmalloc(ignore_t);
if (!i)
return;
i->sequence = sequence;
i->next = 0;
*ps->ignore_tail = i;
ps->ignore_tail = &i->next;
}
/**
* Ignore X errors caused by given X request.
*/
static inline void set_ignore_cookie(session_t *ps, xcb_void_cookie_t cookie) {
set_ignore(ps, cookie.sequence);
}
/**
* Determine if a window has a specific property.
*
* @param ps current session
* @param w window to check
* @param atom atom of property to check
* @return true if it has the attribute, false otherwise
*/
static inline bool wid_has_prop(const session_t *ps, xcb_window_t w, xcb_atom_t atom) {
auto r = xcb_get_property_reply(
ps->c, xcb_get_property(ps->c, 0, w, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 0), NULL);
if (!r) {
return false;
}
auto rtype = r->type;
free(r);
if (rtype != XCB_NONE) {
return true;
}
return false;
}
void force_repaint(session_t *ps);
/** @name DBus handling
*/
///@{
#ifdef CONFIG_DBUS
/** @name DBus hooks
*/
///@{
void opts_set_no_fading_openclose(session_t *ps, bool newval);
//!@}
#endif
/**
* Set a <code>bool</code> array of all wintypes to true.
*/
static inline void wintype_arr_enable(bool arr[]) {
wintype_t i;
for (i = 0; i < NUM_WINTYPES; ++i) {
arr[i] = true;
}
}
+124
View File
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#ifdef HAS_STDC_PREDEF_H
#include <stdc-predef.h>
#endif
// clang-format off
#define auto __auto_type
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#define likely_if(x) if (likely(x))
#define unlikely_if(x) if (unlikely(x))
#ifndef __has_attribute
# if __GNUC__ >= 4
# define __has_attribute(x) 1
# else
# define __has_attribute(x) 0
# endif
#endif
#if __has_attribute(const)
# define attr_const __attribute__((const))
#else
# define attr_const
#endif
#if __has_attribute(format)
# define attr_printf(a, b) __attribute__((format(printf, a, b)))
#else
# define attr_printf(a, b)
#endif
#if __has_attribute(pure)
# define attr_pure __attribute__((pure))
#else
# define attr_pure
#endif
#if __has_attribute(unused)
# define attr_unused __attribute__((unused))
#else
# define attr_unused
#endif
#if __has_attribute(warn_unused_result)
# define attr_warn_unused_result __attribute__((warn_unused_result))
#else
# define attr_warn_unused_result
#endif
// An alias for conveninence
#define must_use attr_warn_unused_result
#if __has_attribute(nonnull)
# define attr_nonnull(...) __attribute__((nonnull(__VA_ARGS__)))
# define attr_nonnull_all __attribute__((nonnull))
#else
# define attr_nonnull(...)
# define attr_nonnull_all
#endif
#if __has_attribute(returns_nonnull)
# define attr_ret_nonnull __attribute__((returns_nonnull))
#else
# define attr_ret_nonnull
#endif
#if __has_attribute(deprecated)
# define attr_deprecated __attribute__((deprecated))
#else
# define attr_deprecated
#endif
#if __has_attribute(malloc)
# define attr_malloc __attribute__((malloc))
#else
# define attr_malloc
#endif
#if __has_attribute(fallthrough)
# define fallthrough() __attribute__((fallthrough))
#else
# define fallthrough()
#endif
#if __STDC_VERSION__ >= 201112L
# define attr_noret _Noreturn
#else
# if __has_attribute(noreturn)
# define attr_noret __attribute__((noreturn))
# else
# define attr_noret
# endif
#endif
#if defined(__GNUC__) || defined(__clang__)
# define unreachable __builtin_unreachable()
#else
# define unreachable do {} while(0)
#endif
#ifndef __has_include
# define __has_include(x) 0
#endif
#if !defined(__STDC_NO_THREADS__) && __has_include(<threads.h>)
# include <threads.h>
#elif __STDC_VERSION__ >= 201112L
# define thread_local _Thread_local
#elif defined(__GNUC__) || defined(__clang__)
# define thread_local __thread
#else
# define thread_local _Pragma("GCC error \"No thread local storage support\"") __error__
#endif
// clang-format on
typedef unsigned long ulong;
typedef unsigned int uint;
static inline int attr_const popcntul(unsigned long a) {
return __builtin_popcountl(a);
}
+651
View File
@@ -0,0 +1,651 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2011-2013, Christopher Jeffrey
// Copyright (c) 2013 Richard Grenville <pyxlcy@gmail.com>
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
#include "c2.h"
#include "common.h"
#include "compiler.h"
#include "kernel.h"
#include "log.h"
#include "region.h"
#include "string_utils.h"
#include "types.h"
#include "utils.h"
#include "win.h"
#include "config.h"
/**
* Parse a long number.
*/
bool parse_long(const char *s, long *dest) {
const char *endptr = NULL;
long val = strtol(s, (char **)&endptr, 0);
if (!endptr || endptr == s) {
log_error("Invalid number: %s", s);
return false;
}
while (isspace((unsigned char)*endptr))
++endptr;
if (*endptr) {
log_error("Trailing characters: %s", s);
return false;
}
*dest = val;
return true;
}
/**
* Parse an int number.
*/
bool parse_int(const char *s, int *dest) {
long val;
if (!parse_long(s, &val)) {
return false;
}
if (val > INT_MAX || val < INT_MIN) {
log_error("Number exceeded int limits: %ld", val);
return false;
}
*dest = (int)val;
return true;
}
/**
* Parse a floating-point number in from a string,
* also strips the trailing space and comma after the number.
*
* @param[in] src string to parse
* @param[out] dest return the number parsed from the string
* @return pointer to the last character parsed
*/
const char *parse_readnum(const char *src, double *dest) {
const char *pc = NULL;
double val = strtod_simple(src, &pc);
if (!pc || pc == src) {
log_error("No number found: %s", src);
return src;
}
while (*pc && (isspace((unsigned char)*pc) || *pc == ',')) {
++pc;
}
*dest = val;
return pc;
}
enum blur_method parse_blur_method(const char *src) {
if (strcmp(src, "kernel") == 0) {
return BLUR_METHOD_KERNEL;
} else if (strcmp(src, "box") == 0) {
return BLUR_METHOD_BOX;
} else if (strcmp(src, "gaussian") == 0) {
return BLUR_METHOD_GAUSSIAN;
} else if (strcmp(src, "dual_kawase") == 0) {
return BLUR_METHOD_DUAL_KAWASE;
} else if (strcmp(src, "kawase") == 0) {
log_warn("Blur method 'kawase' has been renamed to 'dual_kawase'. "
"Interpreted as 'dual_kawase', but this will stop working "
"soon.");
return BLUR_METHOD_DUAL_KAWASE;
} else if (strcmp(src, "none") == 0) {
return BLUR_METHOD_NONE;
}
return BLUR_METHOD_INVALID;
}
/**
* Parse a matrix.
*
* @param[in] src the blur kernel string
* @param[out] endptr return where the end of kernel is in the string
* @param[out] hasneg whether the kernel has negative values
*/
conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) {
int width = 0, height = 0;
*hasneg = false;
const char *pc = NULL;
// Get matrix width and height
double val = 0.0;
if (src == (pc = parse_readnum(src, &val)))
goto err1;
src = pc;
width = (int)val;
if (src == (pc = parse_readnum(src, &val)))
goto err1;
src = pc;
height = (int)val;
// Validate matrix width and height
if (width <= 0 || height <= 0) {
log_error("Blue kernel width/height can't be negative.");
goto err1;
}
if (!(width % 2 && height % 2)) {
log_error("Blur kernel width/height must be odd.");
goto err1;
}
if (width > 16 || height > 16)
log_warn("Blur kernel width/height too large, may slow down"
"rendering, and/or consume lots of memory");
// Allocate memory
conv *matrix = cvalloc(sizeof(conv) + (size_t)(width * height) * sizeof(double));
// Read elements
int skip = height / 2 * width + width / 2;
for (int i = 0; i < width * height; ++i) {
// Ignore the center element
if (i == skip) {
matrix->data[i] = 1;
continue;
}
if (src == (pc = parse_readnum(src, &val))) {
goto err2;
}
src = pc;
if (val < 0) {
*hasneg = true;
}
matrix->data[i] = val;
}
// Detect trailing characters
for (; *pc && *pc != ';'; pc++) {
if (!isspace((unsigned char)*pc) && *pc != ',') {
// TODO(yshui) isspace is locale aware, be careful
log_error("Trailing characters in blur kernel string.");
goto err2;
}
}
// Jump over spaces after ';'
if (*pc == ';') {
pc++;
while (*pc && isspace((unsigned char)*pc)) {
++pc;
}
}
// Require an end of string if endptr is not provided, otherwise
// copy end pointer to endptr
if (endptr) {
*endptr = pc;
} else if (*pc) {
log_error("Only one blur kernel expected.");
goto err2;
}
// Fill in width and height
matrix->w = width;
matrix->h = height;
return matrix;
err2:
free(matrix);
err1:
return NULL;
}
/**
* Parse a list of convolution kernels.
*
* @param[in] src string to parse
* @param[out] hasneg whether any of the kernels have negative values
* @return the kernels
*/
struct conv **parse_blur_kern_lst(const char *src, bool *hasneg, int *count) {
// TODO(yshui) just return a predefined kernels, not parse predefined strings...
static const struct {
const char *name;
const char *kern_str;
} CONV_KERN_PREDEF[] = {
{"3x3box", "3,3,1,1,1,1,1,1,1,1,"},
{"5x5box", "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"},
{"7x7box", "7,7,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"
"1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"},
{"3x3gaussian", "3,3,0.243117,0.493069,0.243117,0.493069,0.493069,0.243117,0."
"493069,0.243117,"},
{"5x5gaussian", "5,5,0.003493,0.029143,0.059106,0.029143,0.003493,0.029143,0."
"243117,0.493069,0.243117,0.029143,0.059106,0.493069,0."
"493069,0.059106,0.029143,0.243117,0.493069,0.243117,0."
"029143,0.003493,0.029143,0.059106,0.029143,0.003493,"},
{"7x7gaussian", "7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0."
"000003,0.000102,0.003493,0.029143,0.059106,0.029143,0."
"003493,0.000102,0.000849,0.029143,0.243117,0.493069,0."
"243117,0.029143,0.000849,0.001723,0.059106,0.493069,0."
"493069,0.059106,0.001723,0.000849,0.029143,0.243117,0."
"493069,0.243117,0.029143,0.000849,0.000102,0.003493,0."
"029143,0.059106,0.029143,0.003493,0.000102,0.000003,0."
"000102,0.000849,0.001723,0.000849,0.000102,0.000003,"},
{"9x9gaussian",
"9,9,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0."
"000000,0.000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0."
"000102,0.000003,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0."
"029143,0.003493,0.000102,0.000001,0.000006,0.000849,0.029143,0.243117,0."
"493069,0.243117,0.029143,0.000849,0.000006,0.000012,0.001723,0.059106,0."
"493069,0.493069,0.059106,0.001723,0.000012,0.000006,0.000849,0.029143,0."
"243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000001,0.000102,0."
"003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0."
"000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0."
"000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0."
"000000,"},
{"11x11gaussian",
"11,11,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0."
"000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0."
"000006,0.000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0."
"000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0."
"000000,0.000000,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0."
"029143,0.003493,0.000102,0.000001,0.000000,0.000000,0.000006,0.000849,0."
"029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000000,0."
"000000,0.000012,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0."
"000012,0.000000,0.000000,0.000006,0.000849,0.029143,0.243117,0.493069,0."
"243117,0.029143,0.000849,0.000006,0.000000,0.000000,0.000001,0.000102,0."
"003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0."
"000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0."
"000003,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0.000006,0."
"000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0."
"000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0."
"000000,"},
};
*count = 0;
*hasneg = false;
for (unsigned int i = 0;
i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i) {
if (!strcmp(CONV_KERN_PREDEF[i].name, src))
return parse_blur_kern_lst(CONV_KERN_PREDEF[i].kern_str, hasneg, count);
}
int nkernels = 1;
for (int i = 0; src[i]; i++) {
if (src[i] == ';') {
nkernels++;
}
}
struct conv **ret = ccalloc(nkernels, struct conv *);
int i = 0;
const char *pc = src;
// Continue parsing until the end of source string
i = 0;
while (pc && *pc) {
bool tmp_hasneg;
assert(i < nkernels);
ret[i] = parse_blur_kern(pc, &pc, &tmp_hasneg);
if (!ret[i]) {
for (int j = 0; j < i; j++) {
free(ret[j]);
}
free(ret);
return NULL;
}
i++;
*hasneg |= tmp_hasneg;
}
if (i > 1) {
log_warn("You are seeing this message because you are using "
"multipass blur. Please report an issue to us so we know "
"multipass blur is actually been used. Otherwise it might be "
"removed in future releases");
}
*count = i;
return ret;
}
/**
* Parse a X geometry.
*
* ps->root_width and ps->root_height must be valid
*/
bool parse_geometry(session_t *ps, const char *src, region_t *dest) {
pixman_region32_clear(dest);
if (!src) {
return true;
}
if (!ps->root_width || !ps->root_height) {
return true;
}
long x = 0, y = 0;
long width = ps->root_width, height = ps->root_height;
long val = 0L;
char *endptr = NULL;
src = skip_space(src);
if (!*src) {
goto parse_geometry_end;
}
// Parse width
// Must be base 10, because "0x0..." may appear
if (*src != '+' && *src != '-') {
val = strtol(src, &endptr, 10);
assert(endptr);
if (src != endptr) {
if (val < 0) {
log_error("Invalid width: %s", src);
return false;
}
width = val;
src = endptr;
}
src = skip_space(src);
}
// Parse height
if (*src == 'x') {
++src;
val = strtol(src, &endptr, 10);
assert(endptr);
if (src != endptr) {
if (val < 0) {
log_error("Invalid height: %s", src);
return false;
}
height = val;
src = endptr;
}
src = skip_space(src);
}
// Parse x
if (*src == '+' || *src == '-') {
val = strtol(src, &endptr, 10);
if (endptr && src != endptr) {
x = val;
if (*src == '-') {
x += ps->root_width - width;
}
src = endptr;
}
src = skip_space(src);
}
// Parse y
if (*src == '+' || *src == '-') {
val = strtol(src, &endptr, 10);
if (endptr && src != endptr) {
y = val;
if (*src == '-') {
y += ps->root_height - height;
}
src = endptr;
}
src = skip_space(src);
}
if (*src) {
log_error("Trailing characters: %s", src);
return false;
}
parse_geometry_end:
if (x < INT_MIN || x > INT_MAX || y < INT_MIN || y > INT_MAX) {
log_error("Geometry coordinates exceeded limits: %s", src);
return false;
}
if (width > UINT_MAX || height > UINT_MAX) {
// less than 0 is checked for earlier
log_error("Geometry size exceeded limits: %s", src);
return false;
}
pixman_region32_union_rect(dest, dest, (int)x, (int)y, (uint)width, (uint)height);
return true;
}
/**
* Parse a list of opacity rules.
*/
bool parse_rule_opacity(c2_lptr_t **res, const char *src) {
// Find opacity value
char *endptr = NULL;
long val = strtol(src, &endptr, 0);
if (!endptr || endptr == src) {
log_error("No opacity specified: %s", src);
return false;
}
if (val > 100 || val < 0) {
log_error("Opacity %ld invalid: %s", val, src);
return false;
}
// Skip over spaces
while (*endptr && isspace((unsigned char)*endptr))
++endptr;
if (':' != *endptr) {
log_error("Opacity terminator not found: %s", src);
return false;
}
++endptr;
// Parse pattern
// I hope 1-100 is acceptable for (void *)
return c2_parse(res, endptr, (void *)val);
}
/**
* Add a pattern to a condition linked list.
*/
bool condlst_add(c2_lptr_t **pcondlst, const char *pattern) {
if (!pattern)
return false;
if (!c2_parse(pcondlst, pattern, NULL))
exit(1);
return true;
}
void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_enable,
bool fading_enable, bool blur_enable) {
// Apply default wintype options.
if (!mask[WINTYPE_DESKTOP].shadow) {
// Desktop windows are always drawn without shadow by default.
mask[WINTYPE_DESKTOP].shadow = true;
opt->wintype_option[WINTYPE_DESKTOP].shadow = false;
}
// Focused/unfocused state only apply to a few window types, all other windows
// are always considered focused.
const wintype_t nofocus_type[] = {WINTYPE_UNKNOWN, WINTYPE_NORMAL, WINTYPE_UTILITY};
for (unsigned long i = 0; i < ARR_SIZE(nofocus_type); i++) {
if (!mask[nofocus_type[i]].focus) {
mask[nofocus_type[i]].focus = true;
opt->wintype_option[nofocus_type[i]].focus = false;
}
}
for (unsigned long i = 0; i < NUM_WINTYPES; i++) {
if (!mask[i].shadow) {
mask[i].shadow = true;
opt->wintype_option[i].shadow = shadow_enable;
}
if (!mask[i].fade) {
mask[i].fade = true;
opt->wintype_option[i].fade = fading_enable;
}
if (!mask[i].focus) {
mask[i].focus = true;
opt->wintype_option[i].focus = true;
}
if (!mask[i].blur_background) {
mask[i].blur_background = true;
opt->wintype_option[i].blur_background = blur_enable;
}
if (!mask[i].full_shadow) {
mask[i].full_shadow = true;
opt->wintype_option[i].full_shadow = false;
}
if (!mask[i].redir_ignore) {
mask[i].redir_ignore = true;
opt->wintype_option[i].redir_ignore = false;
}
if (!mask[i].opacity) {
mask[i].opacity = true;
// Opacity is not set to a concrete number here because the
// opacity logic is complicated, and needs an "unset" state
opt->wintype_option[i].opacity = NAN;
}
if (!mask[i].animation) {
mask[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
opt->wintype_option[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
}
if (!mask[i].animation_unmap) {
mask[i].animation_unmap = OPEN_WINDOW_ANIMATION_INVALID;
opt->wintype_option[i].animation_unmap = OPEN_WINDOW_ANIMATION_INVALID;
}
if (!mask[i].animation_workspace_in) {
mask[i].animation_workspace_in = OPEN_WINDOW_ANIMATION_INVALID;
opt->wintype_option[i].animation_workspace_in = OPEN_WINDOW_ANIMATION_INVALID;
}
if (!mask[i].animation_workspace_out) {
mask[i].animation_workspace_out = OPEN_WINDOW_ANIMATION_INVALID;
opt->wintype_option[i].animation_workspace_out = OPEN_WINDOW_ANIMATION_INVALID;
}
if (!mask[i].clip_shadow_above) {
mask[i].clip_shadow_above = true;
opt->wintype_option[i].clip_shadow_above = false;
}
}
}
enum open_window_animation parse_open_window_animation(const char *src) {
if (strcmp(src, "none") == 0) {
return OPEN_WINDOW_ANIMATION_NONE;
}else if (strcmp(src, "auto") == 0) {
return OPEN_WINDOW_ANIMATION_AUTO;
} else if (strcmp(src, "fly-in") == 0) {
return OPEN_WINDOW_ANIMATION_FLYIN;
} else if (strcmp(src, "zoom") == 0) {
return OPEN_WINDOW_ANIMATION_ZOOM;
} else if (strcmp(src, "squeeze") == 0) {
return OPEN_WINDOW_ANIMATION_SQUEEZE;
} else if (strcmp(src, "slide-up") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_UP;
} else if (strcmp(src, "slide-down") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_DOWN;
} else if (strcmp(src, "slide-left") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_LEFT;
} else if (strcmp(src, "slide-right") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_RIGHT;
}
return OPEN_WINDOW_ANIMATION_INVALID;
}
char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) {
// clang-format off
*opt = (struct options){
.backend = BKEND_XRENDER,
.glx_no_stencil = false,
.mark_wmwin_focused = false,
.mark_ovredir_focused = false,
.detect_rounded_corners = false,
.resize_damage = 0,
.unredir_if_possible = false,
.unredir_if_possible_blacklist = NULL,
.unredir_if_possible_delay = 0,
.redirected_force = UNSET,
.stoppaint_force = UNSET,
.dbus = false,
.benchmark = 0,
.benchmark_wid = XCB_NONE,
.logpath = NULL,
.use_damage = true,
.shadow_red = 0.0,
.shadow_green = 0.0,
.shadow_blue = 0.0,
.shadow_radius = 18,
.shadow_offset_x = -15,
.shadow_offset_y = -15,
.shadow_opacity = .75,
.shadow_blacklist = NULL,
.shadow_ignore_shaped = false,
.xinerama_shadow_crop = false,
.shadow_clip_list = NULL,
.corner_radius = 0,
.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,
.animations = false,
.animation_for_open_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_transient_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_unmap_window = OPEN_WINDOW_ANIMATION_AUTO,
.animation_for_workspace_switch_in = OPEN_WINDOW_ANIMATION_AUTO,
.animation_for_workspace_switch_out = OPEN_WINDOW_ANIMATION_AUTO,
.animation_stiffness = 200.0,
.animation_window_mass = 1.0,
.animation_dampening = 25,
.animation_delta = 10,
.animation_force_steps = false,
.animation_clamping = true,
.animation_blacklist = NULL,
.inactive_opacity = 1.0,
.inactive_opacity_override = false,
.active_opacity = 1.0,
.frame_opacity = 1.0,
.detect_client_opacity = false,
.blur_method = BLUR_METHOD_NONE,
.blur_radius = 3,
.blur_deviation = 0.84089642,
.blur_strength = 5,
.blur_background_frame = false,
.blur_background_fixed = false,
.blur_background_blacklist = NULL,
.blur_kerns = NULL,
.blur_kernel_count = 0,
.inactive_dim = 0.0,
.inactive_dim_fixed = false,
.invert_color_list = NULL,
.opacity_rules = NULL,
.max_brightness = 1.0,
.use_ewmh_active_win = false,
.focus_blacklist = NULL,
.detect_transient = false,
.detect_client_leader = false,
.no_ewmh_fullscreen = false,
.track_leader = false,
.rounded_corners_blacklist = NULL
};
// clang-format on
char *ret = NULL;
#ifdef CONFIG_LIBCONFIG
ret = parse_config_libconfig(opt, config_file, shadow_enable, fading_enable,
hasneg, winopt_mask);
#else
(void)config_file;
(void)shadow_enable;
(void)fading_enable;
(void)hasneg;
(void)winopt_mask;
#endif
return ret;
}
+378
View File
@@ -0,0 +1,378 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2011-2013, Christopher Jeffrey
// Copyright (c) 2013 Richard Grenville <pyxlcy@gmail.com>
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
/// Common functions and definitions for configuration parsing
/// Used for command line arguments and config files
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
#include <xcb/xcb.h>
#include <xcb/xfixes.h>
#ifdef CONFIG_LIBCONFIG
#include <libconfig.h>
#endif
#include "compiler.h"
#include "kernel.h"
#include "log.h"
#include "region.h"
#include "types.h"
#include "win_defs.h"
typedef struct session session_t;
/// @brief Possible backends
enum backend {
BKEND_XRENDER,
BKEND_GLX,
BKEND_XR_GLX_HYBRID,
BKEND_DUMMY,
NUM_BKEND,
};
enum open_window_animation {
OPEN_WINDOW_ANIMATION_NONE = 0,
OPEN_WINDOW_ANIMATION_AUTO,
OPEN_WINDOW_ANIMATION_FLYIN,
OPEN_WINDOW_ANIMATION_ZOOM,
OPEN_WINDOW_ANIMATION_SQUEEZE,
OPEN_WINDOW_ANIMATION_SLIDE_UP,
OPEN_WINDOW_ANIMATION_SLIDE_DOWN,
OPEN_WINDOW_ANIMATION_SLIDE_LEFT,
OPEN_WINDOW_ANIMATION_SLIDE_RIGHT,
OPEN_WINDOW_ANIMATION_SLIDE_IN,
OPEN_WINDOW_ANIMATION_SLIDE_OUT,
OPEN_WINDOW_ANIMATION_INVALID,
};
typedef struct win_option_mask {
bool shadow : 1;
bool fade : 1;
bool focus : 1;
bool blur_background : 1;
bool full_shadow : 1;
bool redir_ignore : 1;
bool opacity : 1;
bool clip_shadow_above : 1;
enum open_window_animation animation;
enum open_window_animation animation_unmap;
enum open_window_animation animation_workspace_in;
enum open_window_animation animation_workspace_out;
} win_option_mask_t;
typedef struct win_option {
bool shadow;
bool fade;
bool focus;
bool blur_background;
bool full_shadow;
bool redir_ignore;
double opacity;
bool clip_shadow_above;
enum open_window_animation animation;
enum open_window_animation animation_unmap;
enum open_window_animation animation_workspace_in;
enum open_window_animation animation_workspace_out;
} win_option_t;
enum blur_method {
BLUR_METHOD_NONE = 0,
BLUR_METHOD_KERNEL,
BLUR_METHOD_BOX,
BLUR_METHOD_GAUSSIAN,
BLUR_METHOD_DUAL_KAWASE,
BLUR_METHOD_INVALID,
};
typedef struct _c2_lptr c2_lptr_t;
/// Structure representing all options.
typedef struct options {
// === Debugging ===
bool monitor_repaint;
bool print_diagnostics;
/// Render to a separate window instead of taking over the screen
bool debug_mode;
// === General ===
/// Use the experimental new backends?
bool experimental_backends;
/// Path to write PID to.
char *write_pid_path;
/// The backend in use.
enum backend backend;
/// Whether to sync X drawing with X Sync fence to avoid certain delay
/// issues with GLX backend.
bool xrender_sync_fence;
/// Whether to avoid using stencil buffer under GLX backend. Might be
/// unsafe.
bool glx_no_stencil;
/// Whether to avoid rebinding pixmap on window damage.
bool glx_no_rebind_pixmap;
/// Custom fragment shader for painting windows, as a string.
char *glx_fshader_win_str;
/// Whether to detect rounded corners.
bool detect_rounded_corners;
/// Force painting of window content with blending.
bool force_win_blend;
/// Resize damage for a specific number of pixels.
int resize_damage;
/// Whether to unredirect all windows if a full-screen opaque window
/// is detected.
bool unredir_if_possible;
/// List of conditions of windows to ignore as a full-screen window
/// when determining if a window could be unredirected.
c2_lptr_t *unredir_if_possible_blacklist;
/// Delay before unredirecting screen, in milliseconds.
long unredir_if_possible_delay;
/// Forced redirection setting through D-Bus.
switch_t redirected_force;
/// Whether to stop painting. Controlled through D-Bus.
switch_t stoppaint_force;
/// Whether to enable D-Bus support.
bool dbus;
/// Path to log file.
char *logpath;
/// Number of cycles to paint in benchmark mode. 0 for disabled.
int benchmark;
/// Window to constantly repaint in benchmark mode. 0 for full-screen.
xcb_window_t benchmark_wid;
/// A list of conditions of windows not to paint.
c2_lptr_t *paint_blacklist;
/// Whether to show all X errors.
bool show_all_xerrors;
/// Whether to avoid acquiring X Selection.
bool no_x_selection;
/// Window type option override.
win_option_t wintype_option[NUM_WINTYPES];
// === VSync & software optimization ===
/// VSync method to use;
bool vsync;
/// Whether to use glFinish() instead of glFlush() for (possibly) better
/// VSync yet probably higher CPU usage.
bool vsync_use_glfinish;
/// Whether use damage information to help limit the area to paint
bool use_damage;
// === Shadow ===
/// Red, green and blue tone of the shadow.
double shadow_red, shadow_green, shadow_blue;
int shadow_radius;
int shadow_offset_x, shadow_offset_y;
double shadow_opacity;
/// argument string to shadow-exclude-reg option
char *shadow_exclude_reg_str;
/// Shadow blacklist. A linked list of conditions.
c2_lptr_t *shadow_blacklist;
/// Whether bounding-shaped window should be ignored.
bool shadow_ignore_shaped;
/// Whether to crop shadow to the very Xinerama screen.
bool xinerama_shadow_crop;
/// Don't draw shadow over these windows. A linked list of conditions.
c2_lptr_t *shadow_clip_list;
// === Fading ===
/// How much to fade in in a single fading step.
double fade_in_step;
/// How much to fade out in a single fading step.
double fade_out_step;
/// Fading time delta. In milliseconds.
int fade_delta;
/// Whether to disable fading on window open/close.
bool no_fading_openclose;
/// Whether to disable fading on ARGB managed destroyed windows.
bool no_fading_destroyed_argb;
/// Fading blacklist. A linked list of conditions.
c2_lptr_t *fade_blacklist;
// === Animations ===
/// Whether to do window animations
bool animations;
/// Which animation to run when opening a window
enum open_window_animation animation_for_open_window;
/// Which animation to run when opening a transient window
enum open_window_animation animation_for_transient_window;
/// Which animation to run when unmapping (e.g. minimizing) a window
enum open_window_animation animation_for_unmap_window;
/// Which animation to run when switching workspace
/// IMPORTANT: will only work if window manager updates
/// _NET_CURRENT_DESKTOP before doing the hide/show of windows
enum open_window_animation animation_for_workspace_switch_in;
enum open_window_animation animation_for_workspace_switch_out;
/// Spring stiffness for animation
double animation_stiffness;
/// Window mass for animation
double animation_window_mass;
/// Animation dampening
double animation_dampening;
/// Animation delta. In milliseconds.
double animation_delta;
/// Whether to force animations to not miss a beat
bool animation_force_steps;
/// Whether to clamp animations
bool animation_clamping;
/// Animation blacklist. A linked list of conditions.
c2_lptr_t *animation_blacklist;
/// TODO: open/close animations
// === Opacity ===
/// Default opacity for inactive windows.
/// 32-bit integer with the format of _NET_WM_WINDOW_OPACITY.
double inactive_opacity;
/// Default opacity for inactive windows.
double active_opacity;
/// Whether inactive_opacity overrides the opacity set by window
/// attributes.
bool inactive_opacity_override;
/// Frame opacity. Relative to window opacity, also affects shadow
/// opacity.
double frame_opacity;
/// 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 ===
/// Blur method for background of semi-transparent windows
enum blur_method blur_method;
// Size of the blur kernel
int blur_radius;
// Standard deviation for the gaussian blur
double blur_deviation;
// Strength of the dual_kawase blur
int blur_strength;
/// Whether to blur background when the window frame is not opaque.
/// Implies blur_background.
bool blur_background_frame;
/// Whether to use fixed blur strength instead of adjusting according
/// to window opacity.
bool blur_background_fixed;
/// Background blur blacklist. A linked list of conditions.
c2_lptr_t *blur_background_blacklist;
/// Blur convolution kernel.
struct conv **blur_kerns;
/// Number of convolution kernels
int blur_kernel_count;
/// 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
/// based on window opacity.
bool inactive_dim_fixed;
/// Conditions of windows to have inverted colors.
c2_lptr_t *invert_color_list;
/// Rules to change window opacity.
c2_lptr_t *opacity_rules;
/// Limit window brightness
double max_brightness;
// Radius of rounded window corners
int corner_radius;
/// Rounded corners blacklist. A linked list of conditions.
c2_lptr_t *rounded_corners_blacklist;
// === Focus related ===
/// Whether to try to detect WM windows and mark them as focused.
bool mark_wmwin_focused;
/// Whether to mark override-redirect windows as focused.
bool mark_ovredir_focused;
/// Whether to use EWMH _NET_ACTIVE_WINDOW to find active window.
bool use_ewmh_active_win;
/// A list of windows always to be considered focused.
c2_lptr_t *focus_blacklist;
/// Whether to do window grouping with <code>WM_TRANSIENT_FOR</code>.
bool detect_transient;
/// Whether to do window grouping with <code>WM_CLIENT_LEADER</code>.
bool detect_client_leader;
// === Calculated ===
/// Whether we need to track window leaders.
bool track_leader;
// Don't use EWMH to detect fullscreen applications
bool no_ewmh_fullscreen;
// Make transparent windows clip other windows, instead of blending on top of
// them
bool transparent_clipping;
} options_t;
extern const char *const BACKEND_STRS[NUM_BKEND + 1];
bool must_use parse_long(const char *, long *);
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 *);
enum blur_method must_use parse_blur_method(const char *src);
enum open_window_animation must_use parse_open_window_animation(const char *src);
/**
* Add a pattern to a condition linked list.
*/
bool condlst_add(c2_lptr_t **, const char *);
#ifdef CONFIG_LIBCONFIG
/// Parse a configuration file
/// Returns the actually config_file name used, allocated on heap
/// Outputs:
/// shadow_enable = whether shaodw is enabled globally
/// fading_enable = whether fading is enabled globally
/// win_option_mask = whether option overrides for specific window type is set for given
/// options
/// hasneg = whether the convolution kernel has negative values
char *
parse_config_libconfig(options_t *, const char *config_file, bool *shadow_enable,
bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask);
#endif
void set_default_winopts(options_t *, win_option_mask_t *, bool shadow_enable,
bool fading_enable, bool blur_enable);
/// Parse a configuration file is that is enabled, also initialize the winopt_mask with
/// default values
/// Outputs and returns:
/// same as parse_config_libconfig
char *parse_config(options_t *, const char *config_file, bool *shadow_enable,
bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask);
/**
* Parse a backend option argument.
*/
static inline attr_pure enum backend parse_backend(const char *str) {
for (enum backend i = 0; BACKEND_STRS[i]; ++i) {
if (!strcasecmp(str, BACKEND_STRS[i])) {
return i;
}
}
// Keep compatibility with an old revision containing a spelling mistake...
if (!strcasecmp(str, "xr_glx_hybird")) {
log_warn("backend xr_glx_hybird should be xr_glx_hybrid, the misspelt "
"version will be removed soon.");
return BKEND_XR_GLX_HYBRID;
}
// cju wants to use dashes
if (!strcasecmp(str, "xr-glx-hybrid")) {
log_warn("backend xr-glx-hybrid should be xr_glx_hybrid, the alternative "
"version will be removed soon.");
return BKEND_XR_GLX_HYBRID;
}
log_error("Invalid backend argument: %s", str);
return NUM_BKEND;
}
/**
* Parse a VSync option argument.
*/
static inline bool parse_vsync(const char *str) {
if (strcmp(str, "no") == 0 || strcmp(str, "none") == 0 ||
strcmp(str, "false") == 0 || strcmp(str, "nah") == 0) {
return false;
}
return true;
}
// vim: set noet sw=8 ts=8 :
+767
View File
@@ -0,0 +1,767 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2012-2014 Richard Grenville <pyxlcy@gmail.com>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libconfig.h>
#include <libgen.h>
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "err.h"
#include "log.h"
#include "options.h"
#include "string_utils.h"
#include "utils.h"
#include "win.h"
#pragma GCC diagnostic error "-Wunused-parameter"
/**
* Wrapper of libconfig's <code>config_lookup_int</code>.
*
* So it takes a pointer to bool.
*/
static inline int lcfg_lookup_bool(const config_t *config, const char *path, bool *value) {
int ival;
int ret = config_lookup_bool(config, path, &ival);
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);
}
}
/// Search for config file under a base directory
FILE *open_config_file_at(const char *base, char **out_path) {
static const char *config_paths[] = {"/picom.conf", "/picom/picom.conf",
"/compton.conf", "/compton/compton.conf"};
for (size_t i = 0; i < ARR_SIZE(config_paths); i++) {
char *path = mstrjoin(base, config_paths[i]);
FILE *ret = fopen(path, "r");
if (ret && out_path) {
*out_path = path;
} else {
free(path);
}
if (ret) {
if (strstr(config_paths[i], "compton")) {
log_warn("This compositor has been renamed to \"picom\", "
"the old config file paths is deprecated. "
"Please replace the \"compton\"s in the path "
"with \"picom\"");
}
return ret;
}
}
return NULL;
}
/**
* Get a file stream of the configuration file to read.
*
* Follows the XDG specification to search for the configuration file.
*/
FILE *open_config_file(const char *cpath, char **ppath) {
static const char config_filename_legacy[] = "/.compton.conf";
if (cpath) {
FILE *ret = fopen(cpath, "r");
if (ret && ppath)
*ppath = strdup(cpath);
return ret;
}
// First search for config file in user config directory
auto config_home = xdg_config_home();
auto ret = open_config_file_at(config_home, ppath);
free((void *)config_home);
if (ret) {
return ret;
}
// Fall back to legacy config file in user home directory
const char *home = getenv("HOME");
if (home && strlen(home)) {
auto path = mstrjoin(home, config_filename_legacy);
ret = fopen(path, "r");
if (ret && ppath) {
*ppath = path;
} else {
free(path);
}
if (ret) {
return ret;
}
}
// Fall back to config file in system config directory
auto config_dirs = xdg_config_dirs();
for (int i = 0; config_dirs[i]; i++) {
ret = open_config_file_at(config_dirs[i], ppath);
if (ret) {
free(config_dirs);
return ret;
}
}
free(config_dirs);
return NULL;
}
/**
* Parse a condition list in configuration file.
*/
void parse_cfg_condlst(const config_t *pcfg, c2_lptr_t **pcondlst, const char *name) {
config_setting_t *setting = config_lookup(pcfg, name);
if (setting) {
// Parse an array of options
if (config_setting_is_array(setting)) {
int i = config_setting_length(setting);
while (i--)
condlst_add(pcondlst,
config_setting_get_string_elem(setting, i));
}
// Treat it as a single pattern if it's a string
else if (CONFIG_TYPE_STRING == config_setting_type(setting)) {
condlst_add(pcondlst, config_setting_get_string(setting));
}
}
}
/**
* Parse an opacity rule list in configuration file.
*/
static inline void
parse_cfg_condlst_opct(options_t *opt, const config_t *pcfg, const char *name) {
config_setting_t *setting = config_lookup(pcfg, name);
if (setting) {
// Parse an array of options
if (config_setting_is_array(setting)) {
int i = config_setting_length(setting);
while (i--)
if (!parse_rule_opacity(
&opt->opacity_rules,
config_setting_get_string_elem(setting, i)))
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_opacity(&opt->opacity_rules,
config_setting_get_string(setting)))
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);
const config_setting_t *setting = config_lookup(cfg, str);
free(str);
int ival = 0;
const char *sval = NULL;
if (setting) {
if (config_setting_lookup_bool(setting, "shadow", &ival)) {
o->shadow = ival;
mask->shadow = true;
}
if (config_setting_lookup_bool(setting, "fade", &ival)) {
o->fade = ival;
mask->fade = true;
}
if (config_setting_lookup_bool(setting, "focus", &ival)) {
o->focus = ival;
mask->focus = true;
}
if (config_setting_lookup_bool(setting, "blur-background", &ival)) {
o->blur_background = ival;
mask->blur_background = true;
}
if (config_setting_lookup_bool(setting, "full-shadow", &ival)) {
o->full_shadow = ival;
mask->full_shadow = true;
}
if (config_setting_lookup_bool(setting, "redir-ignore", &ival)) {
o->redir_ignore = ival;
mask->redir_ignore = true;
}
if (config_setting_lookup_bool(setting, "clip-shadow-above", &ival)) {
o->clip_shadow_above = ival;
mask->clip_shadow_above = true;
}
if (config_setting_lookup_string(setting, "animation", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
animation = OPEN_WINDOW_ANIMATION_NONE;
o->animation = animation;
mask->animation = OPEN_WINDOW_ANIMATION_INVALID;
}
if (config_setting_lookup_string(setting, "animation-unmap", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
animation = OPEN_WINDOW_ANIMATION_NONE;
o->animation_unmap = animation;
mask->animation_unmap = OPEN_WINDOW_ANIMATION_INVALID;
}
if (config_setting_lookup_string(setting, "animation-workspace-in", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
animation = OPEN_WINDOW_ANIMATION_NONE;
o->animation_workspace_in = animation;
mask->animation_workspace_in = OPEN_WINDOW_ANIMATION_INVALID;
}
if (config_setting_lookup_string(setting, "animation-workspace-out", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
animation = OPEN_WINDOW_ANIMATION_NONE;
o->animation_workspace_out = animation;
mask->animation_workspace_out = OPEN_WINDOW_ANIMATION_INVALID;
}
double fval;
if (config_setting_lookup_float(setting, "opacity", &fval)) {
o->opacity = normalize_d(fval);
mask->opacity = true;
}
}
}
/**
* Parse a configuration file from default location.
*
* Returns the actually config_file name
*/
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;
int ival = 0;
bool bval;
double dval = 0.0;
// libconfig manages string memory itself, so no need to manually free
// anything
const char *sval = NULL;
f = open_config_file(config_file, &path);
if (!f) {
free(path);
if (config_file) {
log_fatal("Failed to read configuration file \"%s\".", config_file);
return ERR_PTR(-1);
}
return NULL;
}
config_init(&cfg);
#ifdef CONFIG_OPTION_ALLOW_OVERRIDES
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);
if (parent)
config_set_include_dir(&cfg, parent);
free(path2);
}
{
int read_result = config_read(&cfg, f);
fclose(f);
f = NULL;
if (read_result == CONFIG_FALSE) {
log_fatal("Error when reading configuration file \"%s\", line "
"%d: %s",
path, config_error_line(&cfg), config_error_text(&cfg));
goto err;
}
}
config_set_auto_convert(&cfg, 1);
// 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)) {
opt->fade_delta = ival;
}
// -I (fade_in_step)
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)) {
opt->fade_out_step = normalize_d(dval);
}
// -r (shadow_radius)
config_lookup_int(&cfg, "shadow-radius", &opt->shadow_radius);
// -o (shadow_opacity)
config_lookup_float(&cfg, "shadow-opacity", &opt->shadow_opacity);
// -l (shadow_offset_x)
config_lookup_int(&cfg, "shadow-offset-x", &opt->shadow_offset_x);
// -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)) {
opt->inactive_opacity = normalize_d(dval);
}
// --active_opacity
if (config_lookup_float(&cfg, "active-opacity", &dval)) {
opt->active_opacity = normalize_d(dval);
}
// --corner-radius
config_lookup_int(&cfg, "corner-radius", &opt->corner_radius);
// --rounded-corners-exclude
parse_cfg_condlst(&cfg, &opt->rounded_corners_blacklist, "rounded-corners-exclude");
// -e (frame_opacity)
config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity);
// -c (shadow_enable)
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.");
opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = dval;
opt->wintype_option[WINTYPE_POPUP_MENU].opacity = dval;
winopt_mask[WINTYPE_DROPDOWN_MENU].opacity = true;
winopt_mask[WINTYPE_POPUP_MENU].opacity = true;
}
// -f (fading_enable)
if (config_lookup_bool(&cfg, "fading", &ival))
*fading_enable = ival;
// --no-fading-open-close
lcfg_lookup_bool(&cfg, "no-fading-openclose", &opt->no_fading_openclose);
// --no-fading-destroyed-argb
lcfg_lookup_bool(&cfg, "no-fading-destroyed-argb", &opt->no_fading_destroyed_argb);
// --shadow-red
config_lookup_float(&cfg, "shadow-red", &opt->shadow_red);
// --shadow-green
config_lookup_float(&cfg, "shadow-green", &opt->shadow_green);
// --shadow-blue
config_lookup_float(&cfg, "shadow-blue", &opt->shadow_blue);
// --shadow-color
if (config_lookup_string(&cfg, "shadow-color", &sval)) {
struct color rgb;
rgb = hex_to_rgb(sval);
opt->shadow_red = rgb.red;
opt->shadow_green = rgb.green;
opt->shadow_blue = rgb.blue;
}
// --shadow-exclude-reg
if (config_lookup_string(&cfg, "shadow-exclude-reg", &sval))
opt->shadow_exclude_reg_str = strdup(sval);
// --inactive-opacity-override
lcfg_lookup_bool(&cfg, "inactive-opacity-override", &opt->inactive_opacity_override);
// --inactive-dim
config_lookup_float(&cfg, "inactive-dim", &opt->inactive_dim);
// --mark-wmwin-focused
lcfg_lookup_bool(&cfg, "mark-wmwin-focused", &opt->mark_wmwin_focused);
// --mark-ovredir-focused
lcfg_lookup_bool(&cfg, "mark-ovredir-focused", &opt->mark_ovredir_focused);
// --shadow-ignore-shaped
lcfg_lookup_bool(&cfg, "shadow-ignore-shaped", &opt->shadow_ignore_shaped);
// --detect-rounded-corners
lcfg_lookup_bool(&cfg, "detect-rounded-corners", &opt->detect_rounded_corners);
// --xinerama-shadow-crop
lcfg_lookup_bool(&cfg, "xinerama-shadow-crop", &opt->xinerama_shadow_crop);
// --detect-client-opacity
lcfg_lookup_bool(&cfg, "detect-client-opacity", &opt->detect_client_opacity);
// --refresh-rate
if (config_lookup_int(&cfg, "refresh-rate", &ival)) {
log_warn("The refresh-rate %s", deprecation_message);
}
// --vsync
if (config_lookup_string(&cfg, "vsync", &sval)) {
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
if (config_lookup_string(&cfg, "backend", &sval)) {
opt->backend = parse_backend(sval);
if (opt->backend >= NUM_BKEND) {
log_fatal("Cannot parse backend");
goto err;
}
}
// --log-level
if (config_lookup_string(&cfg, "log-level", &sval)) {
auto level = string_to_log_level(sval);
if (level == LOG_LEVEL_INVALID) {
log_warn("Invalid log level, defaults to WARN");
} else {
log_set_level_tls(level);
}
}
// --log-file
if (config_lookup_string(&cfg, "log-file", &sval)) {
if (*sval != '/') {
log_warn("The log-file in your configuration file is not an "
"absolute path");
}
opt->logpath = strdup(sval);
}
// --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
lcfg_lookup_bool(&cfg, "unredir-if-possible", &opt->unredir_if_possible);
// --unredir-if-possible-delay
if (config_lookup_int(&cfg, "unredir-if-possible-delay", &ival)) {
if (ival < 0) {
log_warn("Invalid unredir-if-possible-delay %d", ival);
} else {
opt->unredir_if_possible_delay = ival;
}
}
// --inactive-dim-fixed
lcfg_lookup_bool(&cfg, "inactive-dim-fixed", &opt->inactive_dim_fixed);
// --detect-transient
lcfg_lookup_bool(&cfg, "detect-transient", &opt->detect_transient);
// --detect-client-leader
lcfg_lookup_bool(&cfg, "detect-client-leader", &opt->detect_client_leader);
// --no-ewmh-fullscreen
lcfg_lookup_bool(&cfg, "no-ewmh-fullscreen", &opt->no_ewmh_fullscreen);
// --transparent-clipping
lcfg_lookup_bool(&cfg, "transparent-clipping", &opt->transparent_clipping);
// --shadow-exclude
parse_cfg_condlst(&cfg, &opt->shadow_blacklist, "shadow-exclude");
// --clip-shadow-above
parse_cfg_condlst(&cfg, &opt->shadow_clip_list, "clip-shadow-above");
// --fade-exclude
parse_cfg_condlst(&cfg, &opt->fade_blacklist, "fade-exclude");
// --animations
lcfg_lookup_bool(&cfg, "animations", &opt->animations);
// --animation-for-open-window
if (config_lookup_string(&cfg, "animation-for-open-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid open-window animation %s", sval);
goto err;
}
opt->animation_for_open_window = animation;
}
// --animation-for-transient-window
if (config_lookup_string(&cfg, "animation-for-transient-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid transient-window animation %s", sval);
goto err;
}
opt->animation_for_transient_window = animation;
}
// --animation-for-unmap-window
if (config_lookup_string(&cfg, "animation-for-unmap-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid unmap-window animation %s", sval);
goto err;
}
opt->animation_for_unmap_window = animation;
}
// --animation-for-workspace-switch-in
if (config_lookup_string(&cfg, "animation-for-workspace-switch-in", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid workspace-switch-in animation %s", sval);
goto err;
}
opt->animation_for_workspace_switch_in = animation;
}
// --animation-for-workspace-switch-out
if (config_lookup_string(&cfg, "animation-for-workspace-switch-out", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid workspace-switch-out animation %s", sval);
goto err;
}
opt->animation_for_workspace_switch_out = animation;
}
// --animation-stiffness
config_lookup_float(&cfg, "animation-stiffness", &opt->animation_stiffness);
// --animation-window-mass
config_lookup_float(&cfg, "animation-window-mass", &opt->animation_window_mass);
// --animation-dampening
config_lookup_float(&cfg, "animation-dampening", &opt->animation_dampening);
// --animation-delta
config_lookup_float(&cfg, "animation-delta", &opt->animation_delta);
// --animation-force-steps
lcfg_lookup_bool(&cfg, "animation-force-steps", &opt->animation_force_steps);
// --animation-clamping
lcfg_lookup_bool(&cfg, "animation-clamping", &opt->animation_clamping);
// --animation-exclude
parse_cfg_condlst(&cfg, &opt->animation_blacklist, "animation-exclude");
// --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");
// --opacity-rule
parse_cfg_condlst_opct(opt, &cfg, "opacity-rule");
// --unredir-if-possible-exclude
parse_cfg_condlst(&cfg, &opt->unredir_if_possible_blacklist,
"unredir-if-possible-exclude");
// --blur-method
if (config_lookup_string(&cfg, "blur-method", &sval)) {
enum blur_method method = parse_blur_method(sval);
if (method >= BLUR_METHOD_INVALID) {
log_fatal("Invalid blur method %s", sval);
goto err;
}
opt->blur_method = method;
}
// --blur-size
config_lookup_int(&cfg, "blur-size", &opt->blur_radius);
// --blur-deviation
config_lookup_float(&cfg, "blur-deviation", &opt->blur_deviation);
// --blur-strength
config_lookup_int(&cfg, "blur-strength", &opt->blur_strength);
// --blur-background
if (config_lookup_bool(&cfg, "blur-background", &ival) && ival) {
if (opt->blur_method == BLUR_METHOD_NONE) {
opt->blur_method = BLUR_METHOD_KERNEL;
}
}
// --blur-background-frame
lcfg_lookup_bool(&cfg, "blur-background-frame", &opt->blur_background_frame);
// --blur-background-fixed
lcfg_lookup_bool(&cfg, "blur-background-fixed", &opt->blur_background_fixed);
// --blur-kern
if (config_lookup_string(&cfg, "blur-kern", &sval)) {
opt->blur_kerns =
parse_blur_kern_lst(sval, conv_kern_hasneg, &opt->blur_kernel_count);
if (!opt->blur_kerns) {
log_fatal("Cannot parse \"blur-kern\"");
goto err;
}
}
// --resize-damage
config_lookup_int(&cfg, "resize-damage", &opt->resize_damage);
// --glx-no-stencil
lcfg_lookup_bool(&cfg, "glx-no-stencil", &opt->glx_no_stencil);
// --glx-no-rebind-pixmap
lcfg_lookup_bool(&cfg, "glx-no-rebind-pixmap", &opt->glx_no_rebind_pixmap);
lcfg_lookup_bool(&cfg, "force-win-blend", &opt->force_win_blend);
// --glx-swap-method
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;
}
if (strcmp(sval, "undefined") != 0 && val != 0) {
// If not undefined, we will use damage and buffer-age to limit
// the rendering area.
should_remove = false;
}
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);
// --max-brightness
if (config_lookup_float(&cfg, "max-brightness", &opt->max_brightness) &&
opt->use_damage && opt->max_brightness < 1) {
log_warn("max-brightness requires use-damage = false. Falling back to "
"1.0");
opt->max_brightness = 1.0;
}
// --glx-use-gpushader4
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
lcfg_lookup_bool(&cfg, "xrender-sync-fence", &opt->xrender_sync_fence);
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");
config_setting_t *blur_cfg = config_lookup(&cfg, "blur");
if (blur_cfg) {
if (config_setting_lookup_string(blur_cfg, "method", &sval)) {
enum blur_method method = parse_blur_method(sval);
if (method >= BLUR_METHOD_INVALID) {
log_warn("Invalid blur method %s, ignoring.", sval);
} else {
opt->blur_method = method;
}
}
config_setting_lookup_int(blur_cfg, "size", &opt->blur_radius);
if (config_setting_lookup_string(blur_cfg, "kernel", &sval)) {
opt->blur_kerns = parse_blur_kern_lst(sval, conv_kern_hasneg,
&opt->blur_kernel_count);
if (!opt->blur_kerns) {
log_warn("Failed to parse blur kernel: %s", sval);
}
}
config_setting_lookup_float(blur_cfg, "deviation", &opt->blur_deviation);
config_setting_lookup_int(blur_cfg, "strength", &opt->blur_strength);
}
// --write-pid-path
if (config_lookup_string(&cfg, "write-pid-path", &sval)) {
if (*sval != '/') {
log_warn("The write-pid-path in your configuration file is not"
" an absolute path");
}
opt->write_pid_path = strdup(sval);
}
// Wintype settings
// XXX ! Refactor all the wintype_* arrays into a struct
for (wintype_t i = 0; i < NUM_WINTYPES; ++i) {
parse_wintype_config(&cfg, WINTYPES[i], &opt->wintype_option[i],
&winopt_mask[i]);
}
// Compatibility with the old name for notification windows.
parse_wintype_config(&cfg, "notify", &opt->wintype_option[WINTYPE_NOTIFICATION],
&winopt_mask[WINTYPE_NOTIFICATION]);
config_destroy(&cfg);
return path;
err:
config_destroy(&cfg);
free(path);
return ERR_PTR(-1);
}
+1700
View File
File diff suppressed because it is too large. Load diff
+54
View File
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
/*
* Compton - a compositor for X11
*
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2011-2013, Christopher Jeffrey
* See LICENSE-mit for more information.
*
*/
#include <stdbool.h>
#include <dbus/dbus.h>
typedef struct session session_t;
struct win;
/**
* Return a string representation of a D-Bus message type.
*/
static inline const char *cdbus_repr_msgtype(DBusMessage *msg) {
return dbus_message_type_to_string(dbus_message_get_type(msg));
}
/**
* Initialize D-Bus connection.
*/
bool cdbus_init(session_t *ps, const char *uniq_name);
/**
* Destroy D-Bus connection.
*/
void cdbus_destroy(session_t *ps);
/// Generate dbus win_added signal
void cdbus_ev_win_added(session_t *ps, struct win *w);
/// Generate dbus win_destroyed signal
void cdbus_ev_win_destroyed(session_t *ps, struct win *w);
/// Generate dbus win_mapped signal
void cdbus_ev_win_mapped(session_t *ps, struct win *w);
/// Generate dbus win_unmapped signal
void cdbus_ev_win_unmapped(session_t *ps, struct win *w);
/// Generate dbus win_focusout signal
void cdbus_ev_win_focusout(session_t *ps, struct win *w);
/// Generate dbus win_focusin signal
void cdbus_ev_win_focusin(session_t *ps, struct win *w);
// vim: set noet sw=8 ts=8 :
+53
View File
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#include <stdio.h>
#include <xcb/xcb.h>
#include <xcb/composite.h>
#include "backend/driver.h"
#include "diagnostic.h"
#include "config.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("**CFLAGS:** %s\n", "??");
printf("\n### Extensions:\n\n");
printf("* Shape: %s\n", ps->shape_exists ? "Yes" : "No");
printf("* XRandR: %s\n", ps->randr_exists ? "Yes" : "No");
printf("* Present: %s\n", ps->present_exists ? "Present" : "Not Present");
printf("\n### Misc:\n\n");
printf("* Use Overlay: %s\n", ps->overlay != XCB_NONE ? "Yes" : "No");
if (ps->overlay == XCB_NONE) {
if (compositor_running) {
printf(" (Another compositor is already running)\n");
} else if (session_redirection_mode(ps) != XCB_COMPOSITE_REDIRECT_MANUAL) {
printf(" (Not in manual redirection mode)\n");
} else {
printf("\n");
}
}
#ifdef __FAST_MATH__
printf("* Fast Math: Yes\n");
#endif
printf("* Config file used: %s\n", config_file ?: "None");
printf("\n### Drivers (inaccurate):\n\n");
print_drivers(ps->drivers);
for (int i = 0; i < NUM_BKEND; i++) {
if (backend_list[i] && backend_list[i]->diagnostics) {
printf("\n### Backend: %s\n\n", BACKEND_STRS[i]);
auto data = backend_list[i]->init(ps);
if (!data) {
printf(" Cannot initialize this backend\n");
} else {
backend_list[i]->diagnostics(data);
backend_list[i]->deinit(data);
}
}
}
}
// vim: set noet sw=8 ts=8 :
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <stdbool.h>
typedef struct session session_t;
void print_diagnostics(session_t *, const char *config_file, bool compositor_running);
+37
View File
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2019 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "compiler.h"
// Functions for error reporting, adopted from Linux
// INFO in user space we can probably be more liberal about what pointer we consider
// error. e.g. In x86_64 Linux, all addresses with the highest bit set is invalid in user
// space.
#define MAX_ERRNO 4095
static inline void *must_use ERR_PTR(intptr_t err) {
return (void *)err;
}
static inline intptr_t must_use PTR_ERR(void *ptr) {
return (intptr_t)ptr;
}
static inline bool must_use IS_ERR(void *ptr) {
return unlikely((uintptr_t)ptr > (uintptr_t)-MAX_ERRNO);
}
static inline bool must_use IS_ERR_OR_NULL(void *ptr) {
return unlikely(!ptr) || IS_ERR(ptr);
}
static inline intptr_t must_use PTR_ERR_OR_ZERO(void *ptr) {
if (IS_ERR(ptr)) {
return PTR_ERR(ptr);
}
return 0;
}
+757
View File
@@ -0,0 +1,757 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2019, Yuxuan Shui <yshuiv7@gmail.com>
#include <stdio.h>
#include <X11/Xlibint.h>
#include <X11/extensions/sync.h>
#include <xcb/damage.h>
#include <xcb/randr.h>
#include "atom.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "event.h"
#include "log.h"
#include "picom.h"
#include "region.h"
#include "utils.h"
#include "win.h"
#include "x.h"
/// Event handling with X is complicated. Handling events with other events possibly
/// in-flight is no good. Because your internal state won't be up to date. Also, querying
/// the server while events are in-flight is not good. Because events later in the queue
/// might container information you are querying. Thus those events will cause you to do
/// unnecessary updates even when you already have the latest information (remember, you
/// made the query when those events were already in the queue. so the reply you got is
/// more up-to-date than the events). Also, handling events when other client are making
/// concurrent requests is not good. Because the server states are changing without you
/// knowning them. This is super racy, and can cause lots of potential problems.
///
/// All of above mandates we do these things:
/// 1. Grab server when handling events
/// 2. Make sure the event queue is empty before we make any query to the server
///
/// Notice (2) has a dependency circle. To handle events, you sometimes need to make
/// queries. But to make queries you have to first handle events.
///
/// To break that circle, we split all event handling into top and bottom halves. The
/// bottom half will just look at the event itself, update as much state as they can
/// without making queries, then queue up necessary works need to be done by the top half.
/// The top half will do all the other necessary updates. Before entering the top half, we
/// grab the server and make sure the event queue is empty.
///
/// When top half finished, we enter the render stage, where no server state should be
/// queried. All rendering should be done with our internal knowledge of the server state.
///
// TODO(yshui) the things described above
/**
* Get a window's name from window ID.
*/
static inline const char *ev_window_name(session_t *ps, xcb_window_t wid) {
char *name = "";
if (wid) {
name = "(Failed to get title)";
if (ps->root == wid) {
name = "(Root window)";
} else if (ps->overlay == wid) {
name = "(Overlay)";
} else {
auto w = find_managed_win(ps, wid);
if (!w) {
w = find_toplevel(ps, wid);
}
if (w && w->name) {
name = w->name;
}
}
}
return name;
}
static inline xcb_window_t attr_pure ev_window(session_t *ps, xcb_generic_event_t *ev) {
switch (ev->response_type) {
case FocusIn:
case FocusOut: return ((xcb_focus_in_event_t *)ev)->event;
case CreateNotify: return ((xcb_create_notify_event_t *)ev)->window;
case ConfigureNotify: return ((xcb_configure_notify_event_t *)ev)->window;
case DestroyNotify: return ((xcb_destroy_notify_event_t *)ev)->window;
case MapNotify: return ((xcb_map_notify_event_t *)ev)->window;
case UnmapNotify: return ((xcb_unmap_notify_event_t *)ev)->window;
case ReparentNotify: return ((xcb_reparent_notify_event_t *)ev)->window;
case CirculateNotify: return ((xcb_circulate_notify_event_t *)ev)->window;
case Expose: return ((xcb_expose_event_t *)ev)->window;
case PropertyNotify: return ((xcb_property_notify_event_t *)ev)->window;
case ClientMessage: return ((xcb_client_message_event_t *)ev)->window;
default:
if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) {
return ((xcb_damage_notify_event_t *)ev)->drawable;
}
if (ps->shape_exists && ev->response_type == ps->shape_event) {
return ((xcb_shape_notify_event_t *)ev)->affected_window;
}
return 0;
}
}
#define CASESTRRET(s) \
case s: return #s;
static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) {
static char buf[128];
switch (ev->response_type & 0x7f) {
CASESTRRET(FocusIn);
CASESTRRET(FocusOut);
CASESTRRET(CreateNotify);
CASESTRRET(ConfigureNotify);
CASESTRRET(DestroyNotify);
CASESTRRET(MapNotify);
CASESTRRET(UnmapNotify);
CASESTRRET(ReparentNotify);
CASESTRRET(CirculateNotify);
CASESTRRET(Expose);
CASESTRRET(PropertyNotify);
CASESTRRET(ClientMessage);
}
if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type)
return "Damage";
if (ps->shape_exists && ev->response_type == ps->shape_event)
return "ShapeNotify";
if (ps->xsync_exists) {
int o = ev->response_type - ps->xsync_event;
switch (o) {
CASESTRRET(XSyncCounterNotify);
CASESTRRET(XSyncAlarmNotify);
}
}
sprintf(buf, "Event %d", ev->response_type);
return buf;
}
static inline const char *attr_pure ev_focus_mode_name(xcb_focus_in_event_t *ev) {
switch (ev->mode) {
CASESTRRET(NotifyNormal);
CASESTRRET(NotifyWhileGrabbed);
CASESTRRET(NotifyGrab);
CASESTRRET(NotifyUngrab);
}
return "Unknown";
}
static inline const char *attr_pure ev_focus_detail_name(xcb_focus_in_event_t *ev) {
switch (ev->detail) {
CASESTRRET(NotifyAncestor);
CASESTRRET(NotifyVirtual);
CASESTRRET(NotifyInferior);
CASESTRRET(NotifyNonlinear);
CASESTRRET(NotifyNonlinearVirtual);
CASESTRRET(NotifyPointer);
CASESTRRET(NotifyPointerRoot);
CASESTRRET(NotifyDetailNone);
}
return "Unknown";
}
#undef CASESTRRET
static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) {
log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
ev_focus_detail_name(ev));
ps->pending_updates = true;
}
static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) {
log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
ev_focus_detail_name(ev));
ps->pending_updates = true;
}
static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
if (ev->parent == ps->root) {
add_win_top(ps, ev->window);
}
}
/// Handle configure event of a regular window
static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) {
auto w = find_win(ps, ce->window);
if (!w) {
return;
}
if (!w->managed) {
restack_above(ps, w, ce->above_sibling);
return;
}
auto mw = (struct managed_win *)w;
restack_above(ps, w, ce->above_sibling);
// We check against pending_g here, because there might have been multiple
// configure notifies in this cycle, or the window could receive multiple updates
// while it's unmapped.
bool position_changed = mw->pending_g.x != ce->x || mw->pending_g.y != ce->y;
bool size_changed = mw->pending_g.width != ce->width ||
mw->pending_g.height != ce->height ||
mw->pending_g.border_width != ce->border_width;
if (position_changed || size_changed) {
// Queue pending updates
win_set_flags(mw, WIN_FLAGS_FACTOR_CHANGED);
// TODO(yshui) don't set pending_updates if the window is not
// visible/mapped
ps->pending_updates = true;
// At least one of the following if's is true
if (position_changed) {
log_trace("Window position changed, %dx%d -> %dx%d", mw->g.x,
mw->g.y, ce->x, ce->y);
mw->pending_g.x = ce->x;
mw->pending_g.y = ce->y;
win_set_flags(mw, WIN_FLAGS_POSITION_STALE);
}
if (size_changed) {
log_trace("Window size changed, %dx%d -> %dx%d", mw->g.width,
mw->g.height, ce->width, ce->height);
mw->pending_g.width = ce->width;
mw->pending_g.height = ce->height;
mw->pending_g.border_width = ce->border_width;
win_set_flags(mw, WIN_FLAGS_SIZE_STALE);
}
// Recalculate which screen this window is on
win_update_screen(ps->xinerama_nscrs, ps->xinerama_scr_regs, mw);
}
// override_redirect flag cannot be changed after window creation, as far
// as I know, so there's no point to re-match windows here.
mw->a.override_redirect = ce->override_redirect;
}
static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) {
log_debug("{ send_event: %d, id: %#010x, above: %#010x, override_redirect: %d }",
ev->event, ev->window, ev->above_sibling, ev->override_redirect);
if (ev->window == ps->root) {
set_root_flags(ps, ROOT_FLAGS_CONFIGURED);
} else {
configure_win(ps, ev);
}
}
static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) {
auto w = find_win(ps, ev->window);
auto mw = find_toplevel(ps, ev->window);
if (mw && mw->client_win == mw->base.id) {
// We only want _real_ frame window
assert(&mw->base == w);
mw = NULL;
}
assert(w == NULL || mw == NULL);
if (w != NULL) {
auto _ attr_unused = destroy_win_start(ps, w);
} else if (mw != NULL) {
win_unmark_client(ps, mw);
win_set_flags(mw, WIN_FLAGS_CLIENT_STALE);
ps->pending_updates = true;
} else {
log_debug("Received a destroy notify from an unknown window, %#010x",
ev->window);
}
}
static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) {
// Unmap overlay window if it got mapped but we are currently not
// in redirected state.
if (ps->overlay && ev->window == ps->overlay && !ps->redirected) {
log_debug("Overlay is mapped while we are not redirected");
auto e = xcb_request_check(ps->c, xcb_unmap_window(ps->c, ps->overlay));
if (e) {
log_error("Failed to unmap the overlay window");
free(e);
}
// We don't track the overlay window, so we can return
return;
}
auto w = find_managed_win(ps, ev->window);
if (!w) {
return;
}
win_set_flags(w, WIN_FLAGS_MAPPED);
// FocusIn/Out may be ignored when the window is unmapped, so we must
// recheck focus here
ps->pending_updates = true; // to update focus
}
static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) {
auto w = find_managed_win(ps, ev->window);
if (w) {
unmap_win_start(ps, w);
}
}
static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) {
log_debug("Window %#010x has new parent: %#010x, override_redirect: %d",
ev->window, ev->parent, ev->override_redirect);
auto w_top = find_toplevel(ps, ev->window);
if (w_top) {
win_unmark_client(ps, w_top);
win_set_flags(w_top, WIN_FLAGS_CLIENT_STALE);
ps->pending_updates = true;
}
if (ev->parent == ps->root) {
// X will generate reparent notifiy even if the parent didn't actually
// change (i.e. reparent again to current parent). So we check if that's
// the case
auto w = find_win(ps, ev->window);
if (w) {
// This window has already been reparented to root before,
// so we don't need to create a new window for it, we just need to
// move it to the top
restack_top(ps, w);
} else {
add_win_top(ps, ev->window);
}
} else {
// otherwise, find and destroy the window first
{
auto w = find_win(ps, ev->window);
if (w) {
auto ret = destroy_win_start(ps, w);
if (!ret && w->managed) {
auto mw = (struct managed_win *)w;
CHECK(win_skip_fading(ps, mw));
}
}
}
// Reset event mask in case something wrong happens
xcb_change_window_attributes(
ps->c, ev->window, XCB_CW_EVENT_MASK,
(const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)});
if (!wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) {
log_debug("Window %#010x doesn't have WM_STATE property, it is "
"probably not a client window. But we will listen for "
"property change in case it gains one.",
ev->window);
xcb_change_window_attributes(
ps->c, ev->window, XCB_CW_EVENT_MASK,
(const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) |
XCB_EVENT_MASK_PROPERTY_CHANGE});
} else {
auto w_real_top = find_managed_window_or_parent(ps, ev->parent);
if (w_real_top && w_real_top->state != WSTATE_UNMAPPED &&
w_real_top->state != WSTATE_UNMAPPING) {
log_debug("Mark window %#010x (%s) as having a stale "
"client",
w_real_top->base.id, w_real_top->name);
win_set_flags(w_real_top, WIN_FLAGS_CLIENT_STALE);
ps->pending_updates = true;
} else {
if (!w_real_top)
log_debug("parent %#010x not found", ev->parent);
else {
// Window is not currently mapped, unmark its
// client to trigger a client recheck when it is
// mapped later.
win_unmark_client(ps, w_real_top);
log_debug("parent %#010x (%s) is in state %d",
w_real_top->base.id, w_real_top->name,
w_real_top->state);
}
}
}
}
}
static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) {
auto w = find_win(ps, ev->window);
if (!w)
return;
if (ev->place == PlaceOnTop) {
restack_top(ps, w);
} else {
restack_bottom(ps, w);
}
}
static inline void expose_root(session_t *ps, const rect_t *rects, int nrects) {
region_t region;
pixman_region32_init_rects(&region, rects, nrects);
add_damage(ps, &region);
pixman_region32_fini(&region);
}
static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) {
if (ev->window == ps->root || (ps->overlay && ev->window == ps->overlay)) {
int more = ev->count + 1;
if (ps->n_expose == ps->size_expose) {
if (ps->expose_rects) {
ps->expose_rects =
crealloc(ps->expose_rects, ps->size_expose + more);
ps->size_expose += more;
} else {
ps->expose_rects = ccalloc(more, rect_t);
ps->size_expose = more;
}
}
ps->expose_rects[ps->n_expose].x1 = ev->x;
ps->expose_rects[ps->n_expose].y1 = ev->y;
ps->expose_rects[ps->n_expose].x2 = ev->x + ev->width;
ps->expose_rects[ps->n_expose].y2 = ev->y + ev->height;
ps->n_expose++;
if (ev->count == 0) {
expose_root(ps, ps->expose_rects, ps->n_expose);
ps->n_expose = 0;
}
}
}
static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) {
if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
// Print out changed atom
xcb_get_atom_name_reply_t *reply =
xcb_get_atom_name_reply(ps->c, xcb_get_atom_name(ps->c, ev->atom), NULL);
const char *name = "?";
int name_len = 1;
if (reply) {
name = xcb_get_atom_name_name(reply);
name_len = xcb_get_atom_name_name_length(reply);
}
log_debug("{ atom = %.*s }", name_len, name);
free(reply);
}
if (ps->root == ev->window) {
// If desktop number property changes
if (ev->atom == ps->atoms->a_NET_CURRENT_DESKTOP) {
auto prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_DESKTOP,
1L, XCB_ATOM_CARDINAL, 32);
if (prop.nitems) {
ps->root_desktop_switch_direction = ((int)*prop.c32) - ps->root_desktop_num;
ps->root_desktop_num = (int)*prop.c32;
}
}
if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) {
// to update focus
ps->pending_updates = true;
} else {
// Destroy the root "image" if the wallpaper probably changed
if (x_is_root_back_pixmap_atom(ps->atoms, ev->atom)) {
root_damaged(ps);
}
}
// Unconcerned about any other proprties on root window
return;
}
ps->pending_updates = true;
// If WM_STATE changes
if (ev->atom == ps->atoms->aWM_STATE) {
// Check whether it could be a client window
if (!find_toplevel(ps, ev->window)) {
// Reset event mask anyway
xcb_change_window_attributes(ps->c, ev->window, XCB_CW_EVENT_MASK,
(const uint32_t[]){determine_evmask(
ps, ev->window, WIN_EVMODE_UNKNOWN)});
auto w_top = find_managed_window_or_parent(ps, ev->window);
// ev->window might have not been managed yet, in that case w_top
// would be NULL.
if (w_top) {
win_set_flags(w_top, WIN_FLAGS_CLIENT_STALE);
}
}
return;
}
// If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
// there are always some stupid applications. (#144)
if (ev->atom == ps->atoms->a_NET_WM_WINDOW_TYPE) {
struct managed_win *w = NULL;
if ((w = find_toplevel(ps, ev->window))) {
win_set_property_stale(w, ev->atom);
}
}
if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) {
// Unnecessay until we remove the queue_redraw in ev_handle
queue_redraw(ps);
}
// 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) {
win_set_property_stale(w, ev->atom);
}
}
// If frame extents property changes
if (ev->atom == ps->atoms->a_NET_FRAME_EXTENTS) {
auto w = find_toplevel(ps, ev->window);
if (w) {
win_set_property_stale(w, ev->atom);
}
}
// If name changes
if (ps->atoms->aWM_NAME == ev->atom || ps->atoms->a_NET_WM_NAME == ev->atom) {
auto w = find_toplevel(ps, ev->window);
if (w) {
win_set_property_stale(w, ev->atom);
}
}
// If class changes
if (ps->atoms->aWM_CLASS == ev->atom) {
auto w = find_toplevel(ps, ev->window);
if (w) {
win_set_property_stale(w, ev->atom);
}
}
// If role changes
if (ps->atoms->aWM_WINDOW_ROLE == ev->atom) {
auto w = find_toplevel(ps, ev->window);
if (w) {
win_set_property_stale(w, ev->atom);
}
}
// If _COMPTON_SHADOW changes
if (ps->atoms->a_COMPTON_SHADOW == ev->atom) {
auto w = find_managed_win(ps, ev->window);
if (w) {
win_set_property_stale(w, ev->atom);
}
}
// If a leader property changes
if ((ps->o.detect_transient && ps->atoms->aWM_TRANSIENT_FOR == ev->atom) ||
(ps->o.detect_client_leader && ps->atoms->aWM_CLIENT_LEADER == ev->atom)) {
auto w = find_toplevel(ps, ev->window);
if (w) {
win_set_property_stale(w, ev->atom);
}
}
// Check for other atoms we are tracking
for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
if (platom->atom == ev->atom) {
auto w = find_managed_win(ps, ev->window);
if (!w) {
w = find_toplevel(ps, ev->window);
}
if (w) {
// Set FACTOR_CHANGED so rules based on properties will be
// re-evaluated.
// Don't need to set property stale here, since that only
// concerns properties we explicitly check.
win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED);
}
break;
}
}
}
static inline void repair_win(session_t *ps, struct managed_win *w) {
// Only mapped window can receive damages
assert(win_is_mapped_in_x(w));
region_t parts;
pixman_region32_init(&parts);
if (!w->ever_damaged) {
win_extents(w, &parts);
set_ignore_cookie(
ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, XCB_NONE));
} else {
set_ignore_cookie(
ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, ps->damaged_region));
x_fetch_region(ps->c, ps->damaged_region, &parts);
pixman_region32_translate(&parts, w->g.x + w->g.border_width,
w->g.y + w->g.border_width);
}
log_trace("Mark window %#010x (%s) as having received damage", w->base.id, w->name);
w->ever_damaged = true;
w->pixmap_damaged = true;
// Why care about damage when screen is unredirected?
// We will force full-screen repaint on redirection.
if (!ps->redirected) {
pixman_region32_fini(&parts);
return;
}
// Remove the part in the damage area that could be ignored
if (w->reg_ignore && win_is_region_ignore_valid(ps, w)) {
pixman_region32_subtract(&parts, &parts, w->reg_ignore);
}
add_damage(ps, &parts);
pixman_region32_fini(&parts);
}
static inline void ev_damage_notify(session_t *ps, xcb_damage_notify_event_t *de) {
/*
if (ps->root == de->drawable) {
root_damaged();
return;
} */
auto w = find_managed_win(ps, de->drawable);
if (!w) {
return;
}
repair_win(ps, w);
}
static inline void ev_shape_notify(session_t *ps, xcb_shape_notify_event_t *ev) {
auto w = find_managed_win(ps, ev->affected_window);
if (!w || w->a.map_state == XCB_MAP_STATE_UNMAPPED) {
return;
}
/*
* Empty bounding_shape may indicated an
* unmapped/destroyed window, in which case
* seemingly BadRegion errors would be triggered
* if we attempt to rebuild border_size
*/
// Mark the old bounding shape as damaged
if (!win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) {
region_t tmp = win_get_bounding_shape_global_by_val(w);
add_damage(ps, &tmp);
pixman_region32_fini(&tmp);
}
w->reg_ignore_valid = false;
win_set_flags(w, WIN_FLAGS_SIZE_STALE);
ps->pending_updates = true;
}
static inline void
ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) {
// The only selection we own is the _NET_WM_CM_Sn selection.
// If we lose that one, we should exit.
log_fatal("Another composite manager started and took the _NET_WM_CM_Sn "
"selection.");
quit(ps);
}
void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
if ((ev->response_type & 0x7f) != KeymapNotify) {
discard_ignore(ps, ev->full_sequence);
}
xcb_window_t wid = ev_window(ps, ev);
if (ev->response_type != ps->damage_event + XCB_DAMAGE_NOTIFY) {
log_debug("event %10.10s serial %#010x window %#010x \"%s\"",
ev_name(ps, ev), ev->full_sequence, wid, ev_window_name(ps, wid));
} else {
log_trace("event %10.10s serial %#010x window %#010x \"%s\"",
ev_name(ps, ev), ev->full_sequence, wid, ev_window_name(ps, wid));
}
// Check if a custom XEvent constructor was registered in xlib for this event
// type, and call it discarding the constructed XEvent if any. XESetWireToEvent
// might be used by libraries to intercept messages from the X server e.g. the
// OpenGL lib waiting for DRI2 events.
// XXX This exists to workaround compton issue #33, #34, #47
// For even more details, see:
// https://bugs.freedesktop.org/show_bug.cgi?id=35945
// https://lists.freedesktop.org/archives/xcb/2011-November/007337.html
auto proc = XESetWireToEvent(ps->dpy, ev->response_type, 0);
if (proc) {
XESetWireToEvent(ps->dpy, ev->response_type, proc);
XEvent dummy;
// Stop Xlib from complaining about lost sequence numbers.
// proc might also just be Xlib internal event processing functions, and
// because they probably won't see all X replies, they will complain about
// missing sequence numbers.
//
// We only need the low 16 bits
ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->dpy) & 0xffff);
proc(ps->dpy, &dummy, (xEvent *)ev);
}
// XXX redraw needs to be more fine grained
queue_redraw(ps);
switch (ev->response_type) {
case FocusIn: ev_focus_in(ps, (xcb_focus_in_event_t *)ev); break;
case FocusOut: ev_focus_out(ps, (xcb_focus_out_event_t *)ev); break;
case CreateNotify: ev_create_notify(ps, (xcb_create_notify_event_t *)ev); break;
case ConfigureNotify:
ev_configure_notify(ps, (xcb_configure_notify_event_t *)ev);
break;
case DestroyNotify:
ev_destroy_notify(ps, (xcb_destroy_notify_event_t *)ev);
break;
case MapNotify: ev_map_notify(ps, (xcb_map_notify_event_t *)ev); break;
case UnmapNotify: ev_unmap_notify(ps, (xcb_unmap_notify_event_t *)ev); break;
case ReparentNotify:
ev_reparent_notify(ps, (xcb_reparent_notify_event_t *)ev);
break;
case CirculateNotify:
ev_circulate_notify(ps, (xcb_circulate_notify_event_t *)ev);
break;
case Expose: ev_expose(ps, (xcb_expose_event_t *)ev); break;
case PropertyNotify:
ev_property_notify(ps, (xcb_property_notify_event_t *)ev);
break;
case SelectionClear:
ev_selection_clear(ps, (xcb_selection_clear_event_t *)ev);
break;
case 0: ev_xcb_error(ps, (xcb_generic_error_t *)ev); break;
default:
if (ps->shape_exists && ev->response_type == ps->shape_event) {
ev_shape_notify(ps, (xcb_shape_notify_event_t *)ev);
break;
}
if (ps->randr_exists &&
ev->response_type == (ps->randr_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY)) {
set_root_flags(ps, ROOT_FLAGS_SCREEN_CHANGE);
break;
}
if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) {
ev_damage_notify(ps, (xcb_damage_notify_event_t *)ev);
break;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2019, Yuxuan Shui <yshuiv7@gmail.com>
#include <xcb/xcb.h>
#include "common.h"
void ev_handle(session_t *ps, xcb_generic_event_t *ev);
+188
View File
@@ -0,0 +1,188 @@
#include <errno.h>
#include <string.h>
#ifdef HAS_INOTIFY
#include <sys/inotify.h>
#elif HAS_KQUEUE
// clang-format off
#include <sys/types.h>
// clang-format on
#include <sys/event.h>
#undef EV_ERROR // Avoid clashing with libev's EV_ERROR
#include <fcntl.h> // For O_RDONLY
#include <sys/time.h> // For struct timespec
#include <unistd.h> // For open
#endif
#include <ev.h>
#include <uthash.h>
#include "file_watch.h"
#include "list.h"
#include "log.h"
#include "utils.h"
struct watched_file {
int wd;
void *ud;
file_watch_cb_t cb;
UT_hash_handle hh;
};
struct file_watch_registry {
struct ev_io w;
struct watched_file *reg;
};
static void file_watch_ev_cb(EV_P attr_unused, struct ev_io *w, int revent attr_unused) {
auto fwr = (struct file_watch_registry *)w;
while (true) {
int wd = -1;
#ifdef HAS_INOTIFY
struct inotify_event inotify_event;
auto ret = read(w->fd, &inotify_event, sizeof(struct inotify_event));
if (ret < 0) {
if (errno != EAGAIN) {
log_error_errno("Failed to read from inotify fd");
}
break;
}
wd = inotify_event.wd;
#elif HAS_KQUEUE
struct kevent ev;
struct timespec timeout = {0};
int ret = kevent(fwr->w.fd, NULL, 0, &ev, 1, &timeout);
if (ret <= 0) {
if (ret < 0) {
log_error_errno("Failed to get kevent");
}
break;
}
wd = (int)ev.ident;
#else
assert(false);
#endif
struct watched_file *wf = NULL;
HASH_FIND_INT(fwr->reg, &wd, wf);
if (!wf) {
log_warn("Got notification for a file I didn't watch.");
continue;
}
wf->cb(wf->ud);
}
}
void *file_watch_init(EV_P) {
log_debug("Starting watching for file changes");
int fd = -1;
#ifdef HAS_INOTIFY
fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (fd < 0) {
log_error_errno("inotify_init1 failed");
return NULL;
}
#elif HAS_KQUEUE
fd = kqueue();
if (fd < 0) {
log_error_errno("Failed to create kqueue");
return NULL;
}
#else
log_info("No file watching support found on the host system.");
return NULL;
#endif
auto fwr = ccalloc(1, struct file_watch_registry);
ev_io_init(&fwr->w, file_watch_ev_cb, fd, EV_READ);
ev_io_start(EV_A_ & fwr->w);
return fwr;
}
void file_watch_destroy(EV_P_ void *_fwr) {
log_debug("Stopping watching for file changes");
auto fwr = (struct file_watch_registry *)_fwr;
struct watched_file *i, *tmp;
HASH_ITER(hh, fwr->reg, i, tmp) {
HASH_DEL(fwr->reg, i);
#ifdef HAS_KQUEUE
// kqueue watch descriptors are file descriptors of
// the files we are watching, so we need to close
// them
close(i->wd);
#endif
free(i);
}
ev_io_stop(EV_A_ & fwr->w);
close(fwr->w.fd);
free(fwr);
}
bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *ud) {
log_debug("Adding \"%s\" to watched files", filename);
auto fwr = (struct file_watch_registry *)_fwr;
int wd = -1;
struct stat statbuf;
int ret = stat(filename, &statbuf);
if (ret < 0) {
log_error_errno("Failed to retrieve information about file \"%s\"", filename);
return false;
}
if (!S_ISREG(statbuf.st_mode)) {
log_info("\"%s\" is not a regular file, not watching it.", filename);
return false;
}
#ifdef HAS_INOTIFY
wd = inotify_add_watch(fwr->w.fd, filename,
IN_CLOSE_WRITE | IN_MOVE_SELF | IN_DELETE_SELF);
if (wd < 0) {
log_error_errno("Failed to watch file \"%s\"", filename);
return false;
}
#elif HAS_KQUEUE
wd = open(filename, O_RDONLY);
if (wd < 0) {
log_error_errno("Cannot open file \"%s\" for watching", filename);
return false;
}
uint32_t fflags = NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE | NOTE_ATTRIB;
// NOTE_CLOSE_WRITE is relatively new, so we cannot just use it
#ifdef NOTE_CLOSE_WRITE
fflags |= NOTE_CLOSE_WRITE;
#else
// NOTE_WRITE will receive notification more frequent than necessary, so is less
// preferrable
fflags |= NOTE_WRITE;
#endif
struct kevent ev = {
.ident = (unsigned int)wd, // the wd < 0 case is checked above
.filter = EVFILT_VNODE,
.flags = EV_ADD | EV_CLEAR,
.fflags = fflags,
.data = 0,
.udata = NULL,
};
if (kevent(fwr->w.fd, &ev, 1, NULL, 0, NULL) < 0) {
log_error_errno("Failed to register kevent");
close(wd);
return false;
}
#else
assert(false);
#endif // HAS_KQUEUE
auto w = ccalloc(1, struct watched_file);
w->wd = wd;
w->cb = cb;
w->ud = ud;
HASH_ADD_INT(fwr->reg, wd, w);
return true;
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <stdbool.h>
#include <ev.h>
typedef void (*file_watch_cb_t)(void *);
void *file_watch_init(EV_P);
bool file_watch_add(void *, const char *, file_watch_cb_t, void *);
void file_watch_destroy(EV_P_ void *);
+160
View File
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <assert.h>
#include <math.h>
#include "compiler.h"
#include "kernel.h"
#include "log.h"
#include "utils.h"
/// Sum a region convolution kernel. Region is defined by a width x height rectangle whose
/// top left corner is at (x, y)
double sum_kernel(const conv *map, int x, int y, int width, int height) {
double ret = 0;
// Compute sum of values which are "in range"
int xstart = normalize_i_range(x, 0, map->w),
xend = normalize_i_range(width + x, 0, map->w);
int ystart = normalize_i_range(y, 0, map->h),
yend = normalize_i_range(height + y, 0, map->h);
assert(yend >= ystart && xend >= xstart);
int d = map->w;
if (map->rsum) {
// See sum_kernel_preprocess
double v1 = xstart ? map->rsum[(yend - 1) * d + xstart - 1] : 0;
double v2 = ystart ? map->rsum[(ystart - 1) * d + xend - 1] : 0;
double v3 = (xstart && ystart) ? map->rsum[(ystart - 1) * d + xstart - 1] : 0;
return map->rsum[(yend - 1) * d + xend - 1] - v1 - v2 + v3;
}
for (int yi = ystart; yi < yend; yi++) {
for (int xi = xstart; xi < xend; xi++) {
ret += map->data[yi * d + xi];
}
}
return ret;
}
double sum_kernel_normalized(const conv *map, int x, int y, int width, int height) {
double ret = sum_kernel(map, x, y, width, height);
if (ret < 0) {
ret = 0;
}
if (ret > 1) {
ret = 1;
}
return ret;
}
static inline double attr_const gaussian(double r, double x, double y) {
// Formula can be found here:
// https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics
// Except a special case for r == 0 to produce sharp shadows
if (r == 0)
return 1;
return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r);
}
conv *gaussian_kernel(double r, int size) {
conv *c;
int center = size / 2;
double t;
assert(size % 2 == 1);
c = cvalloc(sizeof(conv) + (size_t)(size * size) * sizeof(double));
c->w = c->h = size;
c->rsum = NULL;
t = 0.0;
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
double g = gaussian(r, x - center, y - center);
t += g;
c->data[y * size + x] = g;
}
}
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
c->data[y * size + x] /= t;
}
}
return c;
}
/// 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) {
double factor = erf(size / r / sqrt(2));
double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r;
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) {
assert(size > 0);
if (row_limit >= 1.0 / 2.0 / size) {
return size * 2;
}
double l = 0, r = size * 2;
while (r - l > 1e-2) {
double mid = (l + r) / 2.0;
double vmid = estimate_first_row_sum(size, mid);
if (vmid > row_limit) {
r = mid;
} else {
l = mid;
}
}
return (l + r) / 2.0;
}
/// 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, 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) {
assert(shadow_radius >= 0);
int size = 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);
return gaussian_kernel(std, size);
}
/// 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
void sum_kernel_preprocess(conv *map) {
if (map->rsum) {
free(map->rsum);
}
auto sum = map->rsum = ccalloc(map->w * map->h, double);
sum[0] = map->data[0];
for (int x = 1; x < map->w; x++) {
sum[x] = sum[x - 1] + map->data[x];
}
const int d = map->w;
for (int y = 1; y < map->h; y++) {
sum[y * d] = sum[(y - 1) * d] + map->data[y * d];
for (int x = 1; x < map->w; x++) {
double tmp = sum[(y - 1) * d + x] + sum[y * d + x - 1] -
sum[(y - 1) * d + x - 1];
sum[y * d + x] = tmp + map->data[y * d + x];
}
}
}
// vim: set noet sw=8 ts=8 :
+39
View File
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <stdlib.h>
#include "compiler.h"
/// Code for generating convolution kernels
typedef struct conv {
int w, h;
double *rsum;
double data[];
} conv;
/// Calculate the sum of a rectangle part of the convolution kernel
/// the rectangle is defined by top left (x, y), and a size (width x height)
double attr_pure sum_kernel(const conv *map, int x, int y, int width, int height);
double attr_pure sum_kernel_normalized(const conv *map, int x, int y, int width, int height);
/// Create a kernel with gaussian distribution with standard deviation `r`, and size
/// `size`.
conv *gaussian_kernel(double r, int size);
/// 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);
/// 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
void sum_kernel_preprocess(conv *map);
static inline void free_conv(conv *k) {
free(k->rsum);
free(k);
}
+108
View File
@@ -0,0 +1,108 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) \
({ \
const __typeof__(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
struct list_node {
struct list_node *next, *prev;
};
#define list_entry(ptr, type, node) container_of(ptr, type, node)
#define list_next_entry(ptr, node) list_entry((ptr)->node.next, __typeof__(*(ptr)), node)
#define list_prev_entry(ptr, node) list_entry((ptr)->node.prev, __typeof__(*(ptr)), node)
/// Insert a new node between two adjacent nodes in the list
static inline void __list_insert_between(struct list_node *prev, struct list_node *next,
struct list_node *new_) {
new_->prev = prev;
new_->next = next;
next->prev = new_;
prev->next = new_;
}
/// Insert a new node after `curr`
static inline void list_insert_after(struct list_node *curr, struct list_node *new_) {
__list_insert_between(curr, curr->next, new_);
}
/// Insert a new node before `curr`
static inline void list_insert_before(struct list_node *curr, struct list_node *new_) {
__list_insert_between(curr->prev, curr, new_);
}
/// Link two nodes in the list, so `next` becomes the successor node of `prev`
static inline void __list_link(struct list_node *prev, struct list_node *next) {
next->prev = prev;
prev->next = next;
}
/// Remove a node from the list
static inline void list_remove(struct list_node *to_remove) {
__list_link(to_remove->prev, to_remove->next);
to_remove->prev = (void *)-1;
to_remove->next = (void *)-2;
}
/// Move `to_move` so that it's before `new_next`
static inline void list_move_before(struct list_node *to_move, struct list_node *new_next) {
list_remove(to_move);
list_insert_before(new_next, to_move);
}
/// Move `to_move` so that it's after `new_prev`
static inline void list_move_after(struct list_node *to_move, struct list_node *new_prev) {
list_remove(to_move);
list_insert_after(new_prev, to_move);
}
/// Initialize a list node that's intended to be the head node
static inline void list_init_head(struct list_node *head) {
head->next = head->prev = head;
}
/// Replace list node `old` with `n`
static inline void list_replace(struct list_node *old, struct list_node *n) {
__list_insert_between(old->prev, old->next, n);
old->prev = (void *)-1;
old->next = (void *)-2;
}
/// Return true if head is the only node in the list. Under usual circumstances this means
/// the list is empty
static inline bool list_is_empty(const struct list_node *head) {
return head->prev == head;
}
/// Return true if `to_check` is the first node in list headed by `head`
static inline bool
list_node_is_first(const struct list_node *head, const struct list_node *to_check) {
return head->next == to_check;
}
/// Return true if `to_check` is the last node in list headed by `head`
static inline bool
list_node_is_last(const struct list_node *head, const struct list_node *to_check) {
return head->prev == to_check;
}
#define list_foreach(type, i, head, member) \
for (type *i = list_entry((head)->next, type, member); &i->member != (head); \
i = list_next_entry(i, member))
/// Like list_for_each, but it's safe to remove the current list node from the list
#define list_foreach_safe(type, i, head, member) \
for (type *i = list_entry((head)->next, type, member), \
*__tmp = list_next_entry(i, member); \
&i->member != (head); i = __tmp, __tmp = list_next_entry(i, member))
+376
View File
@@ -0,0 +1,376 @@
#include <assert.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <time.h>
#include <unistd.h>
#ifdef CONFIG_OPENGL
#include <GL/gl.h>
#include "backend/gl/gl_common.h"
#include "backend/gl/glx.h"
#endif
#include "compiler.h"
#include "log.h"
#include "utils.h"
thread_local struct log *tls_logger;
struct log_target;
struct log {
struct log_target *head;
int log_level;
};
struct log_target {
const struct log_ops *ops;
struct log_target *next;
};
struct log_ops {
void (*write)(struct log_target *, const char *, size_t);
void (*writev)(struct log_target *, const struct iovec *, int vcnt);
void (*destroy)(struct log_target *);
/// Additional strings to print around the log_level string
const char *(*colorize_begin)(enum log_level);
const char *(*colorize_end)(enum log_level);
};
/// Fallback writev for targets don't implement it
static attr_unused void
log_default_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) {
size_t total = 0;
for (int i = 0; i < vcnt; i++) {
total += vec[i].iov_len;
}
if (!total) {
// Nothing to write
return;
}
char *buf = ccalloc(total, char);
total = 0;
for (int i = 0; i < vcnt; i++) {
memcpy(buf + total, vec[i].iov_base, vec[i].iov_len);
total += vec[i].iov_len;
}
tgt->ops->write(tgt, buf, total);
free(buf);
}
static attr_const const char *log_level_to_string(enum log_level level) {
switch (level) {
case LOG_LEVEL_TRACE: return "TRACE";
case LOG_LEVEL_DEBUG: return "DEBUG";
case LOG_LEVEL_INFO: return "INFO";
case LOG_LEVEL_WARN: return "WARN";
case LOG_LEVEL_ERROR: return "ERROR";
case LOG_LEVEL_FATAL: return "FATAL ERROR";
default: return "????";
}
}
enum log_level string_to_log_level(const char *str) {
if (strcasecmp(str, "TRACE") == 0)
return LOG_LEVEL_TRACE;
else if (strcasecmp(str, "DEBUG") == 0)
return LOG_LEVEL_DEBUG;
else if (strcasecmp(str, "INFO") == 0)
return LOG_LEVEL_INFO;
else if (strcasecmp(str, "WARN") == 0)
return LOG_LEVEL_WARN;
else if (strcasecmp(str, "ERROR") == 0)
return LOG_LEVEL_ERROR;
return LOG_LEVEL_INVALID;
}
struct log *log_new(void) {
auto ret = cmalloc(struct log);
ret->log_level = LOG_LEVEL_WARN;
ret->head = NULL;
return ret;
}
void log_add_target(struct log *l, struct log_target *tgt) {
assert(tgt->ops->writev);
tgt->next = l->head;
l->head = tgt;
}
/// Remove a previously added log target for a log struct, and destroy it. If the log
/// target was never added, nothing happens.
void log_remove_target(struct log *l, struct log_target *tgt) {
struct log_target *now = l->head, **prev = &l->head;
while (now) {
if (now == tgt) {
*prev = now->next;
tgt->ops->destroy(tgt);
break;
}
prev = &now->next;
now = now->next;
}
}
/// Destroy a log struct and every log target added to it
void log_destroy(struct log *l) {
// free all tgt
struct log_target *head = l->head;
while (head) {
auto next = head->next;
head->ops->destroy(head);
head = next;
}
free(l);
}
void log_set_level(struct log *l, int level) {
assert(level <= LOG_LEVEL_FATAL && level >= 0);
l->log_level = level;
}
enum log_level log_get_level(const struct log *l) {
return l->log_level;
}
attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func,
const char *fmt, ...) {
assert(level <= LOG_LEVEL_FATAL && level >= 0);
if (level < l->log_level)
return;
char *buf = NULL;
va_list args;
va_start(args, fmt);
int blen = vasprintf(&buf, fmt, args);
va_end(args);
if (blen < 0 || !buf) {
free(buf);
return;
}
struct timespec ts;
timespec_get(&ts, TIME_UTC);
struct tm now;
localtime_r(&ts.tv_sec, &now);
char time_buf[100];
strftime(time_buf, sizeof time_buf, "%x %T", &now);
char *time = NULL;
int tlen = asprintf(&time, "%s.%03ld", time_buf, ts.tv_nsec / 1000000);
if (tlen < 0 || !time) {
free(buf);
free(time);
return;
}
const char *log_level_str = log_level_to_string(level);
size_t llen = strlen(log_level_str);
size_t flen = strlen(func);
struct log_target *head = l->head;
while (head) {
const char *p = "", *s = "";
size_t plen = 0, slen = 0;
if (head->ops->colorize_begin) {
// construct target specific prefix
p = head->ops->colorize_begin(level);
plen = strlen(p);
if (head->ops->colorize_end) {
s = head->ops->colorize_end(level);
slen = strlen(s);
}
}
head->ops->writev(
head,
(struct iovec[]){{.iov_base = "[ ", .iov_len = 2},
{.iov_base = time, .iov_len = (size_t)tlen},
{.iov_base = " ", .iov_len = 1},
{.iov_base = (void *)func, .iov_len = flen},
{.iov_base = " ", .iov_len = 1},
{.iov_base = (void *)p, .iov_len = plen},
{.iov_base = (void *)log_level_str, .iov_len = llen},
{.iov_base = (void *)s, .iov_len = slen},
{.iov_base = " ] ", .iov_len = 3},
{.iov_base = buf, .iov_len = (size_t)blen},
{.iov_base = "\n", .iov_len = 1}},
11);
head = head->next;
}
free(time);
free(buf);
}
/// A trivial deinitializer that simply frees the memory
static attr_unused void logger_trivial_destroy(struct log_target *tgt) {
free(tgt);
}
/// A null log target that does nothing
static const struct log_ops null_logger_ops;
static struct log_target null_logger_target = {
.ops = &null_logger_ops,
};
struct log_target *null_logger_new(void) {
return &null_logger_target;
}
static void null_logger_write(struct log_target *tgt attr_unused,
const char *str attr_unused, size_t len attr_unused) {
return;
}
static void null_logger_writev(struct log_target *tgt attr_unused,
const struct iovec *vec attr_unused, int vcnt attr_unused) {
return;
}
static const struct log_ops null_logger_ops = {
.write = null_logger_write,
.writev = null_logger_writev,
};
/// A file based logger that writes to file (or stdout/stderr)
struct file_logger {
struct log_target tgt;
FILE *f;
struct log_ops ops;
};
static void file_logger_write(struct log_target *tgt, const char *str, size_t len) {
auto f = (struct file_logger *)tgt;
fwrite(str, 1, len, f->f);
}
static void file_logger_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) {
auto f = (struct file_logger *)tgt;
fflush(f->f);
writev(fileno(f->f), vec, vcnt);
}
static void file_logger_destroy(struct log_target *tgt) {
auto f = (struct file_logger *)tgt;
fclose(f->f);
free(tgt);
}
#define ANSI(x) "\033[" x "m"
static const char *terminal_colorize_begin(enum log_level level) {
switch (level) {
case LOG_LEVEL_TRACE: return ANSI("30;2");
case LOG_LEVEL_DEBUG: return ANSI("37;2");
case LOG_LEVEL_INFO: return ANSI("92");
case LOG_LEVEL_WARN: return ANSI("33");
case LOG_LEVEL_ERROR: return ANSI("31;1");
case LOG_LEVEL_FATAL: return ANSI("30;103;1");
default: return "";
}
}
static const char *terminal_colorize_end(enum log_level level attr_unused) {
return ANSI("0");
}
#undef PREFIX
static const struct log_ops file_logger_ops = {
.write = file_logger_write,
.writev = file_logger_writev,
.destroy = file_logger_destroy,
};
struct log_target *file_logger_new(const char *filename) {
FILE *f = fopen(filename, "a");
if (!f) {
return NULL;
}
auto ret = cmalloc(struct file_logger);
ret->tgt.ops = &ret->ops;
ret->f = f;
// Always assume a file is not a terminal
ret->ops = file_logger_ops;
return &ret->tgt;
}
struct log_target *stderr_logger_new(void) {
int fd = dup(STDERR_FILENO);
if (fd < 0) {
return NULL;
}
FILE *f = fdopen(fd, "w");
if (!f) {
return NULL;
}
auto ret = cmalloc(struct file_logger);
ret->tgt.ops = &ret->ops;
ret->f = f;
ret->ops = file_logger_ops;
if (isatty(fd)) {
ret->ops.colorize_begin = terminal_colorize_begin;
ret->ops.colorize_end = terminal_colorize_end;
}
return &ret->tgt;
}
#ifdef CONFIG_OPENGL
/// An opengl logger that can be used for logging into opengl debugging tools,
/// such as apitrace
struct gl_string_marker_logger {
struct log_target tgt;
PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker;
};
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') {
len--;
}
g->gl_string_marker((GLsizei)len, str);
}
static const struct log_ops gl_string_marker_logger_ops = {
.write = gl_string_marker_logger_write,
.writev = log_default_writev,
.destroy = logger_trivial_destroy,
};
struct log_target *gl_string_marker_logger_new(void) {
if (!gl_has_extension("GL_GREMEDY_string_marker")) {
return NULL;
}
void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY");
if (!fnptr)
return NULL;
auto ret = cmalloc(struct gl_string_marker_logger);
ret->tgt.ops = &gl_string_marker_logger_ops;
ret->gl_string_marker = fnptr;
return &ret->tgt;
}
#else
struct log_target *gl_string_marker_logger_new(void) {
return NULL;
}
#endif
// vim: set noet sw=8 ts=8:
+96
View File
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <assert.h>
#include <stdio.h>
#include "compiler.h"
enum log_level {
LOG_LEVEL_INVALID = -1,
LOG_LEVEL_TRACE = 0,
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARN,
LOG_LEVEL_ERROR,
LOG_LEVEL_FATAL,
};
#define LOG_UNLIKELY(level, x, ...) \
do { \
if (unlikely(LOG_LEVEL_##level >= log_get_level_tls())) { \
log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \
} \
} while (0)
#define LOG(level, x, ...) \
do { \
if (LOG_LEVEL_##level >= log_get_level_tls()) { \
log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \
} \
} while (0)
#define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__)
#define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__)
#define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__)
#define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__)
#define log_error(x, ...) LOG(ERROR, x, ##__VA_ARGS__)
#define log_fatal(x, ...) LOG(FATAL, x, ##__VA_ARGS__)
#define log_error_errno(x, ...) LOG(ERROR, x ": %s", ##__VA_ARGS__, strerror(errno))
struct log;
struct log_target;
attr_printf(4, 5) void log_printf(struct log *, int level, const char *func,
const char *fmt, ...);
attr_malloc struct log *log_new(void);
/// Destroy a log struct and every log target added to it
attr_nonnull_all void log_destroy(struct log *);
attr_nonnull(1) void log_set_level(struct log *l, int level);
attr_pure enum log_level log_get_level(const struct log *l);
attr_nonnull_all void log_add_target(struct log *, struct log_target *);
attr_pure enum log_level string_to_log_level(const char *);
/// Remove a previously added log target for a log struct, and destroy it. If the log
/// target was never added, nothing happens.
void log_remove_target(struct log *l, struct log_target *tgt);
extern thread_local struct log *tls_logger;
/// Create a thread local logger
static inline void log_init_tls(void) {
tls_logger = log_new();
}
/// Set thread local logger log level
static inline void log_set_level_tls(int level) {
assert(tls_logger);
log_set_level(tls_logger, level);
}
static inline attr_nonnull_all void log_add_target_tls(struct log_target *tgt) {
assert(tls_logger);
log_add_target(tls_logger, tgt);
}
static inline attr_nonnull_all void log_remove_target_tls(struct log_target *tgt) {
assert(tls_logger);
log_remove_target(tls_logger, tgt);
}
static inline attr_pure enum log_level log_get_level_tls(void) {
assert(tls_logger);
return log_get_level(tls_logger);
}
static inline void log_deinit_tls(void) {
assert(tls_logger);
log_destroy(tls_logger);
tls_logger = NULL;
}
attr_malloc struct log_target *stderr_logger_new(void);
attr_malloc struct log_target *file_logger_new(const char *file);
attr_malloc struct log_target *null_logger_new(void);
attr_malloc struct log_target *gl_string_marker_logger_new(void);
// vim: set noet sw=8 ts=8:
+97
View File
@@ -0,0 +1,97 @@
libev = dependency('libev', required: false)
if not libev.found()
libev = cc.find_library('ev')
endif
base_deps = [
cc.find_library('m'),
libev
]
srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c',
'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c',
'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c') ]
picom_inc = include_directories('.')
cflags = []
required_xcb_packages = [
'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite',
'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb'
]
required_packages = [
'x11', 'x11-xcb', 'xcb-renderutil', 'xcb-image', 'xext', 'pixman-1'
]
foreach i : required_packages
base_deps += [dependency(i, required: true)]
endforeach
foreach i : required_xcb_packages
base_deps += [dependency(i, version: '>=1.12.0', required: true)]
endforeach
if not cc.has_header('uthash.h')
error('Dependency uthash not found')
endif
deps = []
if get_option('config_file')
deps += [dependency('libconfig', version: '>=1.4', required: true)]
cflags += ['-DCONFIG_LIBCONFIG']
srcs += [ 'config_libconfig.c' ]
endif
if get_option('regex')
pcre = dependency('libpcre', required: true)
cflags += ['-DCONFIG_REGEX_PCRE']
if pcre.version().version_compare('>=8.20')
cflags += ['-DCONFIG_REGEX_PCRE_JIT']
endif
deps += [pcre]
endif
if get_option('vsync_drm')
cflags += ['-DCONFIG_VSYNC_DRM']
deps += [dependency('libdrm', required: true)]
endif
if get_option('opengl')
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
deps += [dependency('gl', required: true)]
srcs += [ 'opengl.c' ]
endif
if get_option('dbus')
cflags += ['-DCONFIG_DBUS']
deps += [dependency('dbus-1', required: true)]
srcs += [ 'dbus.c' ]
endif
if get_option('xrescheck')
cflags += ['-DDEBUG_XRC']
srcs += [ 'xrescheck.c' ]
endif
if get_option('unittest')
cflags += ['-DUNIT_TEST']
endif
host_system = host_machine.system()
if host_system == 'linux'
cflags += ['-DHAS_INOTIFY']
elif (host_system == 'freebsd' or host_system == 'netbsd' or
host_system == 'dragonfly' or host_system == 'openbsd')
cflags += ['-DHAS_KQUEUE']
endif
subdir('backend')
picom = executable('picom', srcs, c_args: cflags,
dependencies: [ base_deps, deps, test_h_dep ],
install: true, include_directories: picom_inc)
if get_option('unittest')
test('picom unittest', picom, args: [ '--unittest' ])
endif
+75
View File
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2019, Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
/// Macro metaprogramming
#define _APPLY1(a, ...) a(__VA_ARGS__)
#define _APPLY2(a, ...) a(__VA_ARGS__)
#define _APPLY3(a, ...) a(__VA_ARGS__)
#define _APPLY4(a, ...) a(__VA_ARGS__)
#define RIOTA1(x) x
#define RIOTA2(x) RIOTA1(x##1), RIOTA1(x##0)
#define RIOTA4(x) RIOTA2(x##1), RIOTA2(x##0)
#define RIOTA8(x) RIOTA4(x##1), RIOTA4(x##0)
#define RIOTA16(x) RIOTA8(x##1), RIOTA8(x##0)
/// Generate a list containing 31, 30, ..., 0, in binary
#define RIOTA32(x) RIOTA16(x##1), RIOTA16(x##0)
#define CONCAT2(a, b) a##b
#define CONCAT1(a, b) CONCAT2(a, b)
#define CONCAT(a, b) CONCAT1(a, b)
#define _ARGS_HEAD(head, ...) head
#define _ARGS_SKIP4(_1, _2, _3, _4, ...) __VA_ARGS__
#define _ARGS_SKIP8(...) _APPLY1(_ARGS_SKIP4, _ARGS_SKIP4(__VA_ARGS__))
#define _ARGS_SKIP16(...) _APPLY2(_ARGS_SKIP8, _ARGS_SKIP8(__VA_ARGS__))
#define _ARGS_SKIP32(...) _APPLY3(_ARGS_SKIP16, _ARGS_SKIP16(__VA_ARGS__))
/// Return the 33rd argument
#define _ARG33(...) _APPLY4(_ARGS_HEAD, _ARGS_SKIP32(__VA_ARGS__))
/// Return the number of arguments passed in binary, handles at most 31 elements
#define VA_ARGS_LENGTH(...) _ARG33(0, ##__VA_ARGS__, RIOTA32(0))
#define LIST_APPLY_000000(fn, sep, ...)
#define LIST_APPLY_000001(fn, sep, x, ...) fn(x)
#define LIST_APPLY_000010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000001(fn, sep, __VA_ARGS__)
#define LIST_APPLY_000011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000010(fn, sep, __VA_ARGS__)
#define LIST_APPLY_000100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000011(fn, sep, __VA_ARGS__)
#define LIST_APPLY_000101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000100(fn, sep, __VA_ARGS__)
#define LIST_APPLY_000110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000101(fn, sep, __VA_ARGS__)
#define LIST_APPLY_000111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000110(fn, sep, __VA_ARGS__)
#define LIST_APPLY_001000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000111(fn, sep, __VA_ARGS__)
#define LIST_APPLY_001001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001000(fn, sep, __VA_ARGS__)
#define LIST_APPLY_001010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001001(fn, sep, __VA_ARGS__)
#define LIST_APPLY_001011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001010(fn, sep, __VA_ARGS__)
#define LIST_APPLY_001100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001011(fn, sep, __VA_ARGS__)
#define LIST_APPLY_001101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001100(fn, sep, __VA_ARGS__)
#define LIST_APPLY_001110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001101(fn, sep, __VA_ARGS__)
#define LIST_APPLY_001111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001110(fn, sep, __VA_ARGS__)
#define LIST_APPLY_010000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001111(fn, sep, __VA_ARGS__)
#define LIST_APPLY_010001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010000(fn, sep, __VA_ARGS__)
#define LIST_APPLY_010010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010001(fn, sep, __VA_ARGS__)
#define LIST_APPLY_010011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010010(fn, sep, __VA_ARGS__)
#define LIST_APPLY_010100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010011(fn, sep, __VA_ARGS__)
#define LIST_APPLY_010101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010100(fn, sep, __VA_ARGS__)
#define LIST_APPLY_010110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010101(fn, sep, __VA_ARGS__)
#define LIST_APPLY_010111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010110(fn, sep, __VA_ARGS__)
#define LIST_APPLY_011000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010111(fn, sep, __VA_ARGS__)
#define LIST_APPLY_011001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011000(fn, sep, __VA_ARGS__)
#define LIST_APPLY_011010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011001(fn, sep, __VA_ARGS__)
#define LIST_APPLY_011011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011010(fn, sep, __VA_ARGS__)
#define LIST_APPLY_011100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011011(fn, sep, __VA_ARGS__)
#define LIST_APPLY_011101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011100(fn, sep, __VA_ARGS__)
#define LIST_APPLY_011110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011101(fn, sep, __VA_ARGS__)
#define LIST_APPLY_011111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011110(fn, sep, __VA_ARGS__)
#define LIST_APPLY_(N, fn, sep, ...) CONCAT(LIST_APPLY_, N)(fn, sep, __VA_ARGS__)
#define LIST_APPLY(fn, sep, ...) \
LIST_APPLY_(VA_ARGS_LENGTH(__VA_ARGS__), fn, sep, __VA_ARGS__)
#define SEP_COMMA() ,
#define SEP_COLON() ;
#define SEP_NONE()
File diff suppressed because it is too large. Load diff
+246
View File
@@ -0,0 +1,246 @@
// SPDX-License-Identifier: MIT
/*
* Compton - a compositor for X11
*
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2011-2013, Christopher Jeffrey
* See LICENSE-mit for more information.
*
*/
#pragma once
#include "common.h"
#include "compiler.h"
#include "log.h"
#include "region.h"
#include "render.h"
#include "win.h"
#include <GL/gl.h>
#include <GL/glx.h>
#include <ctype.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/render.h>
#include <xcb/xcb.h>
typedef struct {
/// Fragment shader for blur.
GLuint frag_shader;
/// GLSL program for blur.
GLuint prog;
/// Location of uniform "offset_x" in blur GLSL program.
GLint unifm_offset_x;
/// Location of uniform "offset_y" in blur GLSL program.
GLint unifm_offset_y;
/// Location of uniform "factor_center" in blur GLSL program.
GLint unifm_factor_center;
} glx_blur_pass_t;
typedef struct {
/// Fragment shader for rounded corners.
GLuint frag_shader;
/// GLSL program for rounded corners.
GLuint prog;
/// Location of uniform "radius" in rounded-corners GLSL program.
GLint unifm_radius;
/// Location of uniform "texcoord" in rounded-corners GLSL program.
GLint unifm_texcoord;
/// Location of uniform "texsize" in rounded-corners GLSL program.
GLint unifm_texsize;
/// Location of uniform "borderw" in rounded-corners GLSL program.
GLint unifm_borderw;
/// Location of uniform "borderc" in rounded-corners GLSL program.
GLint unifm_borderc;
/// Location of uniform "resolution" in rounded-corners GLSL program.
GLint unifm_resolution;
/// Location of uniform "texture_scr" in rounded-corners GLSL program.
GLint unifm_tex_scr;
} glx_round_pass_t;
/// Structure containing GLX-dependent data for a session.
typedef struct glx_session {
// === OpenGL related ===
/// GLX context.
GLXContext context;
/// Whether we have GL_ARB_texture_non_power_of_two.
bool has_texture_non_power_of_two;
/// Current GLX Z value.
int z;
glx_blur_pass_t *blur_passes;
glx_round_pass_t *round_passes;
} glx_session_t;
/// @brief Wrapper of a binded GLX texture.
typedef struct _glx_texture {
GLuint texture;
GLXPixmap glpixmap;
xcb_pixmap_t pixmap;
GLenum target;
int width;
int height;
bool y_inverted;
} glx_texture_t;
#define CGLX_SESSION_INIT \
{ .context = NULL }
bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z,
GLfloat factor, const region_t *reg_tgt);
bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy,
int width, int height, int z, double opacity, bool argb, bool neg,
const region_t *reg_tgt, const glx_prog_main_t *pprogram);
bool glx_init(session_t *ps, bool need_render);
void glx_destroy(session_t *ps);
void glx_on_root_change(session_t *ps);
bool glx_init_blur(session_t *ps);
bool glx_init_rounded_corners(session_t *ps);
#ifdef CONFIG_OPENGL
bool glx_load_prog_main(const char *vshader_str, const char *fshader_str,
glx_prog_main_t *pprogram);
#endif
bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width,
int height, bool repeat, const struct glx_fbconfig_info *);
void glx_release_pixmap(session_t *ps, glx_texture_t *ptex);
bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int width, int height);
void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2);
/**
* Check if a texture is binded, or is binded to the given pixmap.
*/
static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap);
}
void glx_set_clip(session_t *ps, const region_t *reg);
bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc);
bool glx_round_corners_dst(session_t *ps, struct managed_win *w,
const glx_texture_t *ptex, int dx, int dy, int width,
int height, float z, float cr, const region_t *reg_tgt);
GLuint glx_create_shader(GLenum shader_type, const char *shader_str);
GLuint glx_create_program(const GLuint *const shaders, int nshaders);
GLuint glx_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str);
unsigned char *glx_take_screenshot(session_t *ps, int *out_length);
/**
* Check if there's a GLX context.
*/
static inline bool glx_has_context(session_t *ps) {
return ps->psglx && ps->psglx->context;
}
/**
* Ensure we have a GLX context.
*/
static inline bool ensure_glx_context(session_t *ps) {
// Create GLX context
if (!glx_has_context(ps))
glx_init(ps, false);
return ps->psglx->context;
}
/**
* Free a GLX texture.
*/
static inline void free_texture_r(session_t *ps attr_unused, GLuint *ptexture) {
if (*ptexture) {
assert(glx_has_context(ps));
glDeleteTextures(1, ptexture);
*ptexture = 0;
}
}
/**
* Free a GLX Framebuffer object.
*/
static inline void free_glx_fbo(GLuint *pfbo) {
if (*pfbo) {
glDeleteFramebuffers(1, pfbo);
*pfbo = 0;
}
assert(!*pfbo);
}
/**
* Free data in glx_blur_cache_t on resize.
*/
static inline void free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) {
free_texture_r(ps, &pbc->textures[0]);
free_texture_r(ps, &pbc->textures[1]);
pbc->width = 0;
pbc->height = 0;
}
/**
* Free a glx_blur_cache_t
*/
static inline void free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) {
free_glx_fbo(&pbc->fbo);
free_glx_bc_resize(ps, pbc);
}
/**
* Free a glx_texture_t.
*/
static inline void free_texture(session_t *ps, glx_texture_t **pptex) {
glx_texture_t *ptex = *pptex;
// Quit if there's nothing
if (!ptex) {
return;
}
glx_release_pixmap(ps, ptex);
free_texture_r(ps, &ptex->texture);
// Free structure itself
free(ptex);
*pptex = NULL;
}
/**
* Free GLX part of paint_t.
*/
static inline void free_paint_glx(session_t *ps, paint_t *ppaint) {
free_texture(ps, &ppaint->ptex);
#ifdef CONFIG_OPENGL
free(ppaint->fbcfg);
#endif
ppaint->fbcfg = NULL;
}
/**
* Free GLX part of win.
*/
static inline void free_win_res_glx(session_t *ps, struct managed_win *w) {
free_paint_glx(ps, &w->paint);
free_paint_glx(ps, &w->shadow_paint);
#ifdef CONFIG_OPENGL
free_glx_bc(ps, &w->glx_blur_cache);
free_texture(ps, &w->glx_texture_bg);
#endif
}
File diff suppressed because it is too large. Load diff
+37
View File
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
/// Parse command line options
#include <stdbool.h>
#include <xcb/render.h> // for xcb_render_fixed_t
#include "compiler.h"
#include "config.h"
#include "types.h"
#include "win.h" // for wintype_t
typedef struct session session_t;
/// 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);
/**
* Process arguments and configuration files.
*
* Parameters:
* shadow_enable = Carry overs from parse_config
* fading_enable
* conv_kern_hasneg
* winopt_mask
* Returns:
* Whether configuration are processed successfully.
*/
bool must_use get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
bool fading_enable, bool conv_kern_hasneg,
win_option_mask_t *winopt_mask);
// vim: set noet sw=8 ts=8:
File diff suppressed because it is too large. Load diff
+115
View File
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
// Copyright (c)
// Throw everything in here.
// !!! DON'T !!!
// === Includes ===
#include <locale.h>
#include <stdbool.h>
#include <stdlib.h>
#include <xcb/xproto.h>
#include <X11/Xutil.h>
#include "backend/backend.h"
#include "c2.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "log.h" // XXX clean up
#include "region.h"
#include "render.h"
#include "types.h"
#include "utils.h"
#include "win.h"
#include "x.h"
enum root_flags {
ROOT_FLAGS_SCREEN_CHANGE = 1, // Received RandR screen change notify, we
// use this to track refresh rate changes
ROOT_FLAGS_CONFIGURED = 2 // Received configure notify on the root window
};
// == Functions ==
// TODO(yshui) move static inline functions that are only used in picom.c, into picom.c
void add_damage(session_t *ps, const region_t *damage);
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 root_damaged(session_t *ps);
void cxinerama_upd_scrs(session_t *ps);
void queue_redraw(session_t *ps);
void discard_ignore(session_t *ps, unsigned long sequence);
void set_root_flags(session_t *ps, uint64_t flags);
void quit(session_t *ps);
xcb_window_t session_get_target_window(session_t *);
uint8_t session_redirection_mode(session_t *ps);
/**
* Set a <code>switch_t</code> array of all unset wintypes to true.
*/
static inline void wintype_arr_enable_unset(switch_t arr[]) {
wintype_t i;
for (i = 0; i < NUM_WINTYPES; ++i)
if (UNSET == arr[i])
arr[i] = ON;
}
/**
* Check if a window ID exists in an array of window IDs.
*
* @param arr the array of window IDs
* @param count amount of elements in the array
* @param wid window ID to search for
*/
static inline bool array_wid_exists(const xcb_window_t *arr, int count, xcb_window_t wid) {
while (count--) {
if (arr[count] == wid) {
return true;
}
}
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) {
}
static inline void
free_win_res_glx(session_t *ps attr_unused, struct managed_win *w attr_unused) {
}
#endif
/**
* Dump an drawable's info.
*/
static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) {
auto r = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, drawable), NULL);
if (!r) {
log_trace("Drawable %#010x: Failed", drawable);
return;
}
log_trace("Drawable %#010x: x = %u, y = %u, wid = %u, hei = %d, b = %u, d = %u",
drawable, r->x, r->y, r->width, r->height, r->border_width, r->depth);
free(r);
}
+214
View File
@@ -0,0 +1,214 @@
// modulemap
module compiler {
header "compiler.h"
}
module string_utils {
header "string_utils.h"
}
module dbus {
header "dbus.h"
}
module kernel {
header "kernel.h"
}
module utils {
// Has macros expands to calloc/malloc
header "utils.h"
export libc.stdlib
}
module region {
header "region.h"
}
module picom {
header "picom.h"
}
module types {
header "types.h"
}
module c2 {
header "c2.h"
}
module render {
header "render.h"
}
module options {
header "options.h"
}
module opengl {
header "opengl.h"
}
module diagnostic {
header "diagnostic.h"
}
module win_defs {
header "win_defs.h"
}
module win {
header "win.h"
export win_defs
}
module log {
header "log.h"
export compiler
}
module x {
header "x.h"
}
module vsync {
header "vsync.h"
}
module common {
header "common.h"
}
module config {
header "config.h"
}
module xrescheck {
header "xrescheck.h"
}
module cache {
header "cache.h"
}
module backend {
module gl {
module gl_common {
header "backend/gl/gl_common.h"
}
module glx {
header "backend/gl/glx.h"
export GL.glx
}
}
module backend {
header "backend/backend.h"
}
module backend_common {
header "backend/backend_common.h"
}
}
module xcb [system] {
module xcb {
header "/usr/include/xcb/xcb.h"
export *
}
module randr {
header "/usr/include/xcb/randr.h"
export *
}
module render {
header "/usr/include/xcb/render.h"
export *
}
module sync {
header "/usr/include/xcb/sync.h"
export *
}
module composite {
header "/usr/include/xcb/composite.h"
export *
}
module xfixes {
header "/usr/include/xcb/xfixes.h"
export *
}
module damage {
header "/usr/include/xcb/damage.h"
export *
}
module xproto {
header "/usr/include/xcb/xproto.h"
export *
}
module present {
header "/usr/include/xcb/present.h"
}
module util {
module render {
header "/usr/include/xcb/xcb_renderutil.h"
export *
}
}
}
module X11 [system] {
module Xlib {
header "/usr/include/X11/Xlib.h"
export *
}
module Xutil {
header "/usr/include/X11/Xutil.h"
export *
}
}
module GL [system] {
module glx {
header "/usr/include/GL/glx.h"
export *
}
module gl {
header "/usr/include/GL/gl.h"
export *
}
}
module libc [system] {
export *
module assert {
export *
textual header "/usr/include/assert.h"
}
module string {
export *
header "/usr/include/string.h"
}
module ctype {
export *
header "/usr/include/ctype.h"
}
module errno {
export *
header "/usr/include/errno.h"
}
module fenv {
export *
header "/usr/include/fenv.h"
}
module inttypes {
export *
header "/usr/include/inttypes.h"
}
module math {
export *
header "/usr/include/math.h"
}
module setjmp {
export *
header "/usr/include/setjmp.h"
}
module stdio {
export *
header "/usr/include/stdio.h"
}
module stdlib [system] {
export *
header "/usr/include/stdlib.h"
}
}
// glib specific header. In it's own module because it
// doesn't exist on some systems with unpatched glib 2.26+
module "xlocale.h" [system] {
export *
header "/usr/include/xlocale.h"
}
// System header that we have difficult with merging.
module "sys_types.h" [system] {
export *
header "/usr/include/sys/types.h"
}
module "signal.h" [system] {
export *
header "/usr/include/signal.h"
}
+100
View File
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <pixman.h>
#include <stdio.h>
#include <stdlib.h>
#include <xcb/xcb.h>
#include "log.h"
#include "utils.h"
typedef struct pixman_region32 pixman_region32_t;
typedef struct pixman_box32 pixman_box32_t;
typedef pixman_region32_t region_t;
typedef pixman_box32_t rect_t;
RC_TYPE(region_t, rc_region, pixman_region32_init, pixman_region32_fini, static inline)
static inline void dump_region(const region_t *x) {
if (log_get_level_tls() < LOG_LEVEL_TRACE) {
return;
}
int nrects;
const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects);
log_trace("nrects: %d", nrects);
for (int i = 0; i < nrects; i++)
log_trace("(%d, %d) - (%d, %d)", rects[i].x1, rects[i].y1, rects[i].x2,
rects[i].y2);
}
/// Convert one xcb rectangle to our rectangle type
static inline rect_t from_x_rect(const xcb_rectangle_t *rect) {
return (rect_t){
.x1 = rect->x,
.y1 = rect->y,
.x2 = rect->x + rect->width,
.y2 = rect->y + rect->height,
};
}
/// Convert an array of xcb rectangles to our rectangle type
/// Returning an array that needs to be freed
static inline rect_t *from_x_rects(int nrects, const xcb_rectangle_t *rects) {
rect_t *ret = ccalloc(nrects, rect_t);
for (int i = 0; i < nrects; i++) {
ret[i] = from_x_rect(rects + i);
}
return ret;
}
/**
* Resize a region.
*/
static inline void _resize_region(const region_t *region, region_t *output, int dx,
int dy) {
if (!region || !output) {
return;
}
if (!dx && !dy) {
if (region != output) {
pixman_region32_copy(output, (region_t *)region);
}
return;
}
// Loop through all rectangles
int nrects;
int nnewrects = 0;
const rect_t *rects = pixman_region32_rectangles((region_t *)region, &nrects);
auto newrects = ccalloc(nrects, rect_t);
for (int i = 0; i < nrects; i++) {
int x1 = rects[i].x1 - dx;
int y1 = rects[i].y1 - dy;
int x2 = rects[i].x2 + dx;
int y2 = rects[i].y2 + dy;
int wid = x2 - x1;
int hei = y2 - y1;
if (wid <= 0 || hei <= 0) {
continue;
}
newrects[nnewrects] =
(rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2};
++nnewrects;
}
pixman_region32_fini(output);
pixman_region32_init_rects(output, newrects, nnewrects);
free(newrects);
}
static inline region_t resize_region(const region_t *region, int dx, int dy) {
region_t ret;
pixman_region32_init(&ret);
_resize_region(region, &ret, dx, dy);
return ret;
}
static inline void resize_region_in_place(region_t *region, int dx, int dy) {
return _resize_region(region, region, dx, dy);
}
File diff suppressed because it is too large. Load diff
+50
View File
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <stdbool.h>
#include <xcb/render.h>
#include <xcb/xcb.h>
#ifdef CONFIG_OPENGL
#include "backend/gl/glx.h"
#endif
#include "region.h"
typedef struct _glx_texture glx_texture_t;
typedef struct glx_prog_main glx_prog_main_t;
typedef struct session session_t;
struct managed_win;
typedef struct paint {
xcb_pixmap_t pixmap;
xcb_render_picture_t pict;
glx_texture_t *ptex;
#ifdef CONFIG_OPENGL
struct glx_fbconfig_info *fbcfg;
#endif
} paint_t;
typedef struct clip {
xcb_render_picture_t pict;
int x;
int y;
} clip_t;
void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, int fullw,
int fullh, double opacity, bool argb, bool neg, int cr,
xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint,
const glx_prog_main_t *pprogram, clip_t *clip);
void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint);
void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage);
void free_picture(xcb_connection_t *c, xcb_render_picture_t *p);
void free_paint(session_t *ps, paint_t *ppaint);
void free_root_tile(session_t *ps);
bool init_render(session_t *ps);
void deinit_render(session_t *ps);
int maximum_buffer_age(session_t *);
+129
View File
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <string.h>
#include <test.h>
#include "compiler.h"
#include "string_utils.h"
#include "utils.h"
#pragma GCC diagnostic push
// gcc warns about legitimate strncpy in mstrjoin and mstrextend
// strncpy(str, src1, len1) intentional truncates the null byte from src1.
// strncpy(str+len1, src2, len2) uses bound depends on the source argument,
// but str is allocated with len1+len2+1, so this strncpy can't overflow
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wstringop-truncation"
#pragma GCC diagnostic ignored "-Wstringop-overflow"
/**
* Allocate the space and join two strings.
*/
char *mstrjoin(const char *src1, const char *src2) {
auto len1 = strlen(src1);
auto len2 = strlen(src2);
auto len = len1 + len2 + 1;
auto str = ccalloc(len, char);
strncpy(str, src1, len1);
strncpy(str + len1, src2, len2);
str[len - 1] = '\0';
return str;
}
TEST_CASE(mstrjoin) {
char *str = mstrjoin("asdf", "qwer");
TEST_STREQUAL(str, "asdfqwer");
free(str);
str = mstrjoin("", "qwer");
TEST_STREQUAL(str, "qwer");
free(str);
str = mstrjoin("asdf", "");
TEST_STREQUAL(str, "asdf");
free(str);
}
/**
* Concatenate a string on heap with another string.
*/
void mstrextend(char **psrc1, const char *src2) {
if (!*psrc1) {
*psrc1 = strdup(src2);
return;
}
auto len1 = strlen(*psrc1);
auto len2 = strlen(src2);
auto len = len1 + len2 + 1;
*psrc1 = crealloc(*psrc1, len);
strncpy(*psrc1 + len1, src2, len2);
(*psrc1)[len - 1] = '\0';
}
TEST_CASE(mstrextend) {
char *str1 = NULL;
mstrextend(&str1, "asdf");
TEST_STREQUAL(str1, "asdf");
mstrextend(&str1, "asd");
TEST_STREQUAL(str1, "asdfasd");
mstrextend(&str1, "");
TEST_STREQUAL(str1, "asdfasd");
free(str1);
}
#pragma GCC diagnostic pop
/// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*)
double strtod_simple(const char *src, const char **end) {
double neg = 1;
if (*src == '-') {
neg = -1;
src++;
} else if (*src == '+') {
src++;
}
double ret = 0;
while (*src >= '0' && *src <= '9') {
ret = ret * 10 + (*src - '0');
src++;
}
if (*src == '.') {
double frac = 0, mult = 0.1;
src++;
while (*src >= '0' && *src <= '9') {
frac += mult * (*src - '0');
mult *= 0.1;
src++;
}
ret += frac;
}
*end = src;
return ret * neg;
}
TEST_CASE(strtod_simple) {
const char *end;
double result = strtod_simple("1.0", &end);
TEST_EQUAL(result, 1);
TEST_EQUAL(*end, '\0');
result = strtod_simple("-1.0", &end);
TEST_EQUAL(result, -1);
TEST_EQUAL(*end, '\0');
result = strtod_simple("+.5", &end);
TEST_EQUAL(result, 0.5);
TEST_EQUAL(*end, '\0');
}
+54
View File
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <ctype.h>
#include <stddef.h>
#include "compiler.h"
#define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1))
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);
/// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*)
double strtod_simple(const char *, const char **);
static inline int uitostr(unsigned int n, char *buf) {
int ret = 0;
unsigned int tmp = n;
while (tmp > 0) {
tmp /= 10;
ret++;
}
if (ret == 0)
ret = 1;
int pos = ret;
while (pos--) {
buf[pos] = (char)(n % 10 + '0');
n /= 10;
}
return ret;
}
static inline const char *skip_space_const(const char *src) {
if (!src)
return NULL;
while (*src && isspace((unsigned char)*src))
src++;
return src;
}
static inline char *skip_space_mut(char *src) {
if (!src)
return NULL;
while (*src && isspace((unsigned char)*src))
src++;
return src;
}
#define skip_space(x) \
_Generic((x), char * : skip_space_mut, const char * : skip_space_const)(x)
+32
View File
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
/// Some common types
#include <stdint.h>
/// Enumeration type to represent switches.
typedef enum {
OFF = 0, // false
ON, // true
UNSET
} switch_t;
/// A structure representing margins around a rectangle.
typedef struct {
int top;
int left;
int bottom;
int right;
} margin_t;
struct color {
double red, green, blue, alpha;
};
typedef uint32_t opacity_t;
#define MARGIN_INIT \
{ 0, 0, 0, 0 }
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <uthash.h>
#define HASH_ITER2(head, el) \
for (__typeof__(head) el = (head), __tmp = el != NULL ? el->hh.next : NULL; \
el != NULL; el = __tmp, __tmp = el != NULL ? el->hh.next : NULL)
+51
View File
@@ -0,0 +1,51 @@
#include <stdio.h>
#include <string.h>
#include <sys/uio.h>
#include "compiler.h"
#include "string_utils.h"
#include "utils.h"
/// Report allocation failure without allocating memory
void report_allocation_failure(const char *func, const char *file, unsigned int line) {
// Since memory allocation failed, we try to print this error message without any
// memory allocation. Since logging framework allocates memory (and might even
// have not been initialized yet), so we can't use it.
char buf[11];
int llen = uitostr(line, buf);
const char msg1[] = " has failed to allocate memory, ";
const char msg2[] = ". Aborting...\n";
const struct iovec v[] = {
{.iov_base = (void *)func, .iov_len = strlen(func)},
{.iov_base = "()", .iov_len = 2},
{.iov_base = (void *)msg1, .iov_len = sizeof(msg1) - 1},
{.iov_base = "at ", .iov_len = 3},
{.iov_base = (void *)file, .iov_len = strlen(file)},
{.iov_base = ":", .iov_len = 1},
{.iov_base = buf, .iov_len = (size_t)llen},
{.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1},
};
writev(STDERR_FILENO, v, ARR_SIZE(v));
abort();
unreachable;
}
///
/// 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)
{
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++;
return n;
}
// vim: set noet sw=8 ts=8 :
+310
View File
@@ -0,0 +1,310 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <test.h>
#include <time.h>
#include "compiler.h"
#include "types.h"
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#ifdef __FAST_MATH__
#warning Use of -ffast-math can cause rendering error or artifacts, \
therefore it is not recommended.
#endif
#ifdef __clang__
__attribute__((optnone))
#else
__attribute__((optimize("-fno-fast-math")))
#endif
static inline bool
safe_isnan(double a) {
return __builtin_isnan(a);
}
/// Same as assert(false), but make sure we abort _even in release builds_.
/// Silence compiler warning caused by release builds making some code paths reachable.
#define BUG() \
do { \
assert(false); \
abort(); \
} while (0)
#define CHECK_EXPR(...) ((void)0)
/// Same as assert, but evaluates the expression even in release builds
#define CHECK(expr) \
do { \
auto _ = (expr); \
/* make sure the original expression appears in the assertion message */ \
assert((CHECK_EXPR(expr), _)); \
(void)_; \
} while (0)
/// Asserts that var is within [lower, upper]. Silence compiler warning about expressions
/// being always true or false.
#define ASSERT_IN_RANGE(var, lower, upper) \
do { \
auto __tmp attr_unused = (var); \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wtype-limits\""); \
assert(__tmp >= lower); \
assert(__tmp <= upper); \
_Pragma("GCC diagnostic pop"); \
} while (0)
/// Asserts that var >= lower. Silence compiler warning about expressions
/// being always true or false.
#define ASSERT_GEQ(var, lower) \
do { \
auto __tmp attr_unused = (var); \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wtype-limits\""); \
assert(__tmp >= lower); \
_Pragma("GCC diagnostic pop"); \
} while (0)
// Some macros for checked cast
// Note these macros are not complete, as in, they won't work for every integer types. But
// they are good enough for our use cases.
#define to_int_checked(val) \
({ \
int64_t tmp = (val); \
ASSERT_IN_RANGE(tmp, INT_MIN, INT_MAX); \
(int)tmp; \
})
#define to_char_checked(val) \
({ \
int64_t tmp = (val); \
ASSERT_IN_RANGE(tmp, CHAR_MIN, CHAR_MAX); \
(char)tmp; \
})
#define to_u16_checked(val) \
({ \
auto tmp = (val); \
ASSERT_IN_RANGE(tmp, 0, UINT16_MAX); \
(uint16_t) tmp; \
})
#define to_i16_checked(val) \
({ \
int64_t tmp = (val); \
ASSERT_IN_RANGE(tmp, INT16_MIN, INT16_MAX); \
(int16_t) tmp; \
})
#define to_u32_checked(val) \
({ \
auto tmp = (val); \
int64_t max attr_unused = UINT32_MAX; /* silence clang tautological \
comparison warning*/ \
ASSERT_IN_RANGE(tmp, 0, max); \
(uint32_t) tmp; \
})
/**
* Normalize an int value to a specific range.
*
* @param i int value to normalize
* @param min minimal value
* @param max maximum value
* @return normalized value
*/
static inline int attr_const normalize_i_range(int i, int min, int max) {
if (i > max)
return max;
if (i < min)
return min;
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)
/**
* Normalize a double value to a specific range.
*
* @param d double value to normalize
* @param min minimal value
* @param max maximum value
* @return normalized value
*/
static inline double attr_const normalize_d_range(double d, double min, double max) {
if (d > max)
return max;
if (d < min)
return min;
return d;
}
/**
* Normalize a double value to 0.\ 0 - 1.\ 0.
*
* @param d double value to normalize
* @return normalized value
*/
static inline double attr_const normalize_d(double d) {
return normalize_d_range(d, 0.0, 1.0);
}
/**
* Convert a hex RGB string to RGB
*/
static inline struct color hex_to_rgb(const char *hex) {
struct color rgb;
// Ignore the # in front of the string
const char *sane_hex = hex + 1;
int hex_color = (int)strtol(sane_hex, NULL, 16);
rgb.red = (float)(hex_color >> 16) / 256;
rgb.green = (float)((hex_color & 0x00ff00) >> 8) / 256;
rgb.blue = (float)(hex_color & 0x0000ff) / 256;
return rgb;
}
attr_noret void
report_allocation_failure(const char *func, const char *file, unsigned int line);
/**
* @brief Quit if the passed-in pointer is empty.
*/
static inline void *
allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr) {
if (unlikely(!ptr)) {
report_allocation_failure(func_name, file, line);
}
return ptr;
}
/// @brief Wrapper of allocchk_().
#define allocchk(ptr) allocchk_(__func__, __FILE__, __LINE__, ptr)
/// @brief Wrapper of malloc().
#define cmalloc(type) ((type *)allocchk(malloc(sizeof(type))))
/// @brief Wrapper of malloc() that takes a size
#define cvalloc(size) allocchk(malloc(size))
/// @brief Wrapper of calloc().
#define ccalloc(nmemb, type) \
({ \
auto tmp = (nmemb); \
ASSERT_GEQ(tmp, 0); \
((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \
})
/// @brief Wrapper of ealloc().
#define crealloc(ptr, nmemb) \
({ \
auto tmp = (nmemb); \
ASSERT_GEQ(tmp, 0); \
((__typeof__(ptr))allocchk(realloc((ptr), (size_t)tmp * sizeof(*(ptr))))); \
})
/// RC_TYPE generates a reference counted type from `type`
///
/// parameters:
/// name = the generated type will be called `name`_t.
/// ctor = the constructor of `type`, will be called when
/// a value of `type` is created. should take one
/// argument of `type *`.
/// dtor = the destructor. will be called when all reference
/// is gone. has same signature as ctor
/// Q = function qualifier. this is the qualifier that
/// will be put before generated functions
//
/// functions generated:
/// `name`_new: create a new reference counted object of `type`
/// `name`_ref: increment the reference counter, return a
/// reference to the object
/// `name`_unref: decrement the reference counter. take a `type **`
/// because it needs to nullify the reference.
#define RC_TYPE(type, name, ctor, dtor, Q) \
typedef struct { \
type inner; \
int ref_count; \
} name##_internal_t; \
typedef type name##_t; \
Q type *name##_new(void) { \
name##_internal_t *ret = cmalloc(name##_internal_t); \
ctor((type *)ret); \
ret->ref_count = 1; \
return (type *)ret; \
} \
Q type *name##_ref(type *a) { \
__auto_type b = (name##_internal_t *)a; \
b->ref_count++; \
return a; \
} \
Q void name##_unref(type **a) { \
__auto_type b = (name##_internal_t *)*a; \
if (!b) \
return; \
b->ref_count--; \
if (!b->ref_count) { \
dtor((type *)b); \
free(b); \
} \
*a = NULL; \
}
/// Generate prototypes for functions generated by RC_TYPE
#define RC_TYPE_PROTO(type, name) \
typedef type name##_t; \
type *name##_new(void); \
void name##_ref(type *a); \
void name##_unref(type **a);
///
/// 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);
// 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 :
+204
View File
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
/// Function pointers to init VSync modes.
#include "common.h"
#include "log.h"
#ifdef CONFIG_OPENGL
#include "backend/gl/glx.h"
#include "opengl.h"
#endif
#ifdef CONFIG_VSYNC_DRM
#include <drm.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#endif
#include "config.h"
#include "vsync.h"
#ifdef CONFIG_VSYNC_DRM
/**
* Wait for next VSync, DRM method.
*
* Stolen from:
* https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp
*/
static int vsync_drm_wait(session_t *ps) {
int ret = -1;
drm_wait_vblank_t vbl;
vbl.request.type = _DRM_VBLANK_RELATIVE, vbl.request.sequence = 1;
do {
ret = ioctl(ps->drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl);
vbl.request.type &= ~(uint)_DRM_VBLANK_RELATIVE;
} while (ret && errno == EINTR);
if (ret)
log_error("VBlank ioctl did not work, unimplemented in this drmver?");
return ret;
}
/**
* Initialize DRM VSync.
*
* @return true for success, false otherwise
*/
static bool vsync_drm_init(session_t *ps) {
// Should we always open card0?
if (ps->drm_fd < 0 && (ps->drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) {
log_error("Failed to open device.");
return false;
}
if (vsync_drm_wait(ps))
return false;
return true;
}
#endif
#ifdef CONFIG_OPENGL
/**
* Initialize OpenGL VSync.
*
* Stolen from:
* http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e
* Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html
*
* @return true for success, false otherwise
*/
static bool vsync_opengl_init(session_t *ps) {
if (!ensure_glx_context(ps))
return false;
return glxext.has_GLX_SGI_video_sync;
}
static bool vsync_opengl_oml_init(session_t *ps) {
if (!ensure_glx_context(ps))
return false;
return glxext.has_GLX_OML_sync_control;
}
static inline bool vsync_opengl_swc_swap_interval(session_t *ps, int interval) {
if (glxext.has_GLX_MESA_swap_control)
return glXSwapIntervalMESA((uint)interval) == 0;
else if (glxext.has_GLX_SGI_swap_control)
return glXSwapIntervalSGI(interval) == 0;
else if (glxext.has_GLX_EXT_swap_control) {
GLXDrawable d = glXGetCurrentDrawable();
if (d == None) {
// We don't have a context??
return false;
}
glXSwapIntervalEXT(ps->dpy, glXGetCurrentDrawable(), interval);
return true;
}
return false;
}
static bool vsync_opengl_swc_init(session_t *ps) {
if (!bkend_use_glx(ps)) {
log_error("OpenGL swap control requires the GLX backend.");
return false;
}
if (!vsync_opengl_swc_swap_interval(ps, 1)) {
log_error("Failed to load a swap control extension.");
return false;
}
return true;
}
/**
* Wait for next VSync, OpenGL method.
*/
static int vsync_opengl_wait(session_t *ps attr_unused) {
unsigned vblank_count = 0;
glXGetVideoSyncSGI(&vblank_count);
glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count);
return 0;
}
/**
* Wait for next VSync, OpenGL OML method.
*
* https://mail.gnome.org/archives/clutter-list/2012-November/msg00031.html
*/
static int vsync_opengl_oml_wait(session_t *ps) {
int64_t ust = 0, msc = 0, sbc = 0;
glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc);
glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc);
return 0;
}
#endif
/**
* Initialize current VSync method.
*/
bool vsync_init(session_t *ps) {
#ifdef CONFIG_OPENGL
if (bkend_use_glx(ps)) {
// Mesa turns on swap control by default, undo that
vsync_opengl_swc_swap_interval(ps, 0);
}
#endif
#ifdef CONFIG_VSYNC_DRM
log_warn("The DRM vsync method is deprecated, please don't enable it.");
#endif
if (!ps->o.vsync) {
return true;
}
#ifdef CONFIG_OPENGL
if (bkend_use_glx(ps)) {
if (!vsync_opengl_swc_init(ps)) {
return false;
}
ps->vsync_wait = NULL; // glXSwapBuffers will automatically wait
// for vsync, we don't need to do anything.
return true;
}
#endif
// Oh no, we are not using glx backend.
// Throwing things at wall.
#ifdef CONFIG_OPENGL
if (vsync_opengl_oml_init(ps)) {
log_info("Using the opengl-oml vsync method");
ps->vsync_wait = vsync_opengl_oml_wait;
return true;
}
if (vsync_opengl_init(ps)) {
log_info("Using the opengl vsync method");
ps->vsync_wait = vsync_opengl_wait;
return true;
}
#endif
#ifdef CONFIG_VSYNC_DRM
if (vsync_drm_init(ps)) {
log_info("Using the drm vsync method");
ps->vsync_wait = vsync_drm_wait;
return true;
}
#endif
log_error("No supported vsync method found for this backend");
return false;
}
+7
View File
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <stdbool.h>
typedef struct session session_t;
bool vsync_init(session_t *ps);
+3147
View File
File diff suppressed because it is too large. Load diff
+533
View File
@@ -0,0 +1,533 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2011-2013, Christopher Jeffrey
// Copyright (c) 2013 Richard Grenville <pyxlcy@gmail.com>
#pragma once
#include <stdbool.h>
#include <xcb/damage.h>
#include <xcb/render.h>
#include <xcb/xcb.h>
#include "uthash_extra.h"
// FIXME shouldn't need this
#ifdef CONFIG_OPENGL
#include <GL/gl.h>
#endif
#include "c2.h"
#include "compiler.h"
#include "list.h"
#include "region.h"
#include "render.h"
#include "types.h"
#include "utils.h"
#include "win_defs.h"
#include "x.h"
struct backend_base;
typedef struct session session_t;
typedef struct _glx_texture glx_texture_t;
#define win_stack_foreach_managed(w, win_stack) \
list_foreach(struct managed_win, w, win_stack, base.stack_neighbour) if (w->base.managed)
#define win_stack_foreach_managed_safe(w, win_stack) \
list_foreach_safe(struct managed_win, w, win_stack, \
base.stack_neighbour) if (w->base.managed)
#ifdef CONFIG_OPENGL
// FIXME this type should be in opengl.h
// it is very unideal for it to be here
typedef struct {
/// Framebuffer used for blurring.
GLuint fbo;
/// Textures used for blurring.
GLuint textures[2];
/// Width of the textures.
int width;
/// Height of the textures.
int height;
} glx_blur_cache_t;
#endif
/// An entry in the window stack. May or may not correspond to a window we know about.
struct window_stack_entry {
struct list_node stack_neighbour;
/// The actual window correspond to this stack entry. NULL if we didn't know about
/// this window (e.g. an InputOnly window, or we haven't handled the window
/// creation yet)
struct win *win;
/// The window id. Might not be unique in the stack, because there might be
/// destroyed window still fading out in the stack.
xcb_window_t id;
};
/**
* About coordinate systems
*
* In general, X is the horizontal axis, Y is the vertical axis.
* X goes from left to right, Y goes downwards.
*
* Global: the origin is the top left corner of the Xorg screen.
* Local: the origin is the top left corner of the window, border is
* considered part of the window.
*/
/// Structure representing a top-level managed window.
typedef struct win win;
struct win {
UT_hash_handle hh;
struct list_node stack_neighbour;
/// ID of the top-level frame window.
xcb_window_t id;
/// Whether the window is destroyed from Xorg's perspective
bool destroyed : 1;
/// True if we just received CreateNotify, and haven't queried X for any info
/// about the window
bool is_new : 1;
/// True if this window is managed, i.e. this struct is actually a `managed_win`.
/// Always false if `is_new` is true.
bool managed : 1;
};
struct win_geometry {
int16_t x;
int16_t y;
uint16_t width;
uint16_t height;
uint16_t border_width;
};
struct managed_win {
struct win base;
/// backend data attached to this window. Only available when
/// `state` is not UNMAPPED
void *win_image;
void *old_win_image; // Old window image for interpolating window contents during animations
void *shadow_image;
/// Pointer to the next higher window to paint.
struct managed_win *prev_trans;
/// Number of windows above this window
int stacking_rank;
// TODO(yshui) rethink reg_ignore
// Core members
/// The "mapped state" of this window, doesn't necessary
/// match X mapped state, because of fading.
winstate_t state;
/// Window attributes.
xcb_get_window_attributes_reply_t a;
/// The geometry of the window body, excluding the window border region.
struct win_geometry g;
/// Updated geometry received in events
struct win_geometry pending_g;
/// Xinerama screen this window is on.
int xinerama_scr;
/// Window visual pict format
const xcb_render_pictforminfo_t *pictfmt;
/// Client window visual pict format
const xcb_render_pictforminfo_t *client_pictfmt;
/// Window painting mode.
winmode_t mode;
/// Whether the window has been damaged at least once.
bool ever_damaged;
/// Whether the window was damaged after last paint.
bool pixmap_damaged;
/// Damage of the window.
xcb_damage_damage_t damage;
/// Paint info of the window.
paint_t paint;
/// bitmap for properties which needs to be updated
uint64_t *stale_props;
/// number of uint64_ts that has been allocated for stale_props
size_t stale_props_capacity;
/// Bounding shape of the window. In local coordinates.
/// See above about coordinate systems.
region_t bounding_shape;
/// Window flags. Definitions above.
uint64_t flags;
/// The region of screen that will be obscured when windows above is painted,
/// in global coordinates.
/// We use this to reduce the pixels that needed to be paint when painting
/// this window and anything underneath. Depends on window frame
/// opacity state, window geometry, window mapped/unmapped state,
/// window mode of the windows above. DOES NOT INCLUDE the body of THIS WINDOW.
/// NULL means reg_ignore has not been calculated for this window.
rc_region_t *reg_ignore;
/// Whether the reg_ignore of all windows beneath this window are valid
bool reg_ignore_valid;
/// Cached width/height of the window including border.
int widthb, heightb;
/// Whether the window is bounding-shaped.
bool bounding_shaped;
/// Whether the window just have rounded corners.
bool rounded_corners;
/// Whether this window is to be painted.
bool to_paint;
/// Whether the window is painting excluded.
bool paint_excluded;
/// Whether the window is unredirect-if-possible excluded.
bool unredir_if_possible_excluded;
/// Whether this window is in open/close state.
bool in_openclose;
/// Whether this window should ignore all animations
bool animation_ignore;
/// Whether this window was transient when animated on open
bool animation_transient;
/// Current position and destination, for animation
double animation_center_x, animation_center_y;
double animation_dest_center_x, animation_dest_center_y;
double animation_w, animation_h;
double animation_dest_w, animation_dest_h;
/// Spring animation velocity
double animation_velocity_x, animation_velocity_y;
double animation_velocity_w, animation_velocity_h;
/// Track animation progress; goes from 0 to 1
double animation_progress;
/// Inverse of the window distance at the start of animation, for
/// tracking animation progress
double animation_inv_og_distance;
// Client window related members
/// ID of the top-level client window of the window.
xcb_window_t client_win;
/// Type of the window.
wintype_t window_type;
/// Whether it looks like a WM window. We consider a window WM window if
/// it does not have a decedent with WM_STATE and it is not override-
/// redirected itself.
bool wmwin;
/// Leader window ID of the window.
xcb_window_t leader;
/// Cached topmost window ID of the window.
xcb_window_t cache_leader;
// Focus-related members
/// Whether the window is to be considered focused.
bool focused;
/// Override value of window focus state. Set by D-Bus method calls.
switch_t focused_force;
// Blacklist related members
/// Name of the window.
char *name;
/// Window instance class of the window.
char *class_instance;
/// Window general class of the window.
char *class_general;
/// <code>WM_WINDOW_ROLE</code> value of the window.
char *role;
// Opacity-related members
/// Current window opacity.
double opacity;
/// Target window opacity.
double opacity_target;
/// Previous window opacity.
double opacity_target_old;
/// true if window (or client window, for broken window managers
/// 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;
/// true if opacity is set by some rules
bool opacity_is_set;
/// Last window opacity value set by the rules.
double opacity_set;
/// Radius of rounded window corners
int corner_radius;
float border_col[4];
// Fading-related members
/// Override value of window fade state. Set by D-Bus method calls.
switch_t fade_force;
/// Whether fading is excluded by the rules. Calculated.
bool fade_excluded;
// Frame-opacity-related members
/// Current window frame opacity. Affected by window opacity.
double frame_opacity;
/// Frame extents. Acquired from _NET_FRAME_EXTENTS.
margin_t frame_extents;
// Shadow-related members
/// Whether a window has shadow. Calculated.
bool shadow;
/// Override value of window shadow state. Set by D-Bus method calls.
switch_t shadow_force;
/// Opacity of the shadow. Affected by window opacity and frame opacity.
double shadow_opacity;
/// X offset of shadow. Affected by commandline argument.
int shadow_dx;
/// Y offset of shadow. Affected by commandline argument.
int shadow_dy;
/// Width of shadow. Affected by window size and commandline argument.
int shadow_width;
/// Height of shadow. Affected by window size and commandline argument.
int shadow_height;
/// Picture to render shadow. Affected by window size.
paint_t shadow_paint;
/// The value of _COMPTON_SHADOW attribute of the window. Below 0 for
/// none.
long long prop_shadow;
/// Do not paint shadow over this window.
bool clip_shadow_above;
// Dim-related members
/// Whether the window is to be dimmed.
bool dim;
/// Whether to invert window color.
bool invert_color;
/// Override value of window color inversion state. Set by D-Bus method
/// calls.
switch_t invert_color_force;
/// Whether to blur window background.
bool blur_background;
#ifdef CONFIG_OPENGL
/// Textures and FBO background blur use.
glx_blur_cache_t glx_blur_cache;
/// Background texture of the window
glx_texture_t *glx_texture_bg;
#endif
};
/// Process pending updates/images flags on a window. Has to be called in X critical
/// 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);
/// 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);
/// Start the unmap of a window. We cannot unmap immediately since we might need to fade
/// the window out.
void unmap_win_start(struct session *, struct managed_win *);
/// Start the mapping of a window. We cannot map immediately since we might need to fade
/// the window in.
void map_win_start(struct session *, struct managed_win *);
/// Start the destroying of a window. Windows cannot always be destroyed immediately
/// because of fading and such.
bool must_use destroy_win_start(session_t *ps, struct win *w);
/// Release images bound with a window, set the *_NONE flags on the window. Only to be
/// used when de-initializing the backend outside of win.c
void win_release_images(struct backend_base *base, struct managed_win *w);
winmode_t attr_pure win_calc_mode(const struct managed_win *w);
void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val);
void win_set_fade_force(struct managed_win *w, switch_t val);
void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val);
void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t val);
/**
* Set real focused state of a window.
*/
void win_set_focused(session_t *ps, struct managed_win *w);
bool attr_pure win_should_fade(session_t *ps, const struct managed_win *w);
void win_on_factor_change(session_t *ps, struct managed_win *w);
/**
* Update cache data in struct _win that depends on window size.
*/
void win_on_win_size_change(session_t *ps, struct managed_win *w);
void win_unmark_client(session_t *ps, struct managed_win *w);
void win_recheck_client(session_t *ps, struct managed_win *w);
/**
* Calculate and return the opacity target of a window.
*
* 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
*
* @param ps current session
* @param w struct _win object representing the window
*
* @return target opacity
*/
double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w);
bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w);
void win_update_screen(int nscreens, region_t *screens, struct managed_win *w);
/**
* Retrieve the bounding shape of a window.
*/
// XXX was win_border_size
void win_update_bounding_shape(session_t *ps, struct managed_win *w);
/**
* Check if a window has BYPASS_COMPOSITOR property set
*/
bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *w);
/**
* Get a rectangular region in global coordinates a window (and possibly
* its shadow) occupies.
*
* Note w->shadow and shadow geometry must be correct before calling this
* function.
*/
void win_extents(const struct managed_win *w, region_t *res);
region_t win_extents_by_val(const struct managed_win *w);
/**
* Add a window to damaged area.
*
* @param ps current session
* @param w struct _win element representing the window
*/
void add_damage_from_win(session_t *ps, const struct managed_win *w);
/**
* Get a rectangular region a window occupies, excluding frame and shadow.
*
* Return region in global coordinates.
*/
void win_get_region_noframe_local(const struct managed_win *w, region_t *);
void win_get_region_noframe_local_without_corners(const struct managed_win *w, region_t *);
/// Get the region for the frame of the window
void win_get_region_frame_local(const struct managed_win *w, region_t *res);
/// Get the region for the frame of the window, by value
region_t win_get_region_frame_local_by_val(const struct managed_win *w);
/// 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);
/// Insert a new win entry at the top of the stack
struct win *add_win_top(session_t *ps, xcb_window_t id);
/// Query the Xorg for information about window `win`
/// `win` pointer might become invalid after this function returns
struct win *fill_win(session_t *ps, struct win *win);
/// Move window `w` to be right above `below`
void restack_above(session_t *ps, struct win *w, xcb_window_t below);
/// Move window `w` to the bottom of the stack
void restack_bottom(session_t *ps, struct win *w);
/// Move window `w` to the top of the stack
void restack_top(session_t *ps, struct win *w);
/**
* Execute fade callback of a window if fading finished.
*/
bool must_use win_check_fade_finished(session_t *ps, struct managed_win *w);
// Stop receiving events (except ConfigureNotify, XXX why?) from a window
void win_ev_stop(session_t *ps, const struct win *w);
/// Skip the current in progress fading of window,
/// transition the window straight to its end state
///
/// @return whether the window is destroyed and freed
bool must_use win_skip_fading(session_t *ps, struct managed_win *w);
/**
* Find a managed window from window id in window linked list of the session.
*/
struct managed_win *find_managed_win(session_t *ps, xcb_window_t id);
struct win *find_win(session_t *ps, xcb_window_t id);
struct managed_win *find_toplevel(session_t *ps, xcb_window_t id);
/**
* Find a managed window that is, or is a parent of `wid`.
*
* @param ps current session
* @param wid window 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);
/**
* Check if a window is a fullscreen window.
*
* It's not using w->border_size for performance measures.
*/
bool attr_pure win_is_fullscreen(const session_t *ps, const struct managed_win *w);
/**
* Check if a window is focused, without using any focus rules or forced focus settings
*/
bool attr_pure win_is_focused_raw(const session_t *ps, const struct managed_win *w);
/// check if window has ARGB visual
bool attr_pure win_has_alpha(const struct managed_win *w);
/// check if reg_ignore_valid is true for all windows above us
bool attr_pure win_is_region_ignore_valid(session_t *ps, const struct managed_win *w);
/// Whether a given window is mapped on the X server side
bool win_is_mapped_in_x(const struct managed_win *w);
// Find the managed window immediately below `w` in the window stack
struct managed_win *attr_pure win_stack_find_next_managed(const session_t *ps,
const struct list_node *w);
/// Set flags on a window. Some sanity checks are performed
void win_set_flags(struct managed_win *w, uint64_t flags);
/// Clear flags on a window. Some sanity checks are performed
void win_clear_flags(struct managed_win *w, uint64_t flags);
/// Returns true if any of the flags in `flags` is set
bool win_check_flags_any(struct managed_win *w, uint64_t flags);
/// Returns true if all of the flags in `flags` are set
bool win_check_flags_all(struct managed_win *w, uint64_t flags);
/// Mark properties as stale for a window
void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *prop, int nprops);
static inline attr_unused void win_set_property_stale(struct managed_win *w, xcb_atom_t prop) {
return win_set_properties_stale(w, (xcb_atom_t[]){prop}, 1);
}
/// Free all resources in a struct win
void free_win_res(session_t *ps, struct managed_win *w);
static inline void win_region_remove_corners(const struct managed_win *w, region_t *res) {
region_t corners;
pixman_region32_init_rects(
&corners,
(rect_t[]){
{.x1 = 0, .y1 = 0, .x2 = w->corner_radius, .y2 = w->corner_radius},
{.x1 = 0, .y1 = w->heightb - w->corner_radius, .x2 = w->corner_radius, .y2 = w->heightb},
{.x1 = w->widthb - w->corner_radius, .y1 = 0, .x2 = w->widthb, .y2 = w->corner_radius},
{.x1 = w->widthb - w->corner_radius,
.y1 = w->heightb - w->corner_radius,
.x2 = w->widthb,
.y2 = w->heightb},
},
4);
pixman_region32_subtract(res, res, &corners);
pixman_region32_fini(&corners);
}
static inline region_t attr_unused win_get_bounding_shape_global_by_val(struct managed_win *w) {
region_t ret;
pixman_region32_init(&ret);
pixman_region32_copy(&ret, &w->bounding_shape);
pixman_region32_translate(&ret, w->g.x, w->g.y);
return ret;
}
static inline region_t
win_get_bounding_shape_global_without_corners_by_val(struct managed_win *w) {
region_t ret;
pixman_region32_init(&ret);
pixman_region32_copy(&ret, &w->bounding_shape);
win_region_remove_corners(w, &ret);
pixman_region32_translate(&ret, w->g.x, w->g.y);
return ret;
}
/**
* Calculate the extents of the frame of the given window based on EWMH
* _NET_FRAME_EXTENTS and the X window border width.
*/
static inline margin_t attr_pure attr_unused win_calc_frame_extents(const struct managed_win *w) {
margin_t result = w->frame_extents;
result.top = max2(result.top, w->g.border_width);
result.left = max2(result.left, w->g.border_width);
result.bottom = max2(result.bottom, w->g.border_width);
result.right = max2(result.right, w->g.border_width);
return result;
}
/**
* Check whether a window has WM frames.
*/
static inline bool attr_pure attr_unused win_has_frame(const struct managed_win *w) {
return w->g.border_width || w->frame_extents.top || w->frame_extents.left ||
w->frame_extents.right || w->frame_extents.bottom;
}
+102
View File
@@ -0,0 +1,102 @@
#pragma once
#include <stdint.h>
typedef enum {
WINTYPE_UNKNOWN,
WINTYPE_DESKTOP,
WINTYPE_DOCK,
WINTYPE_TOOLBAR,
WINTYPE_MENU,
WINTYPE_UTILITY,
WINTYPE_SPLASH,
WINTYPE_DIALOG,
WINTYPE_NORMAL,
WINTYPE_DROPDOWN_MENU,
WINTYPE_POPUP_MENU,
WINTYPE_TOOLTIP,
WINTYPE_NOTIFICATION,
WINTYPE_COMBO,
WINTYPE_DND,
NUM_WINTYPES
} wintype_t;
/// Enumeration type of window painting mode.
typedef enum {
WMODE_TRANS, // The window body is (potentially) transparent
WMODE_FRAME_TRANS, // The window body is opaque, but the frame is not
WMODE_SOLID, // The window is opaque including the frame
} winmode_t;
/// Transition table:
/// (DESTROYED is when the win struct is destroyed and freed)
/// ('o' means in all other cases)
/// (Window is created in the UNMAPPED state)
/// +-------------+---------+----------+-------+-------+--------+--------+---------+
/// | |UNMAPPING|DESTROYING|MAPPING|FADING |UNMAPPED| MAPPED |DESTROYED|
/// +-------------+---------+----------+-------+-------+--------+--------+---------+
/// | UNMAPPING | o | Window |Window | - | Fading | - | - |
/// | | |destroyed |mapped | |finished| | |
/// +-------------+---------+----------+-------+-------+--------+--------+---------+
/// | DESTROYING | - | o | - | - | - | - | Fading |
/// | | | | | | | |finished |
/// +-------------+---------+----------+-------+-------+--------+--------+---------+
/// | MAPPING | Window | Window | o |Opacity| - | Fading | - |
/// | |unmapped |destroyed | |change | |finished| |
/// +-------------+---------+----------+-------+-------+--------+--------+---------+
/// | FADING | Window | Window | - | o | - | Fading | - |
/// | |unmapped |destroyed | | | |finished| |
/// +-------------+---------+----------+-------+-------+--------+--------+---------+
/// | UNMAPPED | - | - |Window | - | o | - | Window |
/// | | | |mapped | | | |destroyed|
/// +-------------+---------+----------+-------+-------+--------+--------+---------+
/// | MAPPED | Window | Window | - |Opacity| - | o | - |
/// | |unmapped |destroyed | |change | | | |
/// +-------------+---------+----------+-------+-------+--------+--------+---------+
typedef enum {
// The window is being faded out because it's unmapped.
WSTATE_UNMAPPING,
// The window is being faded out because it's destroyed,
WSTATE_DESTROYING,
// The window is being faded in
WSTATE_MAPPING,
// Window opacity is not at the target level
WSTATE_FADING,
// The window is mapped, no fading is in progress.
WSTATE_MAPPED,
// The window is unmapped, no fading is in progress.
WSTATE_UNMAPPED,
} winstate_t;
enum win_flags {
// Note: *_NONE flags are mostly redudant and meant for detecting logical errors
// in the code
/// pixmap is out of date, will be update in win_process_flags
WIN_FLAGS_PIXMAP_STALE = 1,
/// window does not have pixmap bound
WIN_FLAGS_PIXMAP_NONE = 2,
/// there was an error trying to bind the images
WIN_FLAGS_IMAGE_ERROR = 4,
/// shadow is out of date, will be updated in win_process_flags
WIN_FLAGS_SHADOW_STALE = 8,
/// shadow has not been generated
WIN_FLAGS_SHADOW_NONE = 16,
/// the client window needs to be updated
WIN_FLAGS_CLIENT_STALE = 32,
/// the window is mapped by X, we need to call map_win_start for it
WIN_FLAGS_MAPPED = 64,
/// this window has properties which needs to be updated
WIN_FLAGS_PROPERTY_STALE = 128,
// TODO(yshui) _maybe_ split SIZE_STALE into SIZE_STALE and SHAPE_STALE
/// this window has an unhandled size/shape change
WIN_FLAGS_SIZE_STALE = 256,
/// this window has an unhandled position (i.e. x and y) change
WIN_FLAGS_POSITION_STALE = 512,
/// need better name for this, is set when some aspects of the window changed
WIN_FLAGS_FACTOR_CHANGED = 1024,
};
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)
+744
View File
@@ -0,0 +1,744 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#include <stdalign.h>
#include <stdbool.h>
#include <stdlib.h>
#include <X11/Xutil.h>
#include <pixman.h>
#include <xcb/composite.h>
#include <xcb/damage.h>
#include <xcb/glx.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb.h>
#include <xcb/xcb_renderutil.h>
#include <xcb/xfixes.h>
#include "atom.h"
#ifdef CONFIG_OPENGL
#include "backend/gl/glx.h"
#endif
#include "common.h"
#include "compiler.h"
#include "kernel.h"
#include "log.h"
#include "region.h"
#include "utils.h"
#include "x.h"
/**
* Get a specific attribute of a window.
*
* Returns a blank structure if the returned type and format does not
* match the requested type and format.
*
* @param ps current session
* @param w window
* @param atom atom of attribute to fetch
* @param length length to read
* @param rtype atom of the requested type
* @param rformat requested format
* @return a <code>winprop_t</code> structure containing the attribute
* and number of items. A blank one on failure.
*/
winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom,
int offset, int length, xcb_atom_t rtype, int rformat) {
xcb_get_property_reply_t *r = xcb_get_property_reply(
c,
xcb_get_property(c, 0, w, atom, rtype, to_u32_checked(offset),
to_u32_checked(length)),
NULL);
if (r && xcb_get_property_value_length(r) &&
(rtype == XCB_GET_PROPERTY_TYPE_ANY || r->type == rtype) &&
(!rformat || r->format == rformat) &&
(r->format == 8 || r->format == 16 || r->format == 32)) {
auto len = xcb_get_property_value_length(r);
return (winprop_t){
.ptr = xcb_get_property_value(r),
.nitems = (ulong)(len / (r->format / 8)),
.type = r->type,
.format = r->format,
.r = r,
};
}
free(r);
return (winprop_t){
.ptr = NULL, .nitems = 0, .type = XCB_GET_PROPERTY_TYPE_ANY, .format = 0};
}
/// Get the type, format and size in bytes of a window's specific attribute.
winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom) {
xcb_generic_error_t *e = NULL;
auto r = xcb_get_property_reply(
c, xcb_get_property(c, 0, w, atom, XCB_ATOM_ANY, 0, 0), &e);
if (!r) {
log_debug_x_error(e, "Failed to get property info for window %#010x", w);
free(e);
return (winprop_info_t){
.type = XCB_GET_PROPERTY_TYPE_ANY, .format = 0, .length = 0};
}
winprop_info_t winprop_info = {
.type = r->type, .format = r->format, .length = r->bytes_after};
free(r);
return winprop_info;
}
/**
* Get the value of a type-<code>xcb_window_t</code> property of a window.
*
* @return the value if successful, 0 otherwise
*/
xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop) {
// Get the attribute
xcb_window_t p = XCB_NONE;
winprop_t prop = x_get_prop(c, wid, aprop, 1L, XCB_ATOM_WINDOW, 32);
// Return it
if (prop.nitems) {
p = (xcb_window_t)*prop.p32;
}
free_winprop(&prop);
return p;
}
/**
* Get the value of a text property of a window.
*/
bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst,
int *pnstr) {
assert(ps->server_grabbed);
auto prop_info = x_get_prop_info(ps->c, wid, prop);
auto type = prop_info.type;
auto format = prop_info.format;
auto length = prop_info.length;
if (type == XCB_ATOM_NONE) {
return false;
}
if (type != XCB_ATOM_STRING && type != ps->atoms->aUTF8_STRING &&
type != ps->atoms->aC_STRING) {
log_warn("Text property %d of window %#010x has unsupported type: %d",
prop, wid, type);
return false;
}
if (format != 8) {
log_warn("Text property %d of window %#010x has unexpected format: %d",
prop, wid, format);
return false;
}
xcb_generic_error_t *e = NULL;
auto word_count = (length + 4 - 1) / 4;
auto r = xcb_get_property_reply(
ps->c, xcb_get_property(ps->c, 0, wid, prop, type, 0, word_count), &e);
if (!r) {
log_debug_x_error(e, "Failed to get window property for %#010x", wid);
free(e);
return false;
}
assert(length == (uint32_t)xcb_get_property_value_length(r));
void *data = xcb_get_property_value(r);
unsigned int nstr = 0;
uint32_t current_offset = 0;
while (current_offset < length) {
current_offset +=
(uint32_t)strnlen(data + current_offset, length - current_offset) + 1;
nstr += 1;
}
if (nstr == 0) {
// The property is set to an empty string, in that case, we return one
// string
char **strlst = malloc(sizeof(char *));
strlst[0] = "";
*pnstr = 1;
*pstrlst = strlst;
free(r);
return true;
}
// Allocate the pointers and the strings together
void *buf = NULL;
if (posix_memalign(&buf, alignof(char *), length + sizeof(char *) * nstr + 1) != 0) {
abort();
}
char *strlst = buf + sizeof(char *) * nstr;
memcpy(strlst, xcb_get_property_value(r), length);
strlst[length] = '\0'; // X strings aren't guaranteed to be null terminated
char **ret = buf;
current_offset = 0;
nstr = 0;
while (current_offset < length) {
ret[nstr] = strlst + current_offset;
current_offset += (uint32_t)strlen(strlst + current_offset) + 1;
nstr += 1;
}
*pnstr = to_int_checked(nstr);
*pstrlst = ret;
free(r);
return true;
}
// A cache of pict formats. We assume they don't change during the lifetime
// of this program
static thread_local xcb_render_query_pict_formats_reply_t *g_pictfmts = NULL;
static inline void x_get_server_pictfmts(xcb_connection_t *c) {
if (g_pictfmts) {
return;
}
xcb_generic_error_t *e = NULL;
// Get window picture format
g_pictfmts =
xcb_render_query_pict_formats_reply(c, xcb_render_query_pict_formats(c), &e);
if (e || !g_pictfmts) {
log_fatal("failed to get pict formats\n");
abort();
}
}
const xcb_render_pictforminfo_t *
x_get_pictform_for_visual(xcb_connection_t *c, xcb_visualid_t visual) {
x_get_server_pictfmts(c);
xcb_render_pictvisual_t *pv = xcb_render_util_find_visual_format(g_pictfmts, visual);
for (xcb_render_pictforminfo_iterator_t i =
xcb_render_query_pict_formats_formats_iterator(g_pictfmts);
i.rem; xcb_render_pictforminfo_next(&i)) {
if (i.data->id == pv->format) {
return i.data;
}
}
return NULL;
}
static xcb_visualid_t attr_pure x_get_visual_for_pictfmt(xcb_render_query_pict_formats_reply_t *r,
xcb_render_pictformat_t fmt) {
for (auto screen = xcb_render_query_pict_formats_screens_iterator(r); screen.rem;
xcb_render_pictscreen_next(&screen)) {
for (auto depth = xcb_render_pictscreen_depths_iterator(screen.data);
depth.rem; xcb_render_pictdepth_next(&depth)) {
for (auto pv = xcb_render_pictdepth_visuals_iterator(depth.data);
pv.rem; xcb_render_pictvisual_next(&pv)) {
if (pv.data->format == fmt) {
return pv.data->visual;
}
}
}
}
return XCB_NONE;
}
xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) {
x_get_server_pictfmts(c);
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id);
}
xcb_render_pictformat_t
x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) {
x_get_server_pictfmts(c);
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
return pictfmt->id;
}
int x_get_visual_depth(xcb_connection_t *c, xcb_visualid_t visual) {
auto setup = xcb_get_setup(c);
for (auto screen = xcb_setup_roots_iterator(setup); screen.rem;
xcb_screen_next(&screen)) {
for (auto depth = xcb_screen_allowed_depths_iterator(screen.data);
depth.rem; xcb_depth_next(&depth)) {
const int len = xcb_depth_visuals_length(depth.data);
const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data);
for (int i = 0; i < len; i++) {
if (visual == visuals[i].visual_id) {
return depth.data->depth;
}
}
}
}
return -1;
}
xcb_render_picture_t
x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c,
const xcb_render_pictforminfo_t *pictfmt,
xcb_pixmap_t pixmap, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr) {
void *buf = NULL;
if (attr) {
xcb_render_create_picture_value_list_serialize(&buf, valuemask, attr);
if (!buf) {
log_error("failed to serialize picture attributes");
return XCB_NONE;
}
}
xcb_render_picture_t tmp_picture = x_new_id(c);
xcb_generic_error_t *e =
xcb_request_check(c, xcb_render_create_picture_checked(
c, tmp_picture, pixmap, pictfmt->id, valuemask, buf));
free(buf);
if (e) {
log_error_x_error(e, "failed to create picture");
return XCB_NONE;
}
return tmp_picture;
}
xcb_render_picture_t
x_create_picture_with_visual_and_pixmap(xcb_connection_t *c, xcb_visualid_t visual,
xcb_pixmap_t pixmap, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr) {
const xcb_render_pictforminfo_t *pictfmt = x_get_pictform_for_visual(c, visual);
return x_create_picture_with_pictfmt_and_pixmap(c, pictfmt, pixmap, valuemask, attr);
}
xcb_render_picture_t
x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard_t standard,
xcb_pixmap_t pixmap, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr) {
x_get_server_pictfmts(c);
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard);
assert(pictfmt);
return x_create_picture_with_pictfmt_and_pixmap(c, pictfmt, pixmap, valuemask, attr);
}
xcb_render_picture_t
x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
xcb_pict_standard_t standard, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr) {
x_get_server_pictfmts(c);
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard);
assert(pictfmt);
return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr);
}
/**
* Create an picture.
*/
xcb_render_picture_t
x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr) {
uint8_t depth = pictfmt->depth;
xcb_pixmap_t tmp_pixmap = x_create_pixmap(c, depth, d, w, h);
if (!tmp_pixmap) {
return XCB_NONE;
}
xcb_render_picture_t picture = x_create_picture_with_pictfmt_and_pixmap(
c, pictfmt, tmp_pixmap, valuemask, attr);
xcb_free_pixmap(c, tmp_pixmap);
return picture;
}
xcb_render_picture_t
x_create_picture_with_visual(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
xcb_visualid_t visual, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr) {
auto pictfmt = x_get_pictform_for_visual(c, visual);
return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr);
}
bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_t *res) {
xcb_generic_error_t *e = NULL;
xcb_xfixes_fetch_region_reply_t *xr =
xcb_xfixes_fetch_region_reply(c, xcb_xfixes_fetch_region(c, r), &e);
if (!xr) {
log_error_x_error(e, "Failed to fetch rectangles");
return false;
}
int nrect = xcb_xfixes_fetch_region_rectangles_length(xr);
auto b = ccalloc(nrect, pixman_box32_t);
xcb_rectangle_t *xrect = xcb_xfixes_fetch_region_rectangles(xr);
for (int i = 0; i < nrect; i++) {
b[i] = (pixman_box32_t){.x1 = xrect[i].x,
.y1 = xrect[i].y,
.x2 = xrect[i].x + xrect[i].width,
.y2 = xrect[i].y + xrect[i].height};
}
bool ret = pixman_region32_init_rects(res, b, nrect);
free(b);
free(xr);
return ret;
}
void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict,
int16_t clip_x_origin, int16_t clip_y_origin,
const region_t *reg) {
int nrects;
const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects);
auto xrects = ccalloc(nrects, xcb_rectangle_t);
for (int i = 0; i < nrects; i++) {
xrects[i] = (xcb_rectangle_t){
.x = to_i16_checked(rects[i].x1),
.y = to_i16_checked(rects[i].y1),
.width = to_u16_checked(rects[i].x2 - rects[i].x1),
.height = to_u16_checked(rects[i].y2 - rects[i].y1),
};
}
xcb_generic_error_t *e = xcb_request_check(
c, xcb_render_set_picture_clip_rectangles_checked(
c, pict, clip_x_origin, clip_y_origin, to_u32_checked(nrects), xrects));
if (e) {
log_error_x_error(e, "Failed to set clip region");
free(e);
}
free(xrects);
}
void x_clear_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict) {
xcb_render_change_picture_value_list_t v = {.clipmask = XCB_NONE};
xcb_generic_error_t *e = xcb_request_check(
c, xcb_render_change_picture(c, pict, XCB_RENDER_CP_CLIP_MASK, &v));
if (e) {
log_error_x_error(e, "failed to clear clip region");
free(e);
}
}
enum {
XSyncBadCounter = 0,
XSyncBadAlarm = 1,
XSyncBadFence = 2,
};
/**
* Convert a X11 error to string
*
* @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
* for multiple calls to this function,
*/
static const char *
_x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) {
session_t *const ps = ps_g;
int o = 0;
const char *name = "Unknown";
#define CASESTRRET(s) \
case s: \
name = #s; \
break
#define CASESTRRET2(s) \
case XCB_##s: name = #s; break
// TODO(yshui) separate error code out from session_t
o = error_code - ps->xfixes_error;
switch (o) { CASESTRRET2(XFIXES_BAD_REGION); }
o = error_code - ps->damage_error;
switch (o) { CASESTRRET2(DAMAGE_BAD_DAMAGE); }
o = error_code - ps->render_error;
switch (o) {
CASESTRRET2(RENDER_PICT_FORMAT);
CASESTRRET2(RENDER_PICTURE);
CASESTRRET2(RENDER_PICT_OP);
CASESTRRET2(RENDER_GLYPH_SET);
CASESTRRET2(RENDER_GLYPH);
}
if (ps->glx_exists) {
o = error_code - ps->glx_error;
switch (o) {
CASESTRRET2(GLX_BAD_CONTEXT);
CASESTRRET2(GLX_BAD_CONTEXT_STATE);
CASESTRRET2(GLX_BAD_DRAWABLE);
CASESTRRET2(GLX_BAD_PIXMAP);
CASESTRRET2(GLX_BAD_CONTEXT_TAG);
CASESTRRET2(GLX_BAD_CURRENT_WINDOW);
CASESTRRET2(GLX_BAD_RENDER_REQUEST);
CASESTRRET2(GLX_BAD_LARGE_REQUEST);
CASESTRRET2(GLX_UNSUPPORTED_PRIVATE_REQUEST);
CASESTRRET2(GLX_BAD_FB_CONFIG);
CASESTRRET2(GLX_BAD_PBUFFER);
CASESTRRET2(GLX_BAD_CURRENT_DRAWABLE);
CASESTRRET2(GLX_BAD_WINDOW);
CASESTRRET2(GLX_GLX_BAD_PROFILE_ARB);
}
}
if (ps->xsync_exists) {
o = error_code - ps->xsync_error;
switch (o) {
CASESTRRET(XSyncBadCounter);
CASESTRRET(XSyncBadAlarm);
CASESTRRET(XSyncBadFence);
}
}
switch (error_code) {
CASESTRRET2(ACCESS);
CASESTRRET2(ALLOC);
CASESTRRET2(ATOM);
CASESTRRET2(COLORMAP);
CASESTRRET2(CURSOR);
CASESTRRET2(DRAWABLE);
CASESTRRET2(FONT);
CASESTRRET2(G_CONTEXT);
CASESTRRET2(ID_CHOICE);
CASESTRRET2(IMPLEMENTATION);
CASESTRRET2(LENGTH);
CASESTRRET2(MATCH);
CASESTRRET2(NAME);
CASESTRRET2(PIXMAP);
CASESTRRET2(REQUEST);
CASESTRRET2(VALUE);
CASESTRRET2(WINDOW);
}
#undef CASESTRRET
#undef CASESTRRET2
thread_local static char buffer[256];
snprintf(buffer, sizeof(buffer), "X error %d %s request %d minor %d serial %lu",
error_code, name, major, minor, serial);
return buffer;
}
/**
* Log a X11 error
*/
void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) {
log_debug("%s", _x_strerror(serial, major, minor, error_code));
}
/*
* Convert a xcb_generic_error_t to a string that describes the error
*
* @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
* for multiple calls to this function,
*/
const char *x_strerror(xcb_generic_error_t *e) {
if (!e) {
return "No error";
}
return _x_strerror(e->full_sequence, e->major_code, e->minor_code, e->error_code);
}
/**
* Create a pixmap and check that creation succeeded.
*/
xcb_pixmap_t x_create_pixmap(xcb_connection_t *c, uint8_t depth, xcb_drawable_t drawable,
int width, int height) {
xcb_pixmap_t pix = x_new_id(c);
xcb_void_cookie_t cookie = xcb_create_pixmap_checked(
c, depth, pix, drawable, to_u16_checked(width), to_u16_checked(height));
xcb_generic_error_t *err = xcb_request_check(c, cookie);
if (err == NULL) {
return pix;
}
log_error_x_error(err, "Failed to create pixmap");
free(err);
return XCB_NONE;
}
/**
* Validate a pixmap.
*
* Detect whether the pixmap is valid with XGetGeometry. Well, maybe there
* are better ways.
*/
bool x_validate_pixmap(xcb_connection_t *c, xcb_pixmap_t pixmap) {
if (pixmap == XCB_NONE) {
return false;
}
auto r = xcb_get_geometry_reply(c, xcb_get_geometry(c, pixmap), NULL);
if (!r) {
return false;
}
bool ret = r->width && r->height;
free(r);
return ret;
}
/// Names of root window properties that could point to a pixmap of
/// background.
static const char *background_props_str[] = {
"_XROOTPMAP_ID",
"_XSETROOT_ID",
0,
};
xcb_pixmap_t
x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms) {
xcb_pixmap_t pixmap = XCB_NONE;
// Get the values of background attributes
for (int p = 0; background_props_str[p]; p++) {
xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]);
winprop_t prop = x_get_prop(c, root, prop_atom, 1, XCB_ATOM_PIXMAP, 32);
if (prop.nitems) {
pixmap = (xcb_pixmap_t)*prop.p32;
free_winprop(&prop);
break;
}
free_winprop(&prop);
}
return pixmap;
}
bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom) {
for (int p = 0; background_props_str[p]; p++) {
xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]);
if (prop_atom == atom) {
return true;
}
}
return false;
}
/**
* Synchronizes a X Render drawable to ensure all pending painting requests
* are completed.
*/
bool x_fence_sync(xcb_connection_t *c, xcb_sync_fence_t f) {
// TODO(richardgv): If everybody just follows the rules stated in X Sync
// prototype, we need only one fence per screen, but let's stay a bit
// cautious right now
auto e = xcb_request_check(c, xcb_sync_trigger_fence_checked(c, f));
if (e) {
log_error_x_error(e, "Failed to trigger the fence");
goto err;
}
e = xcb_request_check(c, xcb_sync_await_fence_checked(c, 1, &f));
if (e) {
log_error_x_error(e, "Failed to await on a fence");
goto err;
}
e = xcb_request_check(c, xcb_sync_reset_fence_checked(c, f));
if (e) {
log_error_x_error(e, "Failed to reset the fence");
goto err;
}
return true;
err:
free(e);
return false;
}
/**
* 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,
* for compatibility with legacy code.
*
* @param[in] kernel the convolution kernel
* @param[in] center the element to put at the center of the matrix
* @param[inout] ret pointer to an array of `size`, if `size` is too small, more space
* will be allocated, and `*ret` will be updated
* @param[inout] size size of the array pointed to by `ret`, in number of elements
* @return number of elements filled into `*ret`
*/
void x_create_convolution_kernel(const conv *kernel, double center,
struct x_convolution_kernel **ret) {
assert(ret);
if (!*ret || (*ret)->capacity < kernel->w * kernel->h + 2) {
free(*ret);
*ret =
cvalloc(sizeof(struct x_convolution_kernel) +
(size_t)(kernel->w * kernel->h + 2) * sizeof(xcb_render_fixed_t));
(*ret)->capacity = kernel->w * kernel->h + 2;
}
(*ret)->size = kernel->w * kernel->h + 2;
auto buf = (*ret)->kernel;
buf[0] = DOUBLE_TO_XFIXED(kernel->w);
buf[1] = DOUBLE_TO_XFIXED(kernel->h);
double sum = center;
for (int i = 0; i < kernel->w * kernel->h; i++) {
if (i == kernel->w * kernel->h / 2) {
continue;
}
sum += kernel->data[i];
}
// Note for floating points a / b != a * (1 / b), but this shouldn't have any real
// impact on the result
double factor = sum != 0 ? 1.0 / sum : 1;
for (int i = 0; i < kernel->w * kernel->h; i++) {
buf[i + 2] = DOUBLE_TO_XFIXED(kernel->data[i] * factor);
}
buf[kernel->h / 2 * kernel->w + kernel->w / 2 + 2] =
DOUBLE_TO_XFIXED(center * factor);
}
/// Generate a search criteria for fbconfig from a X visual.
/// Returns {-1, -1, -1, -1, -1, 0} on failure
struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual) {
auto pictfmt = x_get_pictform_for_visual(c, visual);
auto depth = x_get_visual_depth(c, visual);
if (!pictfmt || depth == -1) {
log_error("Invalid visual %#03x", visual);
return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
}
if (pictfmt->type != XCB_RENDER_PICT_TYPE_DIRECT) {
log_error("We cannot handle non-DirectColor visuals. Report an "
"issue if you see this error message.");
return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
}
int red_size = popcntul(pictfmt->direct.red_mask),
blue_size = popcntul(pictfmt->direct.blue_mask),
green_size = popcntul(pictfmt->direct.green_mask),
alpha_size = popcntul(pictfmt->direct.alpha_mask);
return (struct xvisual_info){
.red_size = red_size,
.green_size = green_size,
.blue_size = blue_size,
.alpha_size = alpha_size,
.visual_depth = depth,
.visual = visual,
};
}
xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen) {
xcb_screen_iterator_t iter;
iter = xcb_setup_roots_iterator(xcb_get_setup(c));
for (; iter.rem; --screen, xcb_screen_next(&iter)) {
if (screen == 0) {
return iter.data;
}
}
return NULL;
}
+297
View File
@@ -0,0 +1,297 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb.h>
#include <xcb/xcb_renderutil.h>
#include <xcb/xfixes.h>
#include "compiler.h"
#include "kernel.h"
#include "log.h"
#include "region.h"
typedef struct session session_t;
struct atom;
/// Structure representing Window property value.
typedef struct winprop {
union {
void *ptr;
int8_t *p8;
int16_t *p16;
int32_t *p32;
uint32_t *c32; // 32bit cardinal
};
unsigned long nitems;
xcb_atom_t type;
int format;
xcb_get_property_reply_t *r;
} winprop_t;
typedef struct winprop_info {
xcb_atom_t type;
uint8_t format;
uint32_t length;
} winprop_info_t;
struct xvisual_info {
/// Bit depth of the red component
int red_size;
/// Bit depth of the green component
int green_size;
/// Bit depth of the blue component
int blue_size;
/// Bit depth of the alpha component
int alpha_size;
/// The depth of X visual
int visual_depth;
xcb_visualid_t visual;
};
#define XCB_AWAIT_VOID(func, c, ...) \
({ \
bool __success = true; \
__auto_type __e = xcb_request_check(c, func##_checked(c, __VA_ARGS__)); \
if (__e) { \
x_print_error(__e->sequence, __e->major_code, __e->minor_code, \
__e->error_code); \
free(__e); \
__success = false; \
} \
__success; \
})
#define XCB_AWAIT(func, c, ...) \
({ \
xcb_generic_error_t *__e = NULL; \
__auto_type __r = func##_reply(c, func(c, __VA_ARGS__), &__e); \
if (__e) { \
x_print_error(__e->sequence, __e->major_code, __e->minor_code, \
__e->error_code); \
free(__e); \
} \
__r; \
})
#define log_debug_x_error(e, fmt, ...) \
LOG(DEBUG, fmt " (%s)", ##__VA_ARGS__, x_strerror(e))
#define log_error_x_error(e, fmt, ...) \
LOG(ERROR, fmt " (%s)", ##__VA_ARGS__, x_strerror(e))
#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);
if (ret == (uint32_t)-1) {
log_fatal("We seems to have run of XIDs. This is either a bug in the X "
"server, or a resource leakage in the compositor. Please open "
"an issue about this problem. The compositor will die.");
abort();
}
return ret;
}
/**
* Send a request to X server and get the reply to make sure all previous
* requests are processed, and their replies received
*
* xcb_get_input_focus is used here because it is the same request used by
* libX11
*/
static inline void x_sync(xcb_connection_t *c) {
free(xcb_get_input_focus_reply(c, xcb_get_input_focus(c), NULL));
}
/**
* Get a specific attribute of a window.
*
* Returns a blank structure if the returned type and format does not
* match the requested type and format.
*
* @param ps current session
* @param w window
* @param atom atom of attribute to fetch
* @param length length to read
* @param rtype atom of the requested type
* @param rformat requested format
* @return a <code>winprop_t</code> structure containing the attribute
* and number of items. A blank one on failure.
*/
winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom,
int offset, int length, xcb_atom_t rtype, int rformat);
/**
* Wrapper of wid_get_prop_adv().
*/
static inline winprop_t x_get_prop(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t atom,
int length, xcb_atom_t rtype, int rformat) {
return x_get_prop_with_offset(c, wid, atom, 0L, length, rtype, rformat);
}
/// Get the type, format and size in bytes of a window's specific attribute.
winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom);
/// Discard all X events in queue or in flight. Should only be used when the server is
/// grabbed
static inline void x_discard_events(xcb_connection_t *c) {
xcb_generic_event_t *e;
while ((e = xcb_poll_for_event(c))) {
free(e);
}
}
/**
* Get the value of a type-<code>xcb_window_t</code> property of a window.
*
* @return the value if successful, 0 otherwise
*/
xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop);
/**
* Get the value of a text property of a window.
*
* @param[out] pstrlst Out parameter for an array of strings, caller needs to free this
* array
* @param[out] pnstr Number of strings in the array
*/
bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst,
int *pnstr);
const xcb_render_pictforminfo_t *
x_get_pictform_for_visual(xcb_connection_t *, xcb_visualid_t);
int x_get_visual_depth(xcb_connection_t *, xcb_visualid_t);
xcb_render_picture_t
x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *,
const xcb_render_pictforminfo_t *pictfmt,
xcb_pixmap_t pixmap, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr)
attr_nonnull(1, 2);
xcb_render_picture_t
x_create_picture_with_visual_and_pixmap(xcb_connection_t *, xcb_visualid_t visual,
xcb_pixmap_t pixmap, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr)
attr_nonnull(1);
xcb_render_picture_t
x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_t standard,
xcb_pixmap_t pixmap, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr)
attr_nonnull(1);
xcb_render_picture_t
x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
xcb_pict_standard_t standard, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr)
attr_nonnull(1);
/**
* Create an picture.
*/
xcb_render_picture_t
x_create_picture_with_pictfmt(xcb_connection_t *, xcb_drawable_t, int w, int h,
const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr)
attr_nonnull(1, 5);
xcb_render_picture_t
x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h,
xcb_visualid_t visual, uint32_t valuemask,
const xcb_render_create_picture_value_list_t *attr)
attr_nonnull(1);
/// Fetch a X region and store it in a pixman region
bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res);
void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin,
int16_t clip_y_origin, const region_t *);
void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict);
/**
* Log a X11 error
*/
void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code);
/*
* Convert a xcb_generic_error_t to a string that describes the error
*
* @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
* for multiple calls to this function,
*/
const char *x_strerror(xcb_generic_error_t *e);
xcb_pixmap_t x_create_pixmap(xcb_connection_t *, uint8_t depth, xcb_drawable_t drawable,
int width, int height);
bool x_validate_pixmap(xcb_connection_t *, xcb_pixmap_t pxmap);
/**
* Free a <code>winprop_t</code>.
*
* @param pprop pointer to the <code>winprop_t</code> to free.
*/
static inline void free_winprop(winprop_t *pprop) {
// Empty the whole structure to avoid possible issues
if (pprop->r)
free(pprop->r);
pprop->ptr = NULL;
pprop->r = NULL;
pprop->nitems = 0;
}
/// Get the back pixmap of the root window
xcb_pixmap_t
x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms);
/// Return true if the atom refers to a property name that is used for the
/// root window background pixmap
bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom);
bool x_fence_sync(xcb_connection_t *, xcb_sync_fence_t);
struct x_convolution_kernel {
int size;
int capacity;
xcb_render_fixed_t kernel[];
};
/**
* 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,
* for compatibility with legacy code.
*
* @param[in] kernel the convolution kernel
* @param[in] center the element to put at the center of the matrix
* @param[inout] ret pointer to an array of `size`, if `size` is too small, more space
* will be allocated, and `*ret` will be updated.
* @param[inout] size size of the array pointed to by `ret`.
*/
void attr_nonnull(1, 3) x_create_convolution_kernel(const conv *kernel, double center,
struct x_convolution_kernel **ret);
/// Generate a search criteria for fbconfig from a X visual.
/// Returns {-1, -1, -1, -1, -1, -1} on failure
struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual);
xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std);
xcb_render_pictformat_t
x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std);
xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen);
uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c);
+65
View File
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2014 Richard Grenville <pyxlcy@gmail.com>
#include "compiler.h"
#include "log.h"
#include "xrescheck.h"
static xrc_xid_record_t *gs_xid_records = NULL;
#define HASH_ADD_XID(head, xidfield, add) HASH_ADD(hh, head, xidfield, sizeof(xid), add)
#define HASH_FIND_XID(head, findxid, out) HASH_FIND(hh, head, findxid, sizeof(xid), out)
#define M_CPY_POS_DATA(prec) \
prec->file = file; \
prec->func = func; \
prec->line = line;
/**
* @brief Add a record of given XID to the allocation table.
*/
void xrc_add_xid_(XID xid, const char *type, M_POS_DATA_PARAMS) {
auto prec = ccalloc(1, xrc_xid_record_t);
prec->xid = xid;
prec->type = type;
M_CPY_POS_DATA(prec);
HASH_ADD_XID(gs_xid_records, xid, prec);
}
/**
* @brief Delete a record of given XID in the allocation table.
*/
void xrc_delete_xid_(XID xid, M_POS_DATA_PARAMS) {
xrc_xid_record_t *prec = NULL;
HASH_FIND_XID(gs_xid_records, &xid, prec);
if (!prec) {
log_error("XRC: %s:%d %s(): Can't find XID %#010lx we want to delete.",
file, line, func, xid);
return;
}
HASH_DEL(gs_xid_records, prec);
free(prec);
}
/**
* @brief Report about issues found in the XID allocation table.
*/
void xrc_report_xid(void) {
for (xrc_xid_record_t *prec = gs_xid_records; prec; prec = prec->hh.next)
log_trace("XRC: %s:%d %s(): %#010lx (%s) not freed.\n", prec->file,
prec->line, prec->func, prec->xid, prec->type);
}
/**
* @brief Clear the XID allocation table.
*/
void xrc_clear_xid(void) {
xrc_xid_record_t *prec = NULL, *ptmp = NULL;
HASH_ITER(hh, gs_xid_records, prec, ptmp) {
HASH_DEL(gs_xid_records, prec);
free(prec);
}
}
+62
View File
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2014 Richard Grenville <pyxlcy@gmail.com>
#pragma once
#include "common.h"
#include "uthash.h"
typedef struct {
XID xid;
const char *type;
const char *file;
const char *func;
int line;
UT_hash_handle hh;
} xrc_xid_record_t;
#define M_POS_DATA_PARAMS const char *file, int line, const char *func
#define M_POS_DATA_PASSTHROUGH file, line, func
#define M_POS_DATA __FILE__, __LINE__, __func__
void xrc_add_xid_(XID xid, const char *type, M_POS_DATA_PARAMS);
#define xrc_add_xid(xid, type) xrc_add_xid_(xid, type, M_POS_DATA)
void xrc_delete_xid_(XID xid, M_POS_DATA_PARAMS);
#define xrc_delete_xid(xid) xrc_delete_xid_(xid, M_POS_DATA)
void xrc_report_xid(void);
void xrc_clear_xid(void);
// Pixmap
static inline void xcb_create_pixmap_(xcb_connection_t *c, uint8_t depth,
xcb_pixmap_t pixmap, xcb_drawable_t drawable,
uint16_t width, uint16_t height, M_POS_DATA_PARAMS) {
xcb_create_pixmap(c, depth, pixmap, drawable, width, height);
xrc_add_xid_(pixmap, "Pixmap", M_POS_DATA_PASSTHROUGH);
}
#define xcb_create_pixmap(c, depth, pixmap, drawable, width, height) \
xcb_create_pixmap_(c, depth, pixmap, drawable, width, height, M_POS_DATA)
static inline xcb_void_cookie_t
xcb_composite_name_window_pixmap_(xcb_connection_t *c, xcb_window_t window,
xcb_pixmap_t pixmap, M_POS_DATA_PARAMS) {
xcb_void_cookie_t ret = xcb_composite_name_window_pixmap(c, window, pixmap);
xrc_add_xid_(pixmap, "PixmapC", M_POS_DATA_PASSTHROUGH);
return ret;
}
#define xcb_composite_name_window_pixmap(dpy, window, pixmap) \
xcb_composite_name_window_pixmap_(dpy, window, pixmap, M_POS_DATA)
static inline void
xcb_free_pixmap_(xcb_connection_t *c, xcb_pixmap_t pixmap, M_POS_DATA_PARAMS) {
xcb_free_pixmap(c, pixmap);
xrc_delete_xid_(pixmap, M_POS_DATA_PASSTHROUGH);
}
#define xcb_free_pixmap(c, pixmap) xcb_free_pixmap_(c, pixmap, M_POS_DATA);