dwm picom added
This commit is contained in:
1 parent
471b6062af
commit
964ff4b2c7
132 files changed
+32922
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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(®ion);
|
||||
if (buffer_age == -1 || buffer_age > ps->ndamage) {
|
||||
pixman_region32_copy(®ion, &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(®ion, ®ion, &ps->damage_ring[curr]);
|
||||
}
|
||||
pixman_region32_intersect(®ion, ®ion, &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(®_bound_local);
|
||||
pixman_region32_copy(®_bound_local, reg_bound);
|
||||
pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y);
|
||||
|
||||
pixman_region32_init(®_visible_local);
|
||||
pixman_region32_intersect(®_visible_local, reg_visible, reg_paint);
|
||||
pixman_region32_translate(®_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(®_visible_local, ®_visible_local,
|
||||
®_bound_local);
|
||||
pixman_region32_fini(®_bound_local);
|
||||
}
|
||||
|
||||
auto new_img = ps->backend_data->ops->clone_image(ps->backend_data, win_image,
|
||||
®_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,
|
||||
®_frame, ®_visible_local,
|
||||
(double[]){w->frame_opacity});
|
||||
pixman_region32_fini(®_frame);
|
||||
ps->backend_data->ops->compose(ps->backend_data, new_img, w->g.x, w->g.y,
|
||||
w->g.x + w->widthb, w->g.y + w->heightb,
|
||||
reg_paint_in_bound, reg_visible);
|
||||
ps->backend_data->ops->release_image(ps->backend_data, new_img);
|
||||
pixman_region32_fini(®_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(®_damage);
|
||||
pixman_region32_copy(®_damage, &ps->screen_reg);
|
||||
}
|
||||
|
||||
if (!pixman_region32_not_empty(®_damage)) {
|
||||
pixman_region32_fini(®_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(®_damage, blur_width * resize_factor,
|
||||
blur_height * resize_factor);
|
||||
reg_paint = resize_region(®_damage, blur_width * resize_factor,
|
||||
blur_height * resize_factor);
|
||||
pixman_region32_intersect(®_paint, ®_paint, &ps->screen_reg);
|
||||
pixman_region32_intersect(®_damage, ®_damage, &ps->screen_reg);
|
||||
} else {
|
||||
pixman_region32_init(®_paint);
|
||||
pixman_region32_copy(®_paint, ®_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(®_visible);
|
||||
pixman_region32_copy(®_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(®_visible, ®_visible, t->reg_ignore);
|
||||
}
|
||||
|
||||
// Region on screen we don't want any shadows on
|
||||
region_t reg_shadow_clip;
|
||||
pixman_region32_init(®_shadow_clip);
|
||||
|
||||
if (ps->backend_data->ops->prepare) {
|
||||
ps->backend_data->ops->prepare(ps->backend_data, ®_paint);
|
||||
}
|
||||
|
||||
if (ps->root_image) {
|
||||
ps->backend_data->ops->compose(ps->backend_data, ps->root_image, 0, 0,
|
||||
ps->root_width, ps->root_height,
|
||||
®_paint, ®_visible);
|
||||
} else {
|
||||
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
|
||||
®_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(®_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(®_paint_in_bound);
|
||||
pixman_region32_intersect(®_paint_in_bound, ®_bound, ®_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(®_paint_in_bound,
|
||||
®_paint_in_bound, ®_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,
|
||||
®_paint_in_bound, ®_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(®_blur, w->g.x, w->g.y);
|
||||
// make sure reg_blur \in reg_paint
|
||||
pixman_region32_intersect(®_blur, ®_blur, ®_paint);
|
||||
if (ps->o.transparent_clipping) {
|
||||
// ref: <transparent-clipping-note>
|
||||
pixman_region32_intersect(®_blur, ®_blur,
|
||||
®_visible);
|
||||
}
|
||||
ps->backend_data->ops->blur(ps->backend_data, blur_opacity,
|
||||
ps->backend_blur_context,
|
||||
®_blur, ®_visible);
|
||||
pixman_region32_fini(®_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(®_shadow, ®_shadow, ®_paint);
|
||||
if (!ps->o.wintype_option[w->window_type].full_shadow) {
|
||||
pixman_region32_subtract(®_shadow, ®_shadow,
|
||||
®_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(®_shadow, ®_shadow,
|
||||
&ps->shadow_exclude_reg);
|
||||
}
|
||||
if (pixman_region32_not_empty(®_shadow_clip)) {
|
||||
pixman_region32_subtract(®_shadow, ®_shadow,
|
||||
®_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(
|
||||
®_shadow, ®_shadow,
|
||||
&ps->xinerama_scr_regs[w->xinerama_scr]);
|
||||
}
|
||||
|
||||
if (ps->o.transparent_clipping) {
|
||||
// ref: <transparent-clipping-note>
|
||||
pixman_region32_intersect(®_shadow, ®_shadow,
|
||||
®_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, ®_shadow,
|
||||
®_visible);
|
||||
pixman_region32_fini(®_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(®_shadow_clip, ®_shadow_clip, ®_bound);
|
||||
} else {
|
||||
// Remove overlapping window bounds from shadow-clip region
|
||||
pixman_region32_subtract(®_shadow_clip, ®_shadow_clip, ®_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,
|
||||
®_paint_in_bound, ®_visible);
|
||||
} else {
|
||||
process_window_for_painting(
|
||||
ps, w, w->win_image, 1.0, ®_bound, ®_visible,
|
||||
®_paint, ®_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,
|
||||
®_bound, ®_visible,
|
||||
®_paint, ®_paint_in_bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
skip:
|
||||
pixman_region32_fini(®_bound);
|
||||
pixman_region32_fini(®_bound_no_corner);
|
||||
pixman_region32_fini(®_paint_in_bound);
|
||||
}
|
||||
pixman_region32_fini(®_paint);
|
||||
pixman_region32_fini(®_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, ®_damage_debug);
|
||||
pixman_region32_fini(®_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, ®_damage);
|
||||
}
|
||||
|
||||
pixman_region32_fini(®_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 :
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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(®);
|
||||
pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible);
|
||||
x_set_picture_clip_region(xd->base.c, result, 0, 0, ®);
|
||||
#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
|
||||
{
|
||||
const xcb_render_transform_t transform = {
|
||||
DOUBLE_TO_XFIXED((double)img->ewidth / (double)tmpew), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0),
|
||||
DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED((double)img->eheight / (double)tmpeh), DOUBLE_TO_XFIXED(0.0),
|
||||
DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(1.0),
|
||||
};
|
||||
xcb_render_set_picture_transform(xd->base.c, inner->pict, transform);
|
||||
xcb_render_set_picture_filter(xd->base.c, inner->pict, 7, "nearest", 0, NULL);
|
||||
}
|
||||
#undef DOUBLE_TO_XFIXED
|
||||
|
||||
if (img->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), ®);
|
||||
// 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(®);
|
||||
}
|
||||
|
||||
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(®_op);
|
||||
pixman_region32_intersect(®_op, (region_t *)reg_blur, (region_t *)reg_visible);
|
||||
if (!pixman_region32_not_empty(®_op)) {
|
||||
pixman_region32_fini(®_op);
|
||||
return true;
|
||||
}
|
||||
|
||||
region_t reg_op_resized =
|
||||
resize_region(®_op, bctx->resize_width, bctx->resize_height);
|
||||
|
||||
const pixman_box32_t *extent_resized = pixman_region32_extents(®_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(®_op);
|
||||
pixman_region32_fini(®_op_resized);
|
||||
return false;
|
||||
}
|
||||
|
||||
region_t clip;
|
||||
pixman_region32_init(&clip);
|
||||
pixman_region32_copy(&clip, ®_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, ®_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, ®_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, ®_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(®_op);
|
||||
pixman_region32_fini(®_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(®);
|
||||
pixman_region32_intersect(®, (region_t *)reg_op, (region_t *)reg_visible);
|
||||
|
||||
switch (op) {
|
||||
case IMAGE_OP_APPLY_ALPHA:
|
||||
assert(reg_op);
|
||||
|
||||
if (!pixman_region32_not_empty(®)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (dargs[0] == 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!decouple_image(base, img, reg_visible)) {
|
||||
pixman_region32_fini(®);
|
||||
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, ®);
|
||||
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(®);
|
||||
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
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 :
|
||||
@@ -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);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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 :
|
||||
@@ -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 :
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(®ion, rects, nrects);
|
||||
add_damage(ps, ®ion);
|
||||
pixman_region32_fini(®ion);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 *);
|
||||
@@ -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 :
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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))
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
@@ -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 *);
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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 }
|
||||
@@ -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)
|
||||
@@ -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 :
|
||||
@@ -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 :
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
Reference in new issue
Block a user