diff options
Diffstat (limited to 'src')
65 files changed, 0 insertions, 26522 deletions
diff --git a/src/atom.c b/src/atom.c deleted file mode 100644 index 0272dc8..0000000 --- a/src/atom.c +++ /dev/null @@ -1,37 +0,0 @@ -#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; -} diff --git a/src/atom.h b/src/atom.h deleted file mode 100644 index baf3360..0000000 --- a/src/atom.h +++ /dev/null @@ -1,68 +0,0 @@ -#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); -} diff --git a/src/backend/backend.c b/src/backend/backend.c deleted file mode 100644 index b0e562a..0000000 --- a/src/backend/backend.c +++ /dev/null @@ -1,494 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#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: %ld", 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); -} - -/// paint all windows -void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { - 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); - - // 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); - } - - // 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); - } - - 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 { - if (is_animating && w->old_win_image) { - assert(w->old_win_image); - - bool resizing = - w->g.width != w->pending_g.width || - w->g.height != w->pending_g.height; - - // Only animate opacity here if we are resizing - // a transparent window - process_window_for_painting(ps, w, w->win_image, - 1, - ®_bound, ®_visible, - ®_paint, ®_paint_in_bound); - - // Only do this if size changes as otherwise moving - // transparent windows will flicker and if you just - // move so slightly they will keep flickering - if (resizing) { - process_window_for_painting(ps, w, w->old_win_image, - 1.0 - w->animation_progress, - ®_bound, ®_visible, - ®_paint, ®_paint_in_bound); - } - - } else { - process_window_for_painting(ps, w, w->win_image, - 1, - ®_bound, ®_visible, - ®_paint, ®_paint_in_bound); - } - } - skip: - pixman_region32_fini(®_bound); - 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 : diff --git a/src/backend/backend.h b/src/backend/backend.h deleted file mode 100644 index ae107d3..0000000 --- a/src/backend/backend.h +++ /dev/null @@ -1,290 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018, Yuxuan Shui <[email protected]> - -#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 *); - -// When image properties are actually applied to the image, they are applied in a -// particular order: -// -// Color inversion -> Dimming -> Opacity multiply -> Limit maximum brightness -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, -}; - -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); - - /** - * Read the color of the pixel at given position of the given image. Image - * properties have no effect. - * - * @param backend_data backend_data - * @param image_data an image data structure previously returned by the - * backend. the image to read pixel from. - * @param x, y coordinate of the pixel to read - * @param[out] color the color of the pixel - * @return whether the operation is successful - */ - bool (*read_pixel)(backend_t *backend_data, void *image_data, int x, int y, - struct color *output); - - /// Create another instance of the `image_data`. All `image_op` and - /// `set_image_property` calls on the returned image should not affect the - /// original image - 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); -}; - -extern struct backend_operations *backend_list[]; - -void paint_all_new(session_t *ps, struct managed_win *const t, bool ignore_damage) - attr_nonnull(1); diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c deleted file mode 100644 index c0377d3..0000000 --- a/src/backend/backend_common.c +++ /dev/null @@ -1,480 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#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 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); - } - } - 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; - 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; - 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; - 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; - 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_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; 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; - 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; -} diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h deleted file mode 100644 index 5c9c806..0000000 --- a/src/backend/backend_common.h +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#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; - // Effective size of the image - int ewidth, eheight; - bool color_inverted; -}; - -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); diff --git a/src/backend/driver.c b/src/backend/driver.c deleted file mode 100644 index a41d2fd..0000000 --- a/src/backend/driver.c +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#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; -} diff --git a/src/backend/driver.h b/src/backend/driver.h deleted file mode 100644 index a37cda3..0000000 --- a/src/backend/driver.h +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> - -#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"); -} diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c deleted file mode 100644 index a057b97..0000000 --- a/src/backend/dummy/dummy.c +++ /dev/null @@ -1,174 +0,0 @@ -#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, - -}; diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c deleted file mode 100644 index 8cc5a05..0000000 --- a/src/backend/gl/gl_common.c +++ /dev/null @@ -1,1922 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#include <GL/gl.h> -#include <GL/glext.h> -#include <locale.h> -#include <stdbool.h> -#include <stdio.h> -#include <string.h> -#include <xcb/render.h> // for xcb_render_fixed_t, XXX - -#include "backend/backend.h" -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "kernel.h" -#include "log.h" -#include "region.h" -#include "string_utils.h" -#include "types.h" -#include "utils.h" - -#include "backend/backend_common.h" -#include "backend/gl/gl_common.h" - -#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__ -#define QUOTE(...) #__VA_ARGS__ - -static const GLuint vert_coord_loc = 0; -static const GLuint vert_in_texcoord_loc = 1; - -struct gl_blur_context { - enum blur_method method; - gl_blur_shader_t *blur_shader; - - /// Temporary textures used for blurring - GLuint *blur_textures; - int blur_texture_count; - /// Temporary fbos used for blurring - GLuint *blur_fbos; - int blur_fbo_count; - - /// Cached dimensions of each blur_texture. They are the same size as the target, - /// so they are always big enough without resizing. - /// Turns out calling glTexImage to resize is expensive, so we avoid that. - struct texture_size { - int width; - int height; - } * texture_sizes; - - /// Cached dimensions of the offscreen framebuffer. It's the same size as the - /// target but is expanded in either direction by resize_width / resize_height. - int fb_width, fb_height; - - /// How much do we need to resize the damaged region for blurring. - int resize_width, resize_height; - - int npasses; -}; - -static GLint glGetUniformLocationChecked(GLuint p, const char *name) { - auto ret = glGetUniformLocation(p, name); - if (ret < 0) { - log_info("Failed to get location of uniform '%s'. This is normal when " - "using custom shaders.", - name); - } - return ret; -} - -GLuint gl_create_shader(GLenum shader_type, const char *shader_str) { - log_trace("===\n%s\n===", shader_str); - - bool success = false; - GLuint shader = glCreateShader(shader_type); - if (!shader) { - log_error("Failed to create shader with type %#x.", shader_type); - goto end; - } - glShaderSource(shader, 1, &shader_str, NULL); - glCompileShader(shader); - - // Get shader status - { - GLint status = GL_FALSE; - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (GL_FALSE == status) { - GLint log_len = 0; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); - if (log_len) { - char log[log_len + 1]; - glGetShaderInfoLog(shader, log_len, NULL, log); - log_error("Failed to compile shader with type %d: %s", - shader_type, log); - } - goto end; - } - } - - success = true; - -end: - if (shader && !success) { - glDeleteShader(shader); - shader = 0; - } - - return shader; -} - -GLuint gl_create_program(const GLuint *const shaders, int nshaders) { - bool success = false; - GLuint program = glCreateProgram(); - if (!program) { - log_error("Failed to create program."); - goto end; - } - - for (int i = 0; i < nshaders; ++i) - glAttachShader(program, shaders[i]); - glLinkProgram(program); - - // Get program status - { - GLint status = GL_FALSE; - glGetProgramiv(program, GL_LINK_STATUS, &status); - if (GL_FALSE == status) { - GLint log_len = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); - if (log_len) { - char log[log_len + 1]; - glGetProgramInfoLog(program, log_len, NULL, log); - log_error("Failed to link program: %s", log); - } - goto end; - } - } - success = true; - -end: - if (program) { - for (int i = 0; i < nshaders; ++i) - glDetachShader(program, shaders[i]); - } - if (program && !success) { - glDeleteProgram(program); - program = 0; - } - - return program; -} - -/** - * @brief Create a program from vertex and fragment shader strings. - */ -GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) { - GLuint vert_shader = 0; - GLuint frag_shader = 0; - GLuint prog = 0; - - if (vert_shader_str) - vert_shader = gl_create_shader(GL_VERTEX_SHADER, vert_shader_str); - if (frag_shader_str) - frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, frag_shader_str); - - { - GLuint shaders[2]; - int count = 0; - if (vert_shader) { - shaders[count++] = vert_shader; - } - if (frag_shader) { - shaders[count++] = frag_shader; - } - if (count) { - prog = gl_create_program(shaders, count); - } - } - - if (vert_shader) - glDeleteShader(vert_shader); - if (frag_shader) - glDeleteShader(frag_shader); - - return prog; -} - -static void gl_free_prog_main(gl_win_shader_t *pprogram) { - if (!pprogram) - return; - if (pprogram->prog) { - glDeleteProgram(pprogram->prog); - pprogram->prog = 0; - } -} - -/* - * @brief Implements recursive part of gl_average_texture_color. - * - * @note In order to reduce number of textures which needs to be - * allocated and deleted during this recursive render - * we reuse the same two textures for render source and - * destination simply by alterating between them. - * Unfortunately on first iteration source_texture might - * be read-only. In this case we will select auxiliary_texture as - * destination_texture in order not to touch that read-only source - * texture in following render iteration. - * Otherwise we simply will switch source and destination textures - * between each other on each render iteration. - */ -static GLuint -_gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destination_texture, - GLuint auxiliary_texture, GLuint fbo, int width, int height) { - const int max_width = 1; - const int max_height = 1; - const int from_width = next_power_of_two(width); - const int from_height = next_power_of_two(height); - const int to_width = from_width > max_width ? from_width / 2 : from_width; - const int to_height = from_height > max_height ? from_height / 2 : from_height; - - // Prepare coordinates - GLint coord[] = { - // top left - 0, 0, // vertex coord - 0, 0, // texture coord - - // top right - to_width, 0, // vertex coord - width, 0, // texture coord - - // bottom right - to_width, to_height, // vertex coord - width, height, // texture coord - - // bottom left - 0, to_height, // vertex coord - 0, height, // texture coord - }; - glBufferSubData(GL_ARRAY_BUFFER, 0, (long)sizeof(*coord) * 16, coord); - - // Prepare framebuffer for new render iteration - glBindTexture(GL_TEXTURE_2D, destination_texture); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - destination_texture, 0); - gl_check_fb_complete(GL_FRAMEBUFFER); - - // Bind source texture as downscaling shader uniform input - glBindTexture(GL_TEXTURE_2D, source_texture); - - // Render into framebuffer - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); - - // Have we downscaled enough? - GLuint result; - if (to_width > max_width || to_height > max_height) { - GLuint new_source_texture = destination_texture; - GLuint new_destination_texture = - auxiliary_texture != 0 ? auxiliary_texture : source_texture; - result = _gl_average_texture_color(base, new_source_texture, - new_destination_texture, 0, fbo, - to_width, to_height); - } else { - result = destination_texture; - } - - return result; -} - -/* - * @brief Builds a 1x1 texture which has color corresponding to the average of all - * pixels of img by recursively rendering into texture of quorter the size (half - * width and half height). - * Returned texture must not be deleted, since it's owned by the gl_image. It will be - * deleted when the gl_image is released. - */ -static GLuint gl_average_texture_color(backend_t *base, struct backend_image *img) { - auto gd = (struct gl_data *)base; - auto inner = (struct gl_texture *)img->inner; - - // Prepare textures which will be used for destination and source of rendering - // during downscaling. - const int texture_count = ARR_SIZE(inner->auxiliary_texture); - if (!inner->auxiliary_texture[0]) { - assert(!inner->auxiliary_texture[1]); - glGenTextures(texture_count, inner->auxiliary_texture); - glActiveTexture(GL_TEXTURE0); - for (int i = 0; i < texture_count; i++) { - glBindTexture(GL_TEXTURE_2D, inner->auxiliary_texture[i]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, - (GLint[]){0, 0, 0, 0}); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, inner->width, - inner->height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); - } - } - - // Prepare framebuffer used for rendering and bind it - GLuint fbo; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - // Enable shaders - glUseProgram(gd->brightness_shader.prog); - glUniform2f(glGetUniformLocationChecked(gd->brightness_shader.prog, "texsize"), - (GLfloat)inner->width, (GLfloat)inner->height); - - // Prepare vertex attributes - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - GLuint bo[2]; - glGenBuffers(2, bo); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - // Allocate buffers for render input - GLint coord[16] = {0}; - GLuint indices[] = {0, 1, 2, 2, 3, 0}; - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_DYNAMIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, - GL_STATIC_DRAW); - - // Do actual recursive render to 1x1 texture - GLuint result_texture = _gl_average_texture_color( - base, inner->texture, inner->auxiliary_texture[0], - inner->auxiliary_texture[1], fbo, inner->width, inner->height); - - // Cleanup vertex attributes - glDisableVertexAttribArray(vert_coord_loc); - glDisableVertexAttribArray(vert_in_texcoord_loc); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(2, bo); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - - // Cleanup shaders - glUseProgram(0); - - // Cleanup framebuffers - glDeleteFramebuffers(1, &fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDrawBuffer(GL_BACK); - - // Cleanup render textures - glBindTexture(GL_TEXTURE_2D, 0); - - gl_check_err(); - - return result_texture; -} - -/** - * Render a region with texture data. - * - * @param ptex the texture - * @param target the framebuffer to render into - * @param dst_x,dst_y the top left corner of region where this texture - * should go. In OpenGL coordinate system (important!). - * @param reg_tgt the clip region, in Xorg coordinate system - * @param reg_visible ignored - */ -static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target, - GLint *coord, GLuint *indices, int nrects) { - auto gd = (struct gl_data *)base; - auto inner = (struct gl_texture *)img->inner; - if (!img || !inner->texture) { - log_error("Missing texture."); - return; - } - - GLuint brightness = 0; - if (img->max_brightness < 1.0) { - brightness = gl_average_texture_color(base, img); - } - - assert(gd->win_shader.prog); - glUseProgram(gd->win_shader.prog); - if (gd->win_shader.unifm_opacity >= 0) { - glUniform1f(gd->win_shader.unifm_opacity, (float)img->opacity); - } - if (gd->win_shader.unifm_invert_color >= 0) { - glUniform1i(gd->win_shader.unifm_invert_color, img->color_inverted); - } - if (gd->win_shader.unifm_tex >= 0) { - glUniform1i(gd->win_shader.unifm_tex, 0); - } - if (gd->win_shader.unifm_dim >= 0) { - glUniform1f(gd->win_shader.unifm_dim, (float)img->dim); - } - if (gd->win_shader.unifm_brightness >= 0) { - glUniform1i(gd->win_shader.unifm_brightness, 1); - } - if (gd->win_shader.unifm_max_brightness >= 0) { - glUniform1f(gd->win_shader.unifm_max_brightness, (float)img->max_brightness); - } - - // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", - // x, y, width, height, dx, dy, ptex->width, ptex->height, z); - - // Bind texture - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, brightness); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, inner->texture); - - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint bo[2]; - glGenBuffers(2, bo); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, - indices, GL_STATIC_DRAW); - - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); - glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - - glDisableVertexAttribArray(vert_coord_loc); - glDisableVertexAttribArray(vert_in_texcoord_loc); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - - // Cleanup - glBindTexture(GL_TEXTURE_2D, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDrawBuffer(GL_BACK); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(2, bo); - - glUseProgram(0); - - gl_check_err(); - - return; -} - -/// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates -/// @param[in] nrects, rects rectangles -/// @param[in] dst_x, dst_y origin of the OpenGL texture, affect the calculated texture -/// coordinates -/// @param[in] texture_height height of the OpenGL texture -/// @param[in] root_height height of the back buffer -/// @param[in] y_inverted whether the texture is y inverted -/// @param[out] coord, indices output -static void -x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int texture_height, - int root_height, bool y_inverted, GLint *coord, GLuint *indices) { - dst_y = root_height - dst_y; - if (y_inverted) { - dst_y -= texture_height; - } - - for (int i = 0; i < nrects; i++) { - // Y-flip. Note after this, crect.y1 > crect.y2 - rect_t crect = rects[i]; - crect.y1 = root_height - crect.y1; - crect.y2 = root_height - crect.y2; - - // Calculate texture coordinates - // (texture_x1, texture_y1), texture coord for the _bottom left_ corner - GLint texture_x1 = crect.x1 - dst_x, texture_y1 = crect.y2 - dst_y, - texture_x2 = texture_x1 + (crect.x2 - crect.x1), - texture_y2 = texture_y1 + (crect.y1 - crect.y2); - - // X pixmaps might be Y inverted, invert the texture coordinates - if (y_inverted) { - texture_y1 = texture_height - texture_y1; - texture_y2 = texture_height - texture_y2; - } - - // Vertex coordinates - auto vx1 = crect.x1; - auto vy1 = crect.y2; - auto vx2 = crect.x2; - auto vy2 = crect.y1; - - // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", - // ri, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); - - memcpy(&coord[i * 16], - ((GLint[][2]){ - {vx1, vy1}, - {texture_x1, texture_y1}, - {vx2, vy1}, - {texture_x2, texture_y1}, - {vx2, vy2}, - {texture_x2, texture_y2}, - {vx1, vy2}, - {texture_x1, texture_y2}, - }), - sizeof(GLint[2]) * 8); - - GLuint u = (GLuint)(i * 4); - memcpy(&indices[i * 6], - ((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}), - sizeof(GLuint) * 6); - } -} - -// TODO(yshui) make use of reg_visible -void gl_compose(backend_t *base, void *image_data, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_tgt, const region_t *reg_visible attr_unused) { - auto gd = (struct gl_data *)base; - struct backend_image *img = image_data; - auto inner = (struct gl_texture *)img->inner; - - // Painting - int nrects; - const rect_t *rects; - rects = pixman_region32_rectangles((region_t *)reg_tgt, &nrects); - if (!nrects) { - // Nothing to paint - return; - } - - // Until we start to use glClipControl, reg_tgt, dst_x and dst_y and - // in a different coordinate system than the one OpenGL uses. - // OpenGL window coordinate (or NDC) has the origin at the lower left of the - // screen, with y axis pointing up; Xorg has the origin at the upper left of the - // screen, with y axis pointing down. We have to do some coordinate conversion in - // this function - - auto coord = ccalloc(nrects * 16, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - x_rect_to_coords(nrects, rects, dst_x1, dst_y1, inner->height, gd->height, - inner->y_inverted, coord, indices); - - // Interpolate the texture coordinates into the specified range - for (unsigned int i = 2; i < 16; i+=4) { - coord[i+0] = lerp_range(0, dst_x2 - dst_x1, 0, inner->width, coord[i+0]); - coord[i+1] = lerp_range(0, dst_y2 - dst_y1, 0, inner->height, coord[i+1]); - } - - _gl_compose(base, img, gd->back_fbo, coord, indices, nrects); - - free(indices); - free(coord); -} - -/** - * Blur contents in a particular region. - */ -bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, - const GLuint vao[2], const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - int dst_y_fb_coord = bctx->fb_height - extent->y2; - - int curr = 0; - for (int i = 0; i < bctx->npasses; ++i) { - const gl_blur_shader_t *p = &bctx->blur_shader[i]; - assert(p->prog); - - assert(bctx->blur_textures[curr]); - - // The origin to use when sampling from the source texture - GLint texorig_x = extent->x1, texorig_y = dst_y_fb_coord; - GLint tex_width, tex_height; - GLuint src_texture; - - if (i == 0) { - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; - } else { - src_texture = bctx->blur_textures[curr]; - auto src_size = bctx->texture_sizes[curr]; - tex_width = src_size.width; - tex_height = src_size.height; - } - - glBindTexture(GL_TEXTURE_2D, src_texture); - glUseProgram(p->prog); - glUniform2f(p->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - - // The number of indices in the selected vertex array - GLsizei nelems; - - if (i < bctx->npasses - 1) { - assert(bctx->blur_fbos[0]); - assert(bctx->blur_textures[!curr]); - - // not last pass, draw into framebuffer, with resized regions - glBindVertexArray(vao[1]); - nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[0]); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, bctx->blur_textures[!curr], 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - return false; - } - - glUniform1f(p->unifm_opacity, 1.0); - } else { - // last pass, draw directly into the back buffer, with origin - // regions - glBindVertexArray(vao[0]); - nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - - glUniform1f(p->unifm_opacity, (float)opacity); - } - - glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - - // XXX use multiple draw calls is probably going to be slow than - // just simply blur the whole area. - - curr = !curr; - } - - return true; -} - -bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, - const GLuint vao[2], const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - int dst_y_fb_coord = bctx->fb_height - extent->y2; - - int iterations = bctx->blur_texture_count; - int scale_factor = 1; - - // Kawase downsample pass - const gl_blur_shader_t *down_pass = &bctx->blur_shader[0]; - assert(down_pass->prog); - glUseProgram(down_pass->prog); - - glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); - - for (int i = 0; i < iterations; ++i) { - // Scale output width / height by half in each iteration - scale_factor <<= 1; - - GLuint src_texture; - int tex_width, tex_height; - - if (i == 0) { - // first pass: copy from back buffer - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; - } else { - // copy from previous pass - src_texture = bctx->blur_textures[i - 1]; - auto src_size = bctx->texture_sizes[i - 1]; - tex_width = src_size.width; - tex_height = src_size.height; - } - - assert(src_texture); - assert(bctx->blur_fbos[i]); - - glBindTexture(GL_TEXTURE_2D, src_texture); - glBindVertexArray(vao[1]); - auto nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor); - - glUniform2f(down_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - } - - // Kawase upsample pass - const gl_blur_shader_t *up_pass = &bctx->blur_shader[1]; - assert(up_pass->prog); - glUseProgram(up_pass->prog); - - glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); - - for (int i = iterations - 1; i >= 0; --i) { - // Scale output width / height back by two in each iteration - scale_factor >>= 1; - - const GLuint src_texture = bctx->blur_textures[i]; - assert(src_texture); - - // Calculate normalized half-width/-height of a src pixel - auto src_size = bctx->texture_sizes[i]; - int tex_width = src_size.width; - int tex_height = src_size.height; - - // The number of indices in the selected vertex array - GLsizei nelems; - - glBindTexture(GL_TEXTURE_2D, src_texture); - if (i > 0) { - assert(bctx->blur_fbos[i - 1]); - - // not last pass, draw into next framebuffer - glBindVertexArray(vao[1]); - nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glUniform1f(up_pass->unifm_opacity, (GLfloat)1); - } else { - // last pass, draw directly into the back buffer - glBindVertexArray(vao[0]); - nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - - glUniform1f(up_pass->unifm_opacity, (GLfloat)opacity); - } - - glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor); - glUniform2f(up_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - } - - return true; -} - -bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blur, - const region_t *reg_visible attr_unused) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - bool ret = false; - - if (gd->width != bctx->fb_width || gd->height != bctx->fb_height) { - // Resize the temporary textures used for blur in case the root - // size changed - bctx->fb_width = gd->width; - bctx->fb_height = gd->height; - - for (int i = 0; i < bctx->blur_texture_count; ++i) { - auto tex_size = bctx->texture_sizes + i; - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - // Use smaller textures for each iteration (quarter of the - // previous texture) - tex_size->width = 1 + ((bctx->fb_width - 1) >> (i + 1)); - tex_size->height = 1 + ((bctx->fb_height - 1) >> (i + 1)); - } else { - tex_size->width = bctx->fb_width; - tex_size->height = bctx->fb_height; - } - - glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width, - tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); - - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - // Attach texture to FBO target - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - bctx->blur_textures[i], 0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - return false; - } - } - } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - } - - // Remainder: regions are in Xorg coordinates - auto reg_blur_resized = - resize_region(reg_blur, bctx->resize_width, bctx->resize_height); - const rect_t *extent = pixman_region32_extents((region_t *)reg_blur), - *extent_resized = pixman_region32_extents(®_blur_resized); - int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1; - if (width == 0 || height == 0) { - return true; - } - - int nrects, nrects_resized; - const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects), - *rects_resized = - pixman_region32_rectangles(®_blur_resized, &nrects_resized); - if (!nrects || !nrects_resized) { - return true; - } - - auto coord = ccalloc(nrects * 16, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - x_rect_to_coords(nrects, rects, extent_resized->x1, extent_resized->y2, - bctx->fb_height, gd->height, false, coord, indices); - - auto coord_resized = ccalloc(nrects_resized * 16, GLint); - auto indices_resized = ccalloc(nrects_resized * 6, GLuint); - x_rect_to_coords(nrects_resized, rects_resized, extent_resized->x1, - extent_resized->y2, bctx->fb_height, bctx->fb_height, false, - coord_resized, indices_resized); - pixman_region32_fini(®_blur_resized); - - GLuint vao[2]; - glGenVertexArrays(2, vao); - GLuint bo[4]; - glGenBuffers(4, bo); - - glBindVertexArray(vao[0]); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, - indices, GL_STATIC_DRAW); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - glBindVertexArray(vao[1]); - glBindBuffer(GL_ARRAY_BUFFER, bo[2]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16, - coord_resized, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized, - GL_STATIC_DRAW); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - int vao_nelems[2] = {nrects * 6, nrects_resized * 6}; - - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, vao, vao_nelems); - } else { - ret = gl_kernel_blur(base, opacity, ctx, extent_resized, vao, vao_nelems); - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(4, bo); - glBindVertexArray(0); - glDeleteVertexArrays(2, vao); - glUseProgram(0); - - free(indices); - free(coord); - free(indices_resized); - free(coord_resized); - - gl_check_err(); - return ret; -} - -// clang-format off -const char *vertex_shader = GLSL(330, - uniform mat4 projection; - uniform float scale = 1.0; - uniform vec2 texorig; - layout(location = 0) in vec2 coord; - layout(location = 1) in vec2 in_texcoord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(coord, 0, scale); - texcoord = in_texcoord + texorig; - } -); -// clang-format on - -/** - * Load a GLSL main program from shader strings. - */ -static int gl_win_shader_from_string(const char *vshader_str, const char *fshader_str, - gl_win_shader_t *ret) { - // Build program - ret->prog = gl_create_program_from_str(vshader_str, fshader_str); - if (!ret->prog) { - log_error("Failed to create GLSL program."); - return -1; - } - - // Get uniform addresses - ret->unifm_opacity = glGetUniformLocationChecked(ret->prog, "opacity"); - ret->unifm_invert_color = glGetUniformLocationChecked(ret->prog, "invert_color"); - ret->unifm_tex = glGetUniformLocationChecked(ret->prog, "tex"); - ret->unifm_dim = glGetUniformLocationChecked(ret->prog, "dim"); - ret->unifm_brightness = glGetUniformLocationChecked(ret->prog, "brightness"); - ret->unifm_max_brightness = - glGetUniformLocationChecked(ret->prog, "max_brightness"); - - gl_check_err(); - - return true; -} - -/** - * Callback to run on root window size change. - */ -void gl_resize(struct gl_data *gd, int width, int height) { - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - - gd->height = height; - gd->width = width; - - assert(viewport_dimensions[0] >= gd->width); - assert(viewport_dimensions[1] >= gd->height); - - glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_BGR, - GL_UNSIGNED_BYTE, NULL); - - gl_check_err(); -} - -// clang-format off -static const char dummy_frag[] = GLSL(330, - uniform sampler2D tex; - in vec2 texcoord; - void main() { - gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); - } -); - -static const char fill_frag[] = GLSL(330, - uniform vec4 color; - void main() { - gl_FragColor = color; - } -); - -static const char fill_vert[] = GLSL(330, - layout(location = 0) in vec2 in_coord; - uniform mat4 projection; - void main() { - gl_Position = projection * vec4(in_coord, 0, 1); - } -); - -static const char interpolating_frag[] = GLSL(330, - uniform sampler2D tex; - in vec2 texcoord; - void main() { - gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); - } -); - -static const char interpolating_vert[] = GLSL(330, - uniform mat4 projection; - uniform vec2 texsize; - layout(location = 0) in vec2 in_coord; - layout(location = 1) in vec2 in_texcoord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(in_coord, 0, 1); - texcoord = in_texcoord / texsize; - } -); -// clang-format on - -/// Fill a given region in bound framebuffer. -/// @param[in] y_inverted whether the y coordinates in `clip` should be inverted -static void _gl_fill(backend_t *base, struct color c, const region_t *clip, GLuint target, - int height, bool y_inverted) { - static const GLuint fill_vert_in_coord_loc = 0; - int nrects; - const rect_t *rect = pixman_region32_rectangles((region_t *)clip, &nrects); - auto gd = (struct gl_data *)base; - - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint bo[2]; - glGenBuffers(2, bo); - glUseProgram(gd->fill_shader.prog); - glUniform4f(gd->fill_shader.color_loc, (GLfloat)c.red, (GLfloat)c.green, - (GLfloat)c.blue, (GLfloat)c.alpha); - glEnableVertexAttribArray(fill_vert_in_coord_loc); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - - auto coord = ccalloc(nrects * 8, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - for (int i = 0; i < nrects; i++) { - GLint y1 = y_inverted ? height - rect[i].y2 : rect[i].y1, - y2 = y_inverted ? height - rect[i].y1 : rect[i].y2; - // clang-format off - memcpy(&coord[i * 8], - ((GLint[][2]){ - {rect[i].x1, y1}, {rect[i].x2, y1}, - {rect[i].x2, y2}, {rect[i].x1, y2}}), - sizeof(GLint[2]) * 4); - // clang-format on - indices[i * 6 + 0] = (GLuint)i * 4 + 0; - indices[i * 6 + 1] = (GLuint)i * 4 + 1; - indices[i * 6 + 2] = (GLuint)i * 4 + 2; - indices[i * 6 + 3] = (GLuint)i * 4 + 2; - indices[i * 6 + 4] = (GLuint)i * 4 + 3; - indices[i * 6 + 5] = (GLuint)i * 4 + 0; - } - glBufferData(GL_ARRAY_BUFFER, nrects * 8 * (long)sizeof(*coord), coord, GL_STREAM_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, nrects * 6 * (long)sizeof(*indices), - indices, GL_STREAM_DRAW); - - glVertexAttribPointer(fill_vert_in_coord_loc, 2, GL_INT, GL_FALSE, - sizeof(*coord) * 2, (void *)0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); - glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDisableVertexAttribArray(fill_vert_in_coord_loc); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - - glDeleteBuffers(2, bo); - free(indices); - free(coord); - - gl_check_err(); -} - -void gl_fill(backend_t *base, struct color c, const region_t *clip) { - auto gd = (struct gl_data *)base; - return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true); -} - -static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) { - auto gd = (struct gl_data *)base; - gd->release_user_data(base, inner); - assert(inner->user_data == NULL); - - glDeleteTextures(1, &inner->texture); - glDeleteTextures(2, inner->auxiliary_texture); - free(inner); - gl_check_err(); -} - -void gl_release_image(backend_t *base, void *image_data) { - struct backend_image *wd = image_data; - auto inner = (struct gl_texture *)wd->inner; - inner->refcount--; - assert(inner->refcount >= 0); - if (inner->refcount == 0) { - gl_release_image_inner(base, inner); - } - free(wd); -} - -static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { - if (shader->prog) { - glDeleteProgram(shader->prog); - } - - shader->prog = 0; -} - -void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) { - auto bctx = (struct gl_blur_context *)ctx; - // Free GLSL shaders/programs - for (int i = 0; i < bctx->npasses; ++i) { - gl_free_blur_shader(&bctx->blur_shader[i]); - } - free(bctx->blur_shader); - - if (bctx->blur_texture_count && bctx->blur_textures) { - glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures); - free(bctx->blur_textures); - } - if (bctx->blur_texture_count && bctx->texture_sizes) { - free(bctx->texture_sizes); - } - if (bctx->blur_fbo_count && bctx->blur_fbos) { - glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos); - free(bctx->blur_fbos); - } - - bctx->blur_texture_count = 0; - bctx->blur_fbo_count = 0; - - free(bctx); - - gl_check_err(); -} - -/** - * Initialize GL blur filters. - */ -bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, - enum blur_method method, void *args) { - bool success = false; - auto ctx = (struct gl_blur_context *)blur_context; - - struct conv **kernels; - - int nkernels; - ctx->method = BLUR_METHOD_KERNEL; - if (method == BLUR_METHOD_KERNEL) { - nkernels = ((struct kernel_blur_args *)args)->kernel_count; - kernels = ((struct kernel_blur_args *)args)->kernels; - } else { - kernels = generate_blur_kernel(method, args, &nkernels); - } - - if (!nkernels) { - ctx->method = BLUR_METHOD_NONE; - return true; - } - - // Specify required textures and FBOs - ctx->blur_texture_count = 2; - ctx->blur_fbo_count = 1; - - ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t); - - char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); - // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane - // Thanks to hiciu for reporting. - setlocale(LC_NUMERIC, "C"); - - // clang-format off - static const char *FRAG_SHADER_BLUR = GLSL(330, - %s\n // other extension pragmas - uniform sampler2D tex_src; - uniform vec2 pixel_norm; - uniform float opacity; - in vec2 texcoord; - out vec4 out_color; - void main() { - vec2 uv = texcoord * pixel_norm; - vec4 sum = vec4(0.0, 0.0, 0.0, 0.0); - %s //body of the convolution - out_color = sum / float(%.7g) * opacity; - } - ); - static const char *FRAG_SHADER_BLUR_ADD = QUOTE( - sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g)); - ); - // clang-format on - - const char *shader_add = FRAG_SHADER_BLUR_ADD; - char *extension = strdup(""); - - for (int i = 0; i < nkernels; i++) { - auto kern = kernels[i]; - // Build shader - int width = kern->w, height = kern->h; - int nele = width * height; - // '%.7g' is at most 14 characters, inserted 3 times - size_t body_len = (strlen(shader_add) + 42) * (uint)nele; - char *shader_body = ccalloc(body_len, char); - char *pc = shader_body; - - // Make use of the linear interpolation hardware by sampling 2 pixels with - // one texture access by sampling between both pixels based on their - // relative weight. Easiest done in a single dimension as 2D bilinear - // filtering would raise additional constraints on the kernels. Therefore - // only use interpolation along the larger dimension. - double sum = 0.0; - if (width > height) { - // use interpolation in x dimension (width) - for (int j = 0; j < height; ++j) { - for (int k = 0; k < width; k += 2) { - double val1, val2; - val1 = kern->data[j * width + k]; - val2 = (k + 1 < width) - ? kern->data[j * width + k + 1] - : 0; - - double combined_weight = val1 + val2; - if (combined_weight == 0) { - continue; - } - sum += combined_weight; - - double offset_x = - k + (val2 / combined_weight) - (width / 2); - double offset_y = j - (height / 2); - pc += snprintf( - pc, body_len - (ulong)(pc - shader_body), - shader_add, combined_weight, offset_x, offset_y); - assert(pc < shader_body + body_len); - } - } - } else { - // use interpolation in y dimension (height) - for (int j = 0; j < height; j += 2) { - for (int k = 0; k < width; ++k) { - double val1, val2; - val1 = kern->data[j * width + k]; - val2 = (j + 1 < height) - ? kern->data[(j + 1) * width + k] - : 0; - - double combined_weight = val1 + val2; - if (combined_weight == 0) { - continue; - } - sum += combined_weight; - - double offset_x = k - (width / 2); - double offset_y = - j + (val2 / combined_weight) - (height / 2); - pc += snprintf( - pc, body_len - (ulong)(pc - shader_body), - shader_add, combined_weight, offset_x, offset_y); - assert(pc < shader_body + body_len); - } - } - } - - auto pass = ctx->blur_shader + i; - size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) + - strlen(shader_body) + 10 /* sum */ + - 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR, - extension, shader_body, sum); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - free(shader_body); - - // Build program - pass->prog = gl_create_program_from_str(vertex_shader, shader_str); - free(shader_str); - if (!pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(pass->prog, 0, "out_color"); - - // Get uniform addresses - pass->unifm_pixel_norm = - glGetUniformLocationChecked(pass->prog, "pixel_norm"); - pass->unifm_opacity = glGetUniformLocationChecked(pass->prog, "opacity"); - pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - - ctx->resize_width += kern->w / 2; - ctx->resize_height += kern->h / 2; - } - - if (nkernels == 1) { - // Generate an extra null pass so we don't need special code path for - // the single pass case - auto pass = &ctx->blur_shader[1]; - pass->prog = gl_create_program_from_str(vertex_shader, dummy_frag); - pass->unifm_pixel_norm = -1; - pass->unifm_opacity = -1; - pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - - ctx->npasses = 2; - } else { - ctx->npasses = nkernels; - } - - success = true; -out: - if (method != BLUR_METHOD_KERNEL) { - // We generated the blur kernels, so we need to free them - for (int i = 0; i < nkernels; i++) { - free(kernels[i]); - } - free(kernels); - } - - free(extension); - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); - - return success; -} - -bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection, - enum blur_method method, void *args) { - bool success = false; - auto ctx = (struct gl_blur_context *)blur_context; - - ctx->method = method; - - auto blur_params = generate_dual_kawase_params(args); - - // Specify required textures and FBOs - ctx->blur_texture_count = blur_params->iterations; - ctx->blur_fbo_count = blur_params->iterations; - - ctx->resize_width += blur_params->expand; - ctx->resize_height += blur_params->expand; - - ctx->npasses = 2; - ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t); - - char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); - // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane - // Thanks to hiciu for reporting. - setlocale(LC_NUMERIC, "C"); - - // Dual-kawase downsample shader / program - auto down_pass = ctx->blur_shader; - { - // clang-format off - static const char *FRAG_SHADER_DOWN = GLSL(330, - uniform sampler2D tex_src; - uniform float scale = 1.0; - uniform vec2 pixel_norm; - in vec2 texcoord; - out vec4 out_color; - void main() { - vec2 offset = %.7g * pixel_norm; - vec2 uv = texcoord * pixel_norm * (2.0 / scale); - vec4 sum = texture2D(tex_src, uv) * 4.0; - sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset); - sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset); - out_color = sum / 8.0; - } - ); - // clang-format on - - // Build shader - size_t shader_len = - strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = - snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - - // Build program - down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); - free(shader_str); - if (!down_pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(down_pass->prog, 0, "out_color"); - - // Get uniform addresses - down_pass->unifm_pixel_norm = - glGetUniformLocationChecked(down_pass->prog, "pixel_norm"); - down_pass->texorig_loc = - glGetUniformLocationChecked(down_pass->prog, "texorig"); - down_pass->scale_loc = - glGetUniformLocationChecked(down_pass->prog, "scale"); - - // Setup projection matrix - glUseProgram(down_pass->prog); - int pml = glGetUniformLocationChecked(down_pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - } - - // Dual-kawase upsample shader / program - auto up_pass = ctx->blur_shader + 1; - { - // clang-format off - static const char *FRAG_SHADER_UP = GLSL(330, - uniform sampler2D tex_src; - uniform float scale = 1.0; - uniform vec2 pixel_norm; - uniform float opacity; - in vec2 texcoord; - out vec4 out_color; - void main() { - vec2 offset = %.7g * pixel_norm; - vec2 uv = texcoord * pixel_norm / (2 * scale); - vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset); - sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0; - sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0; - sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0; - sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset); - sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0; - out_color = sum / 12.0 * opacity; - } - ); - // clang-format on - - // Build shader - size_t shader_len = - strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = - snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - - // Build program - up_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); - free(shader_str); - if (!up_pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(up_pass->prog, 0, "out_color"); - - // Get uniform addresses - up_pass->unifm_pixel_norm = - glGetUniformLocationChecked(up_pass->prog, "pixel_norm"); - up_pass->unifm_opacity = - glGetUniformLocationChecked(up_pass->prog, "opacity"); - up_pass->texorig_loc = - glGetUniformLocationChecked(up_pass->prog, "texorig"); - up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale"); - - // Setup projection matrix - glUseProgram(up_pass->prog); - int pml = glGetUniformLocationChecked(up_pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - } - - success = true; -out: - free(blur_params); - - if (!success) { - ctx = NULL; - } - - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); - - return success; -} - -void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) { - bool success; - auto gd = (struct gl_data *)base; - - auto ctx = ccalloc(1, struct gl_blur_context); - - if (!method || method >= BLUR_METHOD_INVALID) { - ctx->method = BLUR_METHOD_NONE; - return ctx; - } - - // Set projection matrix to gl viewport dimensions so we can use screen - // coordinates for all vertices - // Note: OpenGL matrices are column major - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, - {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, - {0, 0, 0, 0}, - {-1, -1, 0, 1}}; - - if (method == BLUR_METHOD_DUAL_KAWASE) { - success = gl_create_dual_kawase_blur_context(ctx, projection_matrix[0], - method, args); - } else { - success = - gl_create_kernel_blur_context(ctx, projection_matrix[0], method, args); - } - if (!success || ctx->method == BLUR_METHOD_NONE) { - goto out; - } - - // Texture size will be defined by gl_blur - ctx->blur_textures = ccalloc(ctx->blur_texture_count, GLuint); - ctx->texture_sizes = ccalloc(ctx->blur_texture_count, struct texture_size); - glGenTextures(ctx->blur_texture_count, ctx->blur_textures); - - for (int i = 0; i < ctx->blur_texture_count; ++i) { - glBindTexture(GL_TEXTURE_2D, ctx->blur_textures[i]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - // Generate FBO and textures when needed - ctx->blur_fbos = ccalloc(ctx->blur_fbo_count, GLuint); - glGenFramebuffers(ctx->blur_fbo_count, ctx->blur_fbos); - - for (int i = 0; i < ctx->blur_fbo_count; ++i) { - if (!ctx->blur_fbos[i]) { - log_error("Failed to generate framebuffer objects for blur"); - success = false; - goto out; - } - } - -out: - if (!success) { - gl_destroy_blur_context(&gd->base, ctx); - ctx = NULL; - } - - gl_check_err(); - return ctx; -} - -void gl_get_blur_size(void *blur_context, int *width, int *height) { - auto ctx = (struct gl_blur_context *)blur_context; - *width = ctx->resize_width; - *height = ctx->resize_height; -} - -// clang-format off -const char *win_shader_glsl = GLSL(330, - uniform float opacity; - uniform float dim; - uniform bool invert_color; - in vec2 texcoord; - uniform sampler2D tex; - uniform sampler2D brightness; - uniform float max_brightness; - - void main() { - vec4 c = texelFetch(tex, ivec2(texcoord), 0); - if (invert_color) { - c = vec4(c.aaa - c.rgb, c.a); - } - c = vec4(c.rgb * (1.0 - dim), c.a) * opacity; - - vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb; - // Ref: https://en.wikipedia.org/wiki/Relative_luminance - float brightness = rgb_brightness.r * 0.21 + - rgb_brightness.g * 0.72 + - rgb_brightness.b * 0.07; - if (brightness > max_brightness) - c.rgb = c.rgb * (max_brightness / brightness); - - gl_FragColor = c; - } -); - -const char *present_vertex_shader = GLSL(330, - uniform mat4 projection; - layout(location = 0) in vec2 coord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(coord, 0, 1); - texcoord = coord; - } -); -// clang-format on - -bool gl_init(struct gl_data *gd, session_t *ps) { - // Initialize GLX data structure - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - - glEnable(GL_BLEND); - // X pixmap is in premultiplied alpha, so we might just as well use it too. - // Thanks to derhass for help. - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - // Initialize stencil buffer - glDisable(GL_STENCIL_TEST); - glStencilMask(0x1); - glStencilFunc(GL_EQUAL, 0x1, 0x1); - - // Set gl viewport to the maximum supported size so we won't have to worry about - // it later on when the screen is resized. The corresponding projection matrix can - // be set now and won't have to be updated. Since fragments outside the target - // buffer are skipped anyways, this should have no impact on performance. - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - glViewport(0, 0, viewport_dimensions[0], viewport_dimensions[1]); - - // Clear screen - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - glGenFramebuffers(1, &gd->back_fbo); - glGenTextures(1, &gd->back_texture); - if (!gd->back_fbo || !gd->back_texture) { - log_error("Failed to generate a framebuffer object"); - return false; - } - - glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, 0); - - // Set projection matrix to gl viewport dimensions so we can use screen - // coordinates for all vertices - // Note: OpenGL matrices are column major - GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, - {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, - {0, 0, 0, 0}, - {-1, -1, 0, 1}}; - - // Initialize shaders - gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader); - int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection"); - glUseProgram(gd->win_shader.prog); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - - gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag); - gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color"); - pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection"); - glUseProgram(gd->fill_shader.prog); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - - gd->present_prog = gl_create_program_from_str(present_vertex_shader, dummy_frag); - if (!gd->present_prog) { - log_error("Failed to create the present shader"); - return false; - } - pml = glGetUniformLocationChecked(gd->present_prog, "projection"); - glUseProgram(gd->present_prog); - glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - - gd->brightness_shader.prog = - gl_create_program_from_str(interpolating_vert, interpolating_frag); - if (!gd->brightness_shader.prog) { - log_error("Failed to create the brightness shader"); - return false; - } - pml = glGetUniformLocationChecked(gd->brightness_shader.prog, "projection"); - glUseProgram(gd->brightness_shader.prog); - glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - - // Set up the size of the back texture - gl_resize(gd, ps->root_width, ps->root_height); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - gd->back_texture, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - return false; - } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - - gd->logger = gl_string_marker_logger_new(); - if (gd->logger) { - log_add_target_tls(gd->logger); - } - - const char *vendor = (const char *)glGetString(GL_VENDOR); - log_debug("GL_VENDOR = %s", vendor); - if (strcmp(vendor, "NVIDIA Corporation") == 0) { - log_info("GL vendor is NVIDIA, don't use glFinish"); - gd->is_nvidia = true; - } else { - gd->is_nvidia = false; - } - - return true; -} - -void gl_deinit(struct gl_data *gd) { - gl_free_prog_main(&gd->win_shader); - - if (gd->logger) { - log_remove_target_tls(gd->logger); - gd->logger = NULL; - } - - gl_check_err(); -} - -GLuint gl_new_texture(GLenum target) { - GLuint texture; - glGenTextures(1, &texture); - if (!texture) { - log_error("Failed to generate texture"); - return 0; - } - - glBindTexture(target, texture); - glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT); - glBindTexture(target, 0); - - return texture; -} - -/// Actually duplicate a texture into a new one, if this texture is shared -static inline void gl_image_decouple(backend_t *base, struct backend_image *img) { - if (img->inner->refcount == 1) { - return; - } - auto gd = (struct gl_data *)base; - auto inner = (struct gl_texture *)img->inner; - auto new_tex = ccalloc(1, struct gl_texture); - - new_tex->texture = gl_new_texture(GL_TEXTURE_2D); - new_tex->y_inverted = true; - new_tex->height = inner->height; - new_tex->width = inner->width; - new_tex->refcount = 1; - new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data); - - glBindTexture(GL_TEXTURE_2D, new_tex->texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, new_tex->width, new_tex->height, 0, - GL_BGRA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - - assert(gd->present_prog); - glUseProgram(gd->present_prog); - glBindTexture(GL_TEXTURE_2D, inner->texture); - - GLuint fbo; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - new_tex->texture, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - gl_check_fb_complete(GL_DRAW_FRAMEBUFFER); - - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); - - // clang-format off - GLint coord[] = { - // top left - 0, 0, // vertex coord - 0, 0, // texture coord - - // top right - new_tex->width, 0, // vertex coord - new_tex->width, 0, // texture coord - - // bottom right - new_tex->width, new_tex->height, - new_tex->width, new_tex->height, - - // bottom left - 0, new_tex->height, - 0, new_tex->height, - }; - // clang-format on - GLuint indices[] = {0, 1, 2, 2, 3, 0}; - - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint bo[2]; - glGenBuffers(2, bo); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, - GL_STATIC_DRAW); - - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); - - glDisableVertexAttribArray(vert_coord_loc); - glDisableVertexAttribArray(vert_in_texcoord_loc); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(2, bo); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); - - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); - - gl_check_err(); - - img->inner = (struct backend_image_inner_base *)new_tex; - inner->refcount--; -} - -static void gl_image_apply_alpha(backend_t *base, struct backend_image *img, - const region_t *reg_op, double alpha) { - // Result color = 0 (GL_ZERO) + alpha (GL_CONSTANT_ALPHA) * original color - auto inner = (struct gl_texture *)img->inner; - glBlendFunc(GL_ZERO, GL_CONSTANT_ALPHA); - glBlendColor(0, 0, 0, (GLclampf)alpha); - GLuint fbo; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - inner->texture, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - _gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, 0, false); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); -} - -void gl_present(backend_t *base, const region_t *region) { - auto gd = (struct gl_data *)base; - - int nrects; - const rect_t *rect = pixman_region32_rectangles((region_t *)region, &nrects); - auto coord = ccalloc(nrects * 8, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - for (int i = 0; i < nrects; i++) { - // clang-format off - memcpy(&coord[i * 8], - ((GLint[]){rect[i].x1, gd->height - rect[i].y2, - rect[i].x2, gd->height - rect[i].y2, - rect[i].x2, gd->height - rect[i].y1, - rect[i].x1, gd->height - rect[i].y1}), - sizeof(GLint) * 8); - // clang-format on - - GLuint u = (GLuint)(i * 4); - memcpy(&indices[i * 6], - ((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}), - sizeof(GLuint) * 6); - } - - glUseProgram(gd->present_prog); - glBindTexture(GL_TEXTURE_2D, gd->back_texture); - - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint bo[2]; - glGenBuffers(2, bo); - glEnableVertexAttribArray(vert_coord_loc); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(GLint) * nrects * 8, coord, GL_STREAM_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(GLuint) * nrects * 6, indices, - GL_STREAM_DRAW); - - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL); - glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindVertexArray(0); - glDeleteBuffers(2, bo); - glDeleteVertexArrays(1, &vao); - - free(coord); - free(indices); -} - -bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, - const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) { - struct backend_image *tex = image_data; - switch (op) { - case IMAGE_OP_APPLY_ALPHA: - gl_image_decouple(base, tex); - assert(tex->inner->refcount == 1); - gl_image_apply_alpha(base, tex, reg_op, *(double *)arg); - break; - } - - return true; -} - -bool gl_read_pixel(backend_t *base attr_unused, void *image_data, int x, int y, - struct color *output) { - struct backend_image *tex = image_data; - auto inner = (struct gl_texture *)tex->inner; - GLfloat color[4]; - glReadPixels(x, inner->y_inverted ? inner->height - y : y, 1, 1, GL_RGBA, - GL_FLOAT, color); - output->alpha = color[3]; - output->red = color[0]; - output->green = color[1]; - output->blue = color[2]; - - bool ret = glGetError() == GL_NO_ERROR; - gl_clear_err(); - return ret; -} diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h deleted file mode 100644 index b1d93b0..0000000 --- a/src/backend/gl/gl_common.h +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#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 - -// Program and uniforms for window shader -typedef struct { - GLuint prog; - GLint unifm_opacity; - GLint unifm_invert_color; - GLint unifm_tex; - GLint unifm_dim; - GLint unifm_brightness; - GLint unifm_max_brightness; -} 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 unifm_pixel_norm; - GLint unifm_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; - // 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); - -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; -} diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c deleted file mode 100644 index 1397d19..0000000 --- a/src/backend/gl/glx.c +++ /dev/null @@ -1,648 +0,0 @@ -// 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 <[email protected]> - * 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; - } - - gd->ctx = glXCreateContextAttribsARB(ps->dpy, cfg[i], 0, true, - (int[]){ - GLX_CONTEXT_MAJOR_VERSION_ARB, - 3, - GLX_CONTEXT_MINOR_VERSION_ARB, - 3, - GLX_CONTEXT_PROFILE_MASK_ARB, - GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - 0, - }); - 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, - .read_pixel = gl_read_pixel, - .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, - .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); -#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 -} diff --git a/src/backend/gl/glx.h b/src/backend/gl/glx.h deleted file mode 100644 index 1061f0b..0000000 --- a/src/backend/gl/glx.h +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#pragma once -#include <stdbool.h> -// Older version of glx.h defines function prototypes for these extensions... -// Rename them to avoid conflicts -#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; -}; - -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); diff --git a/src/backend/meson.build b/src/backend/meson.build deleted file mode 100644 index b8f0ad9..0000000 --- a/src/backend/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -# 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 diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c deleted file mode 100644 index ccf358b..0000000 --- a/src/backend/xrender/xrender.c +++ /dev/null @@ -1,779 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#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; -}; - -static void compose_impl(struct _xrender_data *xd, const struct backend_image *img, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_paint, const region_t *reg_visible, - xcb_render_picture_t result) { - auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; - 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->color_inverted || img->dim != 0) && has_alpha) { - // 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); - } - - 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 backend_image); - auto inner = ccalloc(1, struct _xrender_image_data_inner); - inner->depth = (uint8_t)fmt.visual_depth; - inner->width = img->ewidth = r->width; - inner->height = img->eheight = r->height; - inner->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->inner = (struct backend_image_inner_base *)inner; - img->opacity = 1; - 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_image(backend_t *base, void *image) { - struct backend_image *img = image; - img->inner->refcount--; - if (img->inner->refcount == 0) { - release_image_inner(base, (void *)img->inner); - } - 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 bool -read_pixel(backend_t *backend_data, void *image_data, int x, int y, struct color *output) { - auto xd = (struct _xrender_data *)backend_data; - auto img = (struct backend_image *)image_data; - auto inner = (struct _xrender_image_data_inner *)img->inner; - - auto r = XCB_AWAIT(xcb_get_image, xd->base.c, XCB_IMAGE_FORMAT_XY_PIXMAP, inner->pixmap, - to_i16_checked(x), to_i16_checked(y), 1, 1, (uint32_t)-1L); - - if (!r) { - return false; - } - - // Color format seems to be BGRA8888, see glamor_format_for_pixmap from the - // Xserver codebase. - uint8_t *pixels = xcb_get_image_data(r); - output->blue = pixels[0] / 255.0; - output->green = pixels[1] / 255.0; - output->red = pixels[2] / 255.0; - output->alpha = pixels[3] / 255.0; - - return true; -} - -static backend_t *backend_xrender_init(session_t *ps) { - auto xd = ccalloc(1, struct _xrender_data); - init_backend_base(&xd->base, ps); - - 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; -} - -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, - .read_pixel = read_pixel, - .clone_image = default_clone_image, - .set_image_property = default_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: diff --git a/src/c2.c b/src/c2.c deleted file mode 100644 index 3500f7b..0000000 --- a/src/c2.c +++ /dev/null @@ -1,1674 +0,0 @@ -// 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 <ctype.h> -#include <fnmatch.h> -#include <stdio.h> -#include <string.h> - -// libpcre -#ifdef CONFIG_REGEX_PCRE -#include <pcre.h> - -// For compatibility with <libpcre-8.20 -#ifndef PCRE_STUDY_JIT_COMPILE -#define PCRE_STUDY_JIT_COMPILE 0 -#define LPCRE_FREE_STUDY(extra) pcre_free(extra) -#else -#define LPCRE_FREE_STUDY(extra) pcre_free_study(extra) -#endif - -#endif - -#include <X11/Xlib.h> -#include <xcb/xcb.h> - -#include "atom.h" -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "log.h" -#include "string_utils.h" -#include "utils.h" -#include "win.h" -#include "x.h" - -#include "c2.h" - -#pragma GCC diagnostic error "-Wunused-parameter" - -#define C2_MAX_LEVELS 10 - -typedef struct _c2_b c2_b_t; -typedef struct _c2_l c2_l_t; - -/// Pointer to a condition tree. -typedef struct { - bool isbranch : 1; - union { - c2_b_t *b; - c2_l_t *l; - }; -} c2_ptr_t; - -/// Initializer for c2_ptr_t. -#define C2_PTR_INIT \ - { .isbranch = false, .l = NULL, } - -static const c2_ptr_t C2_PTR_NULL = C2_PTR_INIT; - -/// Operator of a branch element. -typedef enum { - C2_B_OUNDEFINED, - C2_B_OAND, - C2_B_OOR, - C2_B_OXOR, -} c2_b_op_t; - -/// Structure for branch element in a window condition -struct _c2_b { - bool neg : 1; - c2_b_op_t op; - c2_ptr_t opr1; - c2_ptr_t opr2; -}; - -/// Initializer for c2_b_t. -#define C2_B_INIT \ - { .neg = false, .op = C2_B_OUNDEFINED, .opr1 = C2_PTR_INIT, .opr2 = C2_PTR_INIT, } - -/// Structure for leaf element in a window condition -struct _c2_l { - bool neg : 1; - enum { C2_L_OEXISTS, - C2_L_OEQ, - C2_L_OGT, - C2_L_OGTEQ, - C2_L_OLT, - C2_L_OLTEQ, - } op : 3; - enum { C2_L_MEXACT, - C2_L_MSTART, - C2_L_MCONTAINS, - C2_L_MWILDCARD, - C2_L_MPCRE, - } match : 3; - bool match_ignorecase : 1; - char *tgt; - xcb_atom_t tgtatom; - bool tgt_onframe; - int index; - enum { C2_L_PUNDEFINED = -1, - C2_L_PID = 0, - C2_L_PX, - C2_L_PY, - C2_L_PX2, - C2_L_PY2, - C2_L_PWIDTH, - C2_L_PHEIGHT, - C2_L_PWIDTHB, - C2_L_PHEIGHTB, - C2_L_PBDW, - C2_L_PFULLSCREEN, - C2_L_POVREDIR, - C2_L_PARGB, - C2_L_PFOCUSED, - C2_L_PWMWIN, - C2_L_PBSHAPED, - C2_L_PROUNDED, - C2_L_PCLIENT, - C2_L_PWINDOWTYPE, - C2_L_PLEADER, - C2_L_PNAME, - C2_L_PCLASSG, - C2_L_PCLASSI, - C2_L_PROLE, - } predef; - enum c2_l_type { - C2_L_TUNDEFINED, - C2_L_TSTRING, - C2_L_TCARDINAL, - C2_L_TWINDOW, - C2_L_TATOM, - C2_L_TDRAWABLE, - } type; - int format; - enum { C2_L_PTUNDEFINED, - C2_L_PTSTRING, - C2_L_PTINT, - } ptntype; - char *ptnstr; - long ptnint; -#ifdef CONFIG_REGEX_PCRE - pcre *regex_pcre; - pcre_extra *regex_pcre_extra; -#endif -}; - -/// Initializer for c2_l_t. -#define C2_L_INIT \ - { \ - .neg = false, .op = C2_L_OEXISTS, .match = C2_L_MEXACT, \ - .match_ignorecase = false, .tgt = NULL, .tgtatom = 0, .tgt_onframe = false, \ - .predef = C2_L_PUNDEFINED, .index = 0, .type = C2_L_TUNDEFINED, \ - .format = 0, .ptntype = C2_L_PTUNDEFINED, .ptnstr = NULL, .ptnint = 0, \ - } - -static const c2_l_t leaf_def = C2_L_INIT; - -/// Linked list type of conditions. -struct _c2_lptr { - c2_ptr_t ptr; - void *data; - struct _c2_lptr *next; -}; - -/// Initializer for c2_lptr_t. -#define C2_LPTR_INIT \ - { .ptr = C2_PTR_INIT, .data = NULL, .next = NULL, } - -/// Structure representing a predefined target. -typedef struct { - const char *name; - enum c2_l_type type; - int format; -} c2_predef_t; - -// Predefined targets. -static const c2_predef_t C2_PREDEFS[] = { - [C2_L_PID] = {"id", C2_L_TCARDINAL, 0}, - [C2_L_PX] = {"x", C2_L_TCARDINAL, 0}, - [C2_L_PY] = {"y", C2_L_TCARDINAL, 0}, - [C2_L_PX2] = {"x2", C2_L_TCARDINAL, 0}, - [C2_L_PY2] = {"y2", C2_L_TCARDINAL, 0}, - [C2_L_PWIDTH] = {"width", C2_L_TCARDINAL, 0}, - [C2_L_PHEIGHT] = {"height", C2_L_TCARDINAL, 0}, - [C2_L_PWIDTHB] = {"widthb", C2_L_TCARDINAL, 0}, - [C2_L_PHEIGHTB] = {"heightb", C2_L_TCARDINAL, 0}, - [C2_L_PBDW] = {"border_width", C2_L_TCARDINAL, 0}, - [C2_L_PFULLSCREEN] = {"fullscreen", C2_L_TCARDINAL, 0}, - [C2_L_POVREDIR] = {"override_redirect", C2_L_TCARDINAL, 0}, - [C2_L_PARGB] = {"argb", C2_L_TCARDINAL, 0}, - [C2_L_PFOCUSED] = {"focused", C2_L_TCARDINAL, 0}, - [C2_L_PWMWIN] = {"wmwin", C2_L_TCARDINAL, 0}, - [C2_L_PBSHAPED] = {"bounding_shaped", C2_L_TCARDINAL, 0}, - [C2_L_PROUNDED] = {"rounded_corners", C2_L_TCARDINAL, 0}, - [C2_L_PCLIENT] = {"client", C2_L_TWINDOW, 0}, - [C2_L_PWINDOWTYPE] = {"window_type", C2_L_TSTRING, 0}, - [C2_L_PLEADER] = {"leader", C2_L_TWINDOW, 0}, - [C2_L_PNAME] = {"name", C2_L_TSTRING, 0}, - [C2_L_PCLASSG] = {"class_g", C2_L_TSTRING, 0}, - [C2_L_PCLASSI] = {"class_i", C2_L_TSTRING, 0}, - [C2_L_PROLE] = {"role", C2_L_TSTRING, 0}, -}; - -/** - * Get the numeric property value from a win_prop_t. - */ -static inline long winprop_get_int(winprop_t prop, size_t index) { - long tgt = 0; - - if (!prop.nitems || index >= prop.nitems) { - return 0; - } - - switch (prop.format) { - case 8: tgt = *(prop.p8 + index); break; - case 16: tgt = *(prop.p16 + index); break; - case 32: tgt = *(prop.p32 + index); break; - default: assert(0); break; - } - - return tgt; -} - -/** - * Compare next word in a string with another string. - */ -static inline int strcmp_wd(const char *needle, const char *src) { - int ret = mstrncmp(needle, src); - if (ret) - return ret; - - char c = src[strlen(needle)]; - if (isalnum((unsigned char)c) || '_' == c) - return 1; - else - return 0; -} - -/** - * Return whether a c2_ptr_t is empty. - */ -static inline attr_unused bool c2_ptr_isempty(const c2_ptr_t p) { - return !(p.isbranch ? (bool)p.b : (bool)p.l); -} - -/** - * Reset a c2_ptr_t. - */ -static inline void c2_ptr_reset(c2_ptr_t *pp) { - if (pp) - memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t)); -} - -/** - * Combine two condition trees. - */ -static inline c2_ptr_t c2h_comb_tree(c2_b_op_t op, c2_ptr_t p1, c2_ptr_t p2) { - c2_ptr_t p = {.isbranch = true, .b = NULL}; - p.b = cmalloc(c2_b_t); - - p.b->neg = false; - p.b->op = op; - p.b->opr1 = p1; - p.b->opr2 = p2; - - return p; -} - -/** - * Get the precedence value of a condition branch operator. - */ -static inline int c2h_b_opp(c2_b_op_t op) { - switch (op) { - case C2_B_OAND: return 2; - case C2_B_OOR: return 1; - case C2_B_OXOR: return 1; - default: break; - } - - assert(0); - return 0; -} - -/** - * Compare precedence of two condition branch operators. - * - * Associativity is left-to-right, forever. - * - * @return positive number if op1 > op2, 0 if op1 == op2 in precedence, - * negative number otherwise - */ -static inline int c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) { - return c2h_b_opp(op1) - c2h_b_opp(op2); -} - -static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level); - -static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult); - -static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult); - -static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult); - -static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult); - -static void c2_free(c2_ptr_t p); - -/** - * Wrapper of c2_free(). - */ -static inline void c2_freep(c2_ptr_t *pp) { - if (pp) { - c2_free(*pp); - c2_ptr_reset(pp); - } -} - -static const char *c2h_dump_str_tgt(const c2_l_t *pleaf); - -static const char *c2h_dump_str_type(const c2_l_t *pleaf); - -static void attr_unused c2_dump(c2_ptr_t p); - -static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf); - -static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond); - -/** - * Parse a condition string. - */ -c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) { - if (!pattern) - return NULL; - - // Parse the pattern - c2_ptr_t result = C2_PTR_INIT; - int offset = -1; - - if (strlen(pattern) >= 2 && ':' == pattern[1]) - offset = c2_parse_legacy(pattern, 0, &result); - else - offset = c2_parse_grp(pattern, 0, &result, 0); - - if (offset < 0) { - c2_freep(&result); - return NULL; - } - - // Insert to pcondlst - { - static const c2_lptr_t lptr_def = C2_LPTR_INIT; - auto plptr = cmalloc(c2_lptr_t); - memcpy(plptr, &lptr_def, sizeof(c2_lptr_t)); - plptr->ptr = result; - plptr->data = data; - if (pcondlst) { - plptr->next = *pcondlst; - *pcondlst = plptr; - } - -#ifdef DEBUG_C2 - log_trace("(\"%s\"): ", pattern); - c2_dump(plptr->ptr); - putchar('\n'); -#endif - - return plptr; - } -} - -#define c2_error(format, ...) \ - do { \ - log_error("Pattern \"%s\" pos %d: " format, pattern, offset, ##__VA_ARGS__); \ - goto fail; \ - } while (0) - -// TODO(yshui) Not a very good macro, should probably be a function -#define C2H_SKIP_SPACES() \ - { \ - while (isspace((unsigned char)pattern[offset])) \ - ++offset; \ - } - -/** - * Parse a group in condition string. - * - * @return offset of next character in string - */ -static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level) { - // Check for recursion levels - if (level > C2_MAX_LEVELS) - c2_error("Exceeded maximum recursion levels."); - - if (!pattern) - return -1; - - // Expected end character - const char endchar = (offset ? ')' : '\0'); - - // We use a system that a maximum of 2 elements are kept. When we find - // the third element, we combine the elements according to operator - // precedence. This design limits operators to have at most two-levels - // of precedence and fixed left-to-right associativity. - - // For storing branch operators. ops[0] is actually unused - c2_b_op_t ops[3] = {}; - // For storing elements - c2_ptr_t eles[2] = {C2_PTR_INIT, C2_PTR_INIT}; - // Index of next free element slot in eles - int elei = 0; - // Pointer to the position of next element - c2_ptr_t *pele = eles; - // Negation flag of next operator - bool neg = false; - // Whether we are expecting an element immediately, is true at first, or - // after encountering a logical operator - bool next_expected = true; - - // Parse the pattern character-by-character - for (; pattern[offset]; ++offset) { - assert(elei <= 2); - - // Jump over spaces - if (isspace((unsigned char)pattern[offset])) - continue; - - // Handle end of group - if (')' == pattern[offset]) - break; - - // Handle "!" - if ('!' == pattern[offset]) { - if (!next_expected) - c2_error("Unexpected \"!\"."); - - neg = !neg; - continue; - } - - // Handle AND and OR - if ('&' == pattern[offset] || '|' == pattern[offset]) { - if (next_expected) - c2_error("Unexpected logical operator."); - - next_expected = true; - if (!mstrncmp("&&", pattern + offset)) { - ops[elei] = C2_B_OAND; - ++offset; - } else if (!mstrncmp("||", pattern + offset)) { - ops[elei] = C2_B_OOR; - ++offset; - } else - c2_error("Illegal logical operator."); - - continue; - } - - // Parsing an element - if (!next_expected) - c2_error("Unexpected expression."); - - assert(!elei || ops[elei]); - - // If we are out of space - if (2 == elei) { - --elei; - // If the first operator has higher or equal precedence, combine - // the first two elements - if (c2h_b_opcmp(ops[1], ops[2]) >= 0) { - eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]); - c2_ptr_reset(&eles[1]); - pele = &eles[elei]; - ops[1] = ops[2]; - } - // Otherwise, combine the second and the incoming one - else { - eles[1] = c2h_comb_tree(ops[2], eles[1], C2_PTR_NULL); - assert(eles[1].isbranch); - pele = &eles[1].b->opr2; - } - // The last operator always needs to be reset - ops[2] = C2_B_OUNDEFINED; - } - - // It's a subgroup if it starts with '(' - if ('(' == pattern[offset]) { - if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0) - goto fail; - } - // Otherwise it's a leaf - else { - if ((offset = c2_parse_target(pattern, offset, pele)) < 0) - goto fail; - - assert(!pele->isbranch && !c2_ptr_isempty(*pele)); - - if ((offset = c2_parse_op(pattern, offset, pele)) < 0) - goto fail; - - if ((offset = c2_parse_pattern(pattern, offset, pele)) < 0) - goto fail; - } - // Decrement offset -- we will increment it in loop update - --offset; - - // Apply negation - if (neg) { - neg = false; - if (pele->isbranch) - pele->b->neg = !pele->b->neg; - else - pele->l->neg = !pele->l->neg; - } - - next_expected = false; - ++elei; - pele = &eles[elei]; - } - - // Wrong end character? - if (pattern[offset] && !endchar) - c2_error("Expected end of string but found '%c'.", pattern[offset]); - if (!pattern[offset] && endchar) - c2_error("Expected '%c' but found end of string.", endchar); - - // Handle end of group - if (!elei) { - c2_error("Empty group."); - } else if (next_expected) { - c2_error("Missing rule before end of group."); - } else if (elei > 1) { - assert(2 == elei); - assert(ops[1]); - eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]); - c2_ptr_reset(&eles[1]); - } - - *presult = eles[0]; - - if (')' == pattern[offset]) - ++offset; - - return offset; - -fail: - c2_freep(&eles[0]); - c2_freep(&eles[1]); - - return -1; -} - -/** - * Parse the target part of a rule. - */ -static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { - // Initialize leaf - presult->isbranch = false; - presult->l = cmalloc(c2_l_t); - - c2_l_t *const pleaf = presult->l; - memcpy(pleaf, &leaf_def, sizeof(c2_l_t)); - - // Parse negation marks - while ('!' == pattern[offset]) { - pleaf->neg = !pleaf->neg; - ++offset; - C2H_SKIP_SPACES(); - } - - // Copy target name out - int tgtlen = 0; - for (; pattern[offset] && - (isalnum((unsigned char)pattern[offset]) || '_' == pattern[offset]); - ++offset) { - ++tgtlen; - } - if (!tgtlen) { - c2_error("Empty target."); - } - pleaf->tgt = strndup(&pattern[offset - tgtlen], (size_t)tgtlen); - - // Check for predefined targets - static const int npredefs = (int)(sizeof(C2_PREDEFS) / sizeof(C2_PREDEFS[0])); - for (int i = 0; i < npredefs; ++i) { - if (!strcmp(C2_PREDEFS[i].name, pleaf->tgt)) { - pleaf->predef = i; - pleaf->type = C2_PREDEFS[i].type; - pleaf->format = C2_PREDEFS[i].format; - break; - } - } - - C2H_SKIP_SPACES(); - - // Parse target-on-frame flag - if ('@' == pattern[offset]) { - pleaf->tgt_onframe = true; - ++offset; - C2H_SKIP_SPACES(); - } - - // Parse index - if ('[' == pattern[offset]) { - if (pleaf->predef != C2_L_PUNDEFINED) { - c2_error("Predefined targets can't have index."); - } - - offset++; - - C2H_SKIP_SPACES(); - - long index = -1; - const char *endptr = NULL; - - if ('*' == pattern[offset]) { - index = -1; - endptr = pattern + offset + 1; - } else { - index = strtol(pattern + offset, (char **)&endptr, 0); - if (index < 0) { - c2_error("Index number invalid."); - } - } - - if (!endptr || pattern + offset == endptr) { - c2_error("No index number found after bracket."); - } - - pleaf->index = to_int_checked(index); - offset = to_int_checked(endptr - pattern); - - C2H_SKIP_SPACES(); - - if (pattern[offset] != ']') { - c2_error("Index end marker not found."); - } - - ++offset; - - C2H_SKIP_SPACES(); - } - - // Parse target type and format - if (':' == pattern[offset]) { - ++offset; - C2H_SKIP_SPACES(); - - // Look for format - bool hasformat = false; - long format = 0; - { - char *endptr = NULL; - format = strtol(pattern + offset, &endptr, 0); - assert(endptr); - if ((hasformat = (endptr && endptr != pattern + offset))) { - offset = to_int_checked(endptr - pattern); - } - C2H_SKIP_SPACES(); - } - - // Look for type - enum c2_l_type type = C2_L_TUNDEFINED; - switch (pattern[offset]) { - case 'w': type = C2_L_TWINDOW; break; - case 'd': type = C2_L_TDRAWABLE; break; - case 'c': type = C2_L_TCARDINAL; break; - case 's': type = C2_L_TSTRING; break; - case 'a': type = C2_L_TATOM; break; - default: c2_error("Invalid type character."); - } - - if (type) { - if (pleaf->predef != C2_L_PUNDEFINED) { - log_warn("Type specified for a default target " - "will be ignored."); - } else { - if (pleaf->type && type != pleaf->type) { - log_warn("Default type overridden on " - "target."); - } - pleaf->type = type; - } - } - - offset++; - C2H_SKIP_SPACES(); - - // Default format - if (!pleaf->format) { - switch (pleaf->type) { - case C2_L_TWINDOW: - case C2_L_TDRAWABLE: - case C2_L_TATOM: pleaf->format = 32; break; - case C2_L_TSTRING: pleaf->format = 8; break; - default: break; - } - } - - // Write format - if (hasformat) { - if (pleaf->predef != C2_L_PUNDEFINED) { - log_warn("Format \"%ld\" specified on a default target " - "will be ignored.", - format); - } else if (pleaf->type == C2_L_TSTRING) { - log_warn("Format \"%ld\" specified on a string target " - "will be ignored.", - format); - } else { - if (pleaf->format && pleaf->format != format) { - log_warn("Default format %d overridden on " - "target.", - pleaf->format); - } - pleaf->format = to_int_checked(format); - } - } - } - - if (!pleaf->type) { - c2_error("Target type cannot be determined."); - } - - // if (!pleaf->predef && !pleaf->format && C2_L_TSTRING != pleaf->type) - // c2_error("Target format cannot be determined."); - - if (pleaf->format && 8 != pleaf->format && 16 != pleaf->format && 32 != pleaf->format) { - c2_error("Invalid format."); - } - - return offset; - -fail: - return -1; -} - -/** - * Parse the operator part of a leaf. - */ -static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) { - c2_l_t *const pleaf = presult->l; - - // Parse negation marks - C2H_SKIP_SPACES(); - while ('!' == pattern[offset]) { - pleaf->neg = !pleaf->neg; - ++offset; - C2H_SKIP_SPACES(); - } - - // Parse qualifiers - if ('*' == pattern[offset] || '^' == pattern[offset] || '%' == pattern[offset] || - '~' == pattern[offset]) { - switch (pattern[offset]) { - case '*': pleaf->match = C2_L_MCONTAINS; break; - case '^': pleaf->match = C2_L_MSTART; break; - case '%': pleaf->match = C2_L_MWILDCARD; break; - case '~': pleaf->match = C2_L_MPCRE; break; - default: assert(0); - } - ++offset; - C2H_SKIP_SPACES(); - } - - // Parse flags - while ('?' == pattern[offset]) { - pleaf->match_ignorecase = true; - ++offset; - C2H_SKIP_SPACES(); - } - - // Parse operator - while ('=' == pattern[offset] || '>' == pattern[offset] || '<' == pattern[offset]) { - if ('=' == pattern[offset] && C2_L_OGT == pleaf->op) - pleaf->op = C2_L_OGTEQ; - else if ('=' == pattern[offset] && C2_L_OLT == pleaf->op) - pleaf->op = C2_L_OLTEQ; - else if (pleaf->op) { - c2_error("Duplicate operator."); - } else { - switch (pattern[offset]) { - case '=': pleaf->op = C2_L_OEQ; break; - case '>': pleaf->op = C2_L_OGT; break; - case '<': pleaf->op = C2_L_OLT; break; - default: assert(0); - } - } - ++offset; - C2H_SKIP_SPACES(); - } - - // Check for problems - if (C2_L_OEQ != pleaf->op && (pleaf->match || pleaf->match_ignorecase)) - c2_error("Exists/greater-than/less-than operators cannot have a " - "qualifier."); - - return offset; - -fail: - return -1; -} - -/** - * Parse the pattern part of a leaf. - */ -static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) { - c2_l_t *const pleaf = presult->l; - - // Exists operator cannot have pattern - if (!pleaf->op) { - return offset; - } - - C2H_SKIP_SPACES(); - - char *endptr = NULL; - if (!strcmp_wd("true", &pattern[offset])) { - pleaf->ptntype = C2_L_PTINT; - pleaf->ptnint = true; - offset += 4; // length of "true"; - } else if (!strcmp_wd("false", &pattern[offset])) { - pleaf->ptntype = C2_L_PTINT; - pleaf->ptnint = false; - offset += 5; // length of "false"; - } else if (pleaf->ptnint = strtol(pattern + offset, &endptr, 0), - pattern + offset != endptr) { - pleaf->ptntype = C2_L_PTINT; - offset = to_int_checked(endptr - pattern); - // Make sure we are stopping at the end of a word - if (isalnum((unsigned char)pattern[offset])) { - c2_error("Trailing characters after a numeric pattern."); - } - } else { - // Parse string patterns - bool raw = false; - char delim = '\0'; - - // String flags - if (tolower((unsigned char)pattern[offset]) == 'r') { - raw = true; - ++offset; - C2H_SKIP_SPACES(); - } - - // Check for delimiters - if (pattern[offset] == '\"' || pattern[offset] == '\'') { - pleaf->ptntype = C2_L_PTSTRING; - delim = pattern[offset]; - ++offset; - } - - if (pleaf->ptntype != C2_L_PTSTRING) { - c2_error("Invalid pattern type."); - } - - // Parse the string now - // We can't determine the length of the pattern, so we use the length - // to the end of the pattern string -- currently escape sequences - // cannot be converted to a string longer than itself. - auto tptnstr = ccalloc((strlen(pattern + offset) + 1), char); - char *ptptnstr = tptnstr; - pleaf->ptnstr = tptnstr; - for (; pattern[offset] && delim != pattern[offset]; ++offset) { - // Handle escape sequences if it's not a raw string - if ('\\' == pattern[offset] && !raw) { - switch (pattern[++offset]) { - case '\\': *(ptptnstr++) = '\\'; break; - case '\'': *(ptptnstr++) = '\''; break; - case '\"': *(ptptnstr++) = '\"'; break; - case 'a': *(ptptnstr++) = '\a'; break; - case 'b': *(ptptnstr++) = '\b'; break; - case 'f': *(ptptnstr++) = '\f'; break; - case 'n': *(ptptnstr++) = '\n'; break; - case 'r': *(ptptnstr++) = '\r'; break; - case 't': *(ptptnstr++) = '\t'; break; - case 'v': *(ptptnstr++) = '\v'; break; - case 'o': - case 'x': { - char *tstr = strndup(pattern + offset + 1, 2); - char *pstr = NULL; - long val = strtol( - tstr, &pstr, ('o' == pattern[offset] ? 8 : 16)); - free(tstr); - if (pstr != &tstr[2] || val <= 0) - c2_error("Invalid octal/hex escape " - "sequence."); - *(ptptnstr++) = to_char_checked(val); - offset += 2; - break; - } - default: c2_error("Invalid escape sequence."); - } - } else { - *(ptptnstr++) = pattern[offset]; - } - } - if (!pattern[offset]) - c2_error("Premature end of pattern string."); - ++offset; - *ptptnstr = '\0'; - pleaf->ptnstr = strdup(tptnstr); - free(tptnstr); - } - - C2H_SKIP_SPACES(); - - if (!pleaf->ptntype) - c2_error("Invalid pattern type."); - - // Check if the type is correct - if (!(((C2_L_TSTRING == pleaf->type || C2_L_TATOM == pleaf->type) && - C2_L_PTSTRING == pleaf->ptntype) || - ((C2_L_TCARDINAL == pleaf->type || C2_L_TWINDOW == pleaf->type || - C2_L_TDRAWABLE == pleaf->type) && - C2_L_PTINT == pleaf->ptntype))) - c2_error("Pattern type incompatible with target type."); - - if (C2_L_PTINT == pleaf->ptntype && pleaf->match) - c2_error("Integer/boolean pattern cannot have operator qualifiers."); - - if (C2_L_PTINT == pleaf->ptntype && pleaf->match_ignorecase) - c2_error("Integer/boolean pattern cannot have flags."); - - if (C2_L_PTSTRING == pleaf->ptntype && - (C2_L_OGT == pleaf->op || C2_L_OGTEQ == pleaf->op || C2_L_OLT == pleaf->op || - C2_L_OLTEQ == pleaf->op)) - c2_error("String pattern cannot have an arithmetic operator."); - - return offset; - -fail: - return -1; -} - -/** - * Parse a condition with legacy syntax. - */ -static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) { - if (strlen(pattern + offset) < 4 || pattern[offset + 1] != ':' || - !strchr(pattern + offset + 2, ':')) { - c2_error("Legacy parser: Invalid format."); - } - - // Allocate memory for new leaf - auto pleaf = cmalloc(c2_l_t); - presult->isbranch = false; - presult->l = pleaf; - memcpy(pleaf, &leaf_def, sizeof(c2_l_t)); - pleaf->type = C2_L_TSTRING; - pleaf->op = C2_L_OEQ; - pleaf->ptntype = C2_L_PTSTRING; - - // Determine the pattern target -#define TGTFILL(pdefid) \ - (pleaf->predef = pdefid, pleaf->type = C2_PREDEFS[pdefid].type, \ - pleaf->format = C2_PREDEFS[pdefid].format) - switch (pattern[offset]) { - case 'n': TGTFILL(C2_L_PNAME); break; - case 'i': TGTFILL(C2_L_PCLASSI); break; - case 'g': TGTFILL(C2_L_PCLASSG); break; - case 'r': TGTFILL(C2_L_PROLE); break; - default: c2_error("Target \"%c\" invalid.\n", pattern[offset]); - } -#undef TGTFILL - - offset += 2; - - // Determine the match type - switch (pattern[offset]) { - case 'e': pleaf->match = C2_L_MEXACT; break; - case 'a': pleaf->match = C2_L_MCONTAINS; break; - case 's': pleaf->match = C2_L_MSTART; break; - case 'w': pleaf->match = C2_L_MWILDCARD; break; - case 'p': pleaf->match = C2_L_MPCRE; break; - default: c2_error("Type \"%c\" invalid.\n", pattern[offset]); - } - ++offset; - - // Determine the pattern flags - while (':' != pattern[offset]) { - switch (pattern[offset]) { - case 'i': pleaf->match_ignorecase = true; break; - default: c2_error("Flag \"%c\" invalid.", pattern[offset]); - } - ++offset; - } - ++offset; - - // Copy the pattern - pleaf->ptnstr = strdup(pattern + offset); - - return offset; - -fail: - return -1; -} - -#undef c2_error - -/** - * Do postprocessing on a condition leaf. - */ -static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) { - // Give a pattern type to a leaf with exists operator, if needed - if (C2_L_OEXISTS == pleaf->op && !pleaf->ptntype) { - pleaf->ptntype = (C2_L_TSTRING == pleaf->type ? C2_L_PTSTRING : C2_L_PTINT); - } - - // Get target atom if it's not a predefined one - if (pleaf->predef == C2_L_PUNDEFINED) { - pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt); - if (!pleaf->tgtatom) { - log_error("Failed to get atom for target \"%s\".", pleaf->tgt); - return false; - } - } - - // Insert target Atom into atom track list - if (pleaf->tgtatom) { - bool found = false; - for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) { - if (pleaf->tgtatom == platom->atom) { - found = true; - break; - } - } - if (!found) { - auto pnew = cmalloc(latom_t); - pnew->next = ps->track_atom_lst; - pnew->atom = pleaf->tgtatom; - ps->track_atom_lst = pnew; - } - } - - // Warn about lower case characters in target name - if (pleaf->predef == C2_L_PUNDEFINED) { - for (const char *pc = pleaf->tgt; *pc; ++pc) { - if (islower((unsigned char)*pc)) { - log_warn("Lowercase character in target name \"%s\".", - pleaf->tgt); - break; - } - } - } - - // PCRE patterns - if (C2_L_PTSTRING == pleaf->ptntype && C2_L_MPCRE == pleaf->match) { -#ifdef CONFIG_REGEX_PCRE - const char *error = NULL; - int erroffset = 0; - int options = 0; - - // Ignore case flag - if (pleaf->match_ignorecase) - options |= PCRE_CASELESS; - - // Compile PCRE expression - pleaf->regex_pcre = - pcre_compile(pleaf->ptnstr, options, &error, &erroffset, NULL); - if (!pleaf->regex_pcre) { - log_error("Pattern \"%s\": PCRE regular expression parsing " - "failed on " - "offset %d: %s", - pleaf->ptnstr, erroffset, error); - return false; - } -#ifdef CONFIG_REGEX_PCRE_JIT - pleaf->regex_pcre_extra = - pcre_study(pleaf->regex_pcre, PCRE_STUDY_JIT_COMPILE, &error); - if (!pleaf->regex_pcre_extra) { - printf("Pattern \"%s\": PCRE regular expression study failed: %s", - pleaf->ptnstr, error); - } -#endif - - // Free the target string - // free(pleaf->tgt); - // pleaf->tgt = NULL; -#else - log_error("PCRE regular expression support not compiled in."); - return false; -#endif - } - - return true; -} - -static bool c2_tree_postprocess(session_t *ps, c2_ptr_t node) { - if (!node.isbranch) { - return c2_l_postprocess(ps, node.l); - } - if (!c2_tree_postprocess(ps, node.b->opr1)) - return false; - return c2_tree_postprocess(ps, node.b->opr2); -} - -bool c2_list_postprocess(session_t *ps, c2_lptr_t *list) { - c2_lptr_t *head = list; - while (head) { - if (!c2_tree_postprocess(ps, head->ptr)) - return false; - head = head->next; - } - return true; -} -/** - * Free a condition tree. - */ -static void c2_free(c2_ptr_t p) { - // For a branch element - if (p.isbranch) { - c2_b_t *const pbranch = p.b; - - if (!pbranch) - return; - - c2_free(pbranch->opr1); - c2_free(pbranch->opr2); - free(pbranch); - } - // For a leaf element - else { - c2_l_t *const pleaf = p.l; - - if (!pleaf) - return; - - free(pleaf->tgt); - free(pleaf->ptnstr); -#ifdef CONFIG_REGEX_PCRE - pcre_free(pleaf->regex_pcre); - LPCRE_FREE_STUDY(pleaf->regex_pcre_extra); -#endif - free(pleaf); - } -} - -/** - * Free a condition tree in c2_lptr_t. - */ -c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) { - if (!lp) - return NULL; - - c2_lptr_t *pnext = lp->next; - c2_free(lp->ptr); - free(lp); - - return pnext; -} - -/** - * Get a string representation of a rule target. - */ -static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) { - if (pleaf->predef != C2_L_PUNDEFINED) { - return C2_PREDEFS[pleaf->predef].name; - } else { - return pleaf->tgt; - } -} - -/** - * Get a string representation of a target. - */ -static const char *c2h_dump_str_type(const c2_l_t *pleaf) { - switch (pleaf->type) { - case C2_L_TWINDOW: return "w"; - case C2_L_TDRAWABLE: return "d"; - case C2_L_TCARDINAL: return "c"; - case C2_L_TSTRING: return "s"; - case C2_L_TATOM: return "a"; - case C2_L_TUNDEFINED: break; - } - - return NULL; -} - -/** - * Dump a condition tree. - */ -static void c2_dump(c2_ptr_t p) { - // For a branch - if (p.isbranch) { - const c2_b_t *const pbranch = p.b; - - if (!pbranch) { - return; - } - - if (pbranch->neg) { - putchar('!'); - } - - printf("("); - c2_dump(pbranch->opr1); - - switch (pbranch->op) { - case C2_B_OAND: printf(" && "); break; - case C2_B_OOR: printf(" || "); break; - case C2_B_OXOR: printf(" XOR "); break; - default: assert(0); break; - } - - c2_dump(pbranch->opr2); - printf(") "); - } - // For a leaf - else { - const c2_l_t *const pleaf = p.l; - - if (!pleaf) { - return; - } - - if (C2_L_OEXISTS == pleaf->op && pleaf->neg) { - putchar('!'); - } - - // Print target name, type, and format - { - printf("%s", c2h_dump_str_tgt(pleaf)); - if (pleaf->tgt_onframe) { - putchar('@'); - } - if (pleaf->predef == C2_L_PUNDEFINED) { - if (pleaf->index < 0) { - printf("[*]"); - } else { - printf("[%d]", pleaf->index); - } - } - printf(":%d%s", pleaf->format, c2h_dump_str_type(pleaf)); - } - - // Print operator - putchar(' '); - - if (C2_L_OEXISTS != pleaf->op && pleaf->neg) { - putchar('!'); - } - - switch (pleaf->match) { - case C2_L_MEXACT: break; - case C2_L_MCONTAINS: putchar('*'); break; - case C2_L_MSTART: putchar('^'); break; - case C2_L_MPCRE: putchar('~'); break; - case C2_L_MWILDCARD: putchar('%'); break; - } - - if (pleaf->match_ignorecase) { - putchar('?'); - } - - switch (pleaf->op) { - case C2_L_OEXISTS: break; - case C2_L_OEQ: fputs("=", stdout); break; - case C2_L_OGT: fputs(">", stdout); break; - case C2_L_OGTEQ: fputs(">=", stdout); break; - case C2_L_OLT: fputs("<", stdout); break; - case C2_L_OLTEQ: fputs("<=", stdout); break; - } - - if (C2_L_OEXISTS == pleaf->op) { - return; - } - - // Print pattern - putchar(' '); - switch (pleaf->ptntype) { - case C2_L_PTINT: printf("%ld", pleaf->ptnint); break; - case C2_L_PTSTRING: - // TODO(yshui) Escape string before printing out? - printf("\"%s\"", pleaf->ptnstr); - break; - default: assert(0); break; - } - } -} - -/** - * Get the type atom of a condition. - */ -static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) { - switch (pleaf->type) { - case C2_L_TCARDINAL: return XCB_ATOM_CARDINAL; - case C2_L_TWINDOW: return XCB_ATOM_WINDOW; - case C2_L_TSTRING: return XCB_ATOM_STRING; - case C2_L_TATOM: return XCB_ATOM_ATOM; - case C2_L_TDRAWABLE: return XCB_ATOM_DRAWABLE; - default: assert(0); break; - } - unreachable; -} - -/** - * Match a window against a single leaf window condition. - * - * For internal use. - */ -static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w, - const c2_l_t *pleaf, bool *pres, bool *perr) { - assert(pleaf); - - const xcb_window_t wid = (pleaf->tgt_onframe ? w->client_win : w->base.id); - - // Return if wid is missing - if (pleaf->predef == C2_L_PUNDEFINED && !wid) { - return; - } - - const int idx = (pleaf->index < 0 ? 0 : pleaf->index); - - switch (pleaf->ptntype) { - // Deal with integer patterns - case C2_L_PTINT: { - long *targets = NULL; - long *targets_free = NULL; - size_t ntargets = 0; - - // Get the value - // A predefined target - long predef_target = 0; - if (pleaf->predef != C2_L_PUNDEFINED) { - *perr = false; - switch (pleaf->predef) { - case C2_L_PID: predef_target = wid; break; - case C2_L_PX: predef_target = w->g.x; break; - case C2_L_PY: predef_target = w->g.y; break; - case C2_L_PX2: predef_target = w->g.x + w->widthb; break; - case C2_L_PY2: predef_target = w->g.y + w->heightb; break; - case C2_L_PWIDTH: predef_target = w->g.width; break; - case C2_L_PHEIGHT: predef_target = w->g.height; break; - case C2_L_PWIDTHB: predef_target = w->widthb; break; - case C2_L_PHEIGHTB: predef_target = w->heightb; break; - case C2_L_PBDW: predef_target = w->g.border_width; break; - case C2_L_PFULLSCREEN: - predef_target = win_is_fullscreen(ps, w); - break; - case C2_L_POVREDIR: predef_target = w->a.override_redirect; break; - case C2_L_PARGB: predef_target = win_has_alpha(w); break; - case C2_L_PFOCUSED: - predef_target = win_is_focused_raw(ps, w); - break; - case C2_L_PWMWIN: predef_target = w->wmwin; break; - case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break; - case C2_L_PROUNDED: predef_target = w->rounded_corners; break; - case C2_L_PCLIENT: predef_target = w->client_win; break; - case C2_L_PLEADER: predef_target = w->leader; break; - default: - *perr = true; - assert(0); - break; - } - ntargets = 1; - targets = &predef_target; - } - // A raw window property - else { - int word_count = 1; - if (pleaf->index < 0) { - // Get length of property in 32-bit multiples - auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom); - word_count = to_int_checked((prop_info.length + 4 - 1) / 4); - } - winprop_t prop = x_get_prop_with_offset( - ps->c, wid, pleaf->tgtatom, idx, word_count, - c2_get_atom_type(pleaf), pleaf->format); - - ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); - if (ntargets > 0) { - targets = targets_free = ccalloc(ntargets, long); - *perr = false; - for (size_t i = 0; i < ntargets; ++i) { - targets[i] = winprop_get_int(prop, i); - } - } - free_winprop(&prop); - } - - if (*perr) { - goto fail_int; - } - - // Do comparison - bool res = false; - for (size_t i = 0; i < ntargets; ++i) { - long tgt = targets[i]; - switch (pleaf->op) { - case C2_L_OEXISTS: - res = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true); - break; - case C2_L_OEQ: res = (tgt == pleaf->ptnint); break; - case C2_L_OGT: res = (tgt > pleaf->ptnint); break; - case C2_L_OGTEQ: res = (tgt >= pleaf->ptnint); break; - case C2_L_OLT: res = (tgt < pleaf->ptnint); break; - case C2_L_OLTEQ: res = (tgt <= pleaf->ptnint); break; - default: *perr = true; assert(0); - } - if (res) { - break; - } - } - *pres = res; - - fail_int: - // Free property values after usage, if necessary - if (targets_free) { - free(targets_free); - } - } break; - // String patterns - case C2_L_PTSTRING: { - const char **targets = NULL; - const char **targets_free = NULL; - const char **targets_free_inner = NULL; - size_t ntargets = 0; - - // A predefined target - const char *predef_target = NULL; - if (pleaf->predef != C2_L_PUNDEFINED) { - switch (pleaf->predef) { - case C2_L_PWINDOWTYPE: - predef_target = WINTYPES[w->window_type]; - break; - case C2_L_PNAME: predef_target = w->name; break; - case C2_L_PCLASSG: predef_target = w->class_general; break; - case C2_L_PCLASSI: predef_target = w->class_instance; break; - case C2_L_PROLE: predef_target = w->role; break; - default: assert(0); break; - } - ntargets = 1; - targets = &predef_target; - } - // An atom type property, convert it to string - else if (pleaf->type == C2_L_TATOM) { - int word_count = 1; - if (pleaf->index < 0) { - // Get length of property in 32-bit multiples - auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom); - word_count = to_int_checked((prop_info.length + 4 - 1) / 4); - } - winprop_t prop = x_get_prop_with_offset( - ps->c, wid, pleaf->tgtatom, idx, word_count, - c2_get_atom_type(pleaf), pleaf->format); - - ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); - targets = targets_free = (const char **)ccalloc(2 * ntargets, char *); - targets_free_inner = targets + ntargets; - - for (size_t i = 0; i < ntargets; ++i) { - xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop, i); - if (atom) { - xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply( - ps->c, xcb_get_atom_name(ps->c, atom), NULL); - if (reply) { - targets[i] = targets_free_inner[i] = strndup( - xcb_get_atom_name_name(reply), - (size_t)xcb_get_atom_name_name_length(reply)); - free(reply); - } - } - } - free_winprop(&prop); - } - // Not an atom type, just fetch the string list - else { - char **strlst = NULL; - int nstr = 0; - if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr)) { - if (pleaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) { - ntargets = to_u32_checked(nstr); - targets = (const char **)strlst; - } else if (nstr > idx) { - ntargets = 1; - targets = (const char **)strlst + idx; - } - } - if (strlst) { - targets_free = (const char **)strlst; - } - } - - if (ntargets == 0) { - goto fail_str; - } - for (size_t i = 0; i < ntargets; ++i) { - if (!targets[i]) { - goto fail_str; - } - } - *perr = false; - - // Actual matching - bool res = false; - for (size_t i = 0; i < ntargets; ++i) { - const char *tgt = targets[i]; - switch (pleaf->op) { - case C2_L_OEXISTS: res = true; break; - case C2_L_OEQ: - switch (pleaf->match) { - case C2_L_MEXACT: - if (pleaf->match_ignorecase) { - res = !strcasecmp(tgt, pleaf->ptnstr); - } else { - res = !strcmp(tgt, pleaf->ptnstr); - } - break; - case C2_L_MCONTAINS: - if (pleaf->match_ignorecase) { - res = strcasestr(tgt, pleaf->ptnstr); - } else { - res = strstr(tgt, pleaf->ptnstr); - } - break; - case C2_L_MSTART: - if (pleaf->match_ignorecase) { - res = !strncasecmp(tgt, pleaf->ptnstr, - strlen(pleaf->ptnstr)); - } else { - res = !strncmp(tgt, pleaf->ptnstr, - strlen(pleaf->ptnstr)); - } - break; - case C2_L_MWILDCARD: { - int flags = 0; - if (pleaf->match_ignorecase) { - flags |= FNM_CASEFOLD; - } - res = !fnmatch(pleaf->ptnstr, tgt, flags); - } break; - case C2_L_MPCRE: -#ifdef CONFIG_REGEX_PCRE - assert(strlen(tgt) <= INT_MAX); - res = (pcre_exec(pleaf->regex_pcre, - pleaf->regex_pcre_extra, tgt, - (int)strlen(tgt), 0, 0, NULL, 0) >= 0); -#else - assert(0); -#endif - break; - } - break; - default: *perr = true; assert(0); - } - if (res) { - break; - } - } - *pres = res; - - fail_str: - // Free the string after usage, if necessary - if (targets_free_inner) { - for (size_t i = 0; i < ntargets; ++i) { - if (targets_free_inner[i]) { - free((void *)targets_free_inner[i]); - } - } - } - // Free property values after usage, if necessary - if (targets_free) { - free(targets_free); - } - } break; - default: assert(0); break; - } -} - -/** - * Match a window against a single window condition. - * - * @return true if matched, false otherwise. - */ -static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond) { - bool result = false; - bool error = true; - - // Handle a branch - if (cond.isbranch) { - const c2_b_t *pb = cond.b; - - if (!pb) - return false; - - error = false; - - switch (pb->op) { - case C2_B_OAND: - result = (c2_match_once(ps, w, pb->opr1) && - c2_match_once(ps, w, pb->opr2)); - break; - case C2_B_OOR: - result = (c2_match_once(ps, w, pb->opr1) || - c2_match_once(ps, w, pb->opr2)); - break; - case C2_B_OXOR: - result = (c2_match_once(ps, w, pb->opr1) != - c2_match_once(ps, w, pb->opr2)); - break; - default: error = true; assert(0); - } - -#ifdef DEBUG_WINMATCH - log_trace("(%#010x): branch: result = %d, pattern = ", w->base.id, result); - c2_dump(cond); - putchar('\n'); -#endif - } - // Handle a leaf - else { - const c2_l_t *pleaf = cond.l; - - if (!pleaf) - return false; - - c2_match_once_leaf(ps, w, pleaf, &result, &error); - - // For EXISTS operator, no errors are fatal - if (C2_L_OEXISTS == pleaf->op && error) { - result = false; - error = false; - } - -#ifdef DEBUG_WINMATCH - log_trace("(%#010x): leaf: result = %d, error = %d, " - "client = %#010x, pattern = ", - w->base.id, result, error, w->client_win); - c2_dump(cond); - putchar('\n'); -#endif - } - - // Postprocess the result - if (error) - result = false; - - if (cond.isbranch ? cond.b->neg : cond.l->neg) - result = !result; - - return result; -} - -/** - * Match a window against a condition linked list. - * - * @param cache a place to cache the last matched condition - * @param pdata a place to return the data - * @return true if matched, false otherwise. - */ -bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, - void **pdata) { - assert(ps->server_grabbed); - // Then go through the whole linked list - for (; condlst; condlst = condlst->next) { - if (c2_match_once(ps, w, condlst->ptr)) { - if (pdata) - *pdata = condlst->data; - return true; - } - } - - return false; -} diff --git a/src/c2.h b/src/c2.h deleted file mode 100644 index d6b1d37..0000000 --- a/src/c2.h +++ /dev/null @@ -1,26 +0,0 @@ -// 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); diff --git a/src/cache.c b/src/cache.c deleted file mode 100644 index 1ffb31c..0000000 --- a/src/cache.c +++ /dev/null @@ -1,95 +0,0 @@ -#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; -} diff --git a/src/cache.h b/src/cache.h deleted file mode 100644 index 3ca054f..0000000 --- a/src/cache.h +++ /dev/null @@ -1,32 +0,0 @@ -#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); diff --git a/src/common.h b/src/common.h deleted file mode 100644 index b7f2fe0..0000000 --- a/src/common.h +++ /dev/null @@ -1,541 +0,0 @@ -// 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 <[email protected]> - * - * 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; - /// Timer for delayed drawing, right now only used by - /// swopti - ev_timer delayed_draw_timer; - /// Use an ev_idle callback for drawing - /// So we only start drawing when events are processed - ev_idle draw_idle; - /// Called everytime we have timeouts or new data on socket, - /// 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. Used if <code>--paint-on-overlay</code>. - 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 fade_time; - /// Time of last window animation step. In milliseconds. - long animation_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 === - /// Currently used refresh rate. - int refresh_rate; - /// Interval between refresh in nanoseconds. - long refresh_intv; - /// Nanosecond offset of the first painting. - long paint_tm_offset; - -#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; - } -} diff --git a/src/compiler.h b/src/compiler.h deleted file mode 100644 index f146bd2..0000000 --- a/src/compiler.h +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018 Yuxuan Shui <[email protected]> -#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); -} diff --git a/src/config.c b/src/config.c deleted file mode 100644 index d24bf64..0000000 --- a/src/config.c +++ /dev/null @@ -1,650 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2011-2013, Christopher Jeffrey -// Copyright (c) 2013 Richard Grenville <[email protected]> - -#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, "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, - - .refresh_rate = 0, - .sw_opti = false, - .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, - - .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; -} diff --git a/src/config.h b/src/config.h deleted file mode 100644 index a22d222..0000000 --- a/src/config.h +++ /dev/null @@ -1,380 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2011-2013, Christopher Jeffrey -// Copyright (c) 2013 Richard Grenville <[email protected]> -// Copyright (c) 2018 Yuxuan Shui <[email protected]> -#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_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 === - /// User-specified refresh rate. - int refresh_rate; - /// Whether to enable refresh-rate-based software optimization. - bool sw_opti; - /// VSync method to use; - bool vsync; - /// Whether to use glFinish() instead of glFlush() for (possibly) better - /// 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; - /// TODO: window animation blacklist - /// TODO: open/close animations - - // === Opacity === - /// Default opacity for inactive windows. - /// 32-bit integer with the format of _NET_WM_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_OPACITY on client windows. Used on window - /// managers that don't pass _NET_WM_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 : diff --git a/src/config_libconfig.c b/src/config_libconfig.c deleted file mode 100644 index 7d266cd..0000000 --- a/src/config_libconfig.c +++ /dev/null @@ -1,784 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2012-2014 Richard Grenville <[email protected]> - -#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) { - 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 - - // -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) - if (config_lookup_bool(&cfg, "shadow", &ival)) - *shadow_enable = ival; - // -C (no_dock_shadow) - if (config_lookup_bool(&cfg, "no-dock-shadow", &ival)) { - log_error("Option `no-dock-shadow` has been removed. Please use the " - "wintype option `shadow` of `dock` instead."); - goto err; - } - // -G (no_dnd_shadow) - if (config_lookup_bool(&cfg, "no-dnd-shadow", &ival)) { - log_error("Option `no-dnd-shadow` has been removed. Please use the " - "wintype option `shadow` of `dnd` instead."); - goto err; - }; - // -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", &opt->refresh_rate)) { - if (opt->refresh_rate < 0) { - log_warn("Invalid refresh rate %d, fallback to 0", opt->refresh_rate); - opt->refresh_rate = 0; - } - } - // --vsync - if (config_lookup_string(&cfg, "vsync", &sval)) { - opt->vsync = parse_vsync(sval); - log_warn("vsync option will take a boolean from now on. \"%s\" is " - "interpreted as \"%s\" for compatibility, but this will stop " - "working soon", - sval, opt->vsync ? "true" : "false"); - } - 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 - lcfg_lookup_bool(&cfg, "sw-opti", &opt->sw_opti); - // --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); - // --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); - 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. - opt->use_damage = true; - } - log_warn("glx-swap-method has been deprecated since v6, your setting " - "\"%s\" should be %s.", - sval, - opt->use_damage ? "replaced by `use-damage = true`" : "removed"); - } - // --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) && ival) { - log_warn("glx-use-gpushader4 is deprecated since v6, please remove it " - "from" - "your config file"); - } - // --xrender-sync - if (config_lookup_bool(&cfg, "xrender-sync", &ival) && ival) { - log_error("Please use xrender-sync-fence instead of xrender-sync."); - 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"); - if (lcfg_lookup_bool(&cfg, "paint-on-overlay", &bval)) { - log_error("\"paint-on-overlay\" has been removed as an option, and " - "the feature is enabled whenever possible"); - goto err; - } - - if (config_lookup_float(&cfg, "alpha-step", &dval)) { - log_error("\"alpha-step\" has been removed, compton now tries to make use" - " of all alpha values"); - goto err; - } - - const char *deprecation_message attr_unused = - "has been removed. If you encounter problems " - "without this feature, please feel free to open a bug report"; - - config_setting_t *blur_cfg = config_lookup(&cfg, "blur"); - if (blur_cfg) { - 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); -} diff --git a/src/dbus.c b/src/dbus.c deleted file mode 100644 index 8d6094e..0000000 --- a/src/dbus.c +++ /dev/null @@ -1,1340 +0,0 @@ -// 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 <X11/Xlib.h> -#include <ctype.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <unistd.h> -#include <xcb/xcb.h> - -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "list.h" -#include "log.h" -#include "string_utils.h" -#include "types.h" -#include "uthash_extra.h" -#include "utils.h" -#include "win.h" - -#include "dbus.h" - -struct cdbus_data { - /// DBus connection. - DBusConnection *dbus_conn; - /// DBus service name. - char *dbus_service; -}; - -// Window type -typedef uint32_t cdbus_window_t; -#define CDBUS_TYPE_WINDOW DBUS_TYPE_UINT32 -#define CDBUS_TYPE_WINDOW_STR DBUS_TYPE_UINT32_AS_STRING - -typedef uint32_t cdbus_enum_t; -#define CDBUS_TYPE_ENUM DBUS_TYPE_UINT32 -#define CDBUS_TYPE_ENUM_STR DBUS_TYPE_UINT32_AS_STRING - -#define CDBUS_SERVICE_NAME "com.github.chjj.compton" -#define CDBUS_INTERFACE_NAME CDBUS_SERVICE_NAME -#define CDBUS_OBJECT_NAME "/com/github/chjj/compton" -#define CDBUS_ERROR_PREFIX CDBUS_INTERFACE_NAME ".error" -#define CDBUS_ERROR_UNKNOWN CDBUS_ERROR_PREFIX ".unknown" -#define CDBUS_ERROR_UNKNOWN_S "Well, I don't know what happened. Do you?" -#define CDBUS_ERROR_BADMSG CDBUS_ERROR_PREFIX ".bad_message" -#define CDBUS_ERROR_BADMSG_S \ - "Unrecognized command. Beware compton " \ - "cannot make you a sandwich." -#define CDBUS_ERROR_BADARG CDBUS_ERROR_PREFIX ".bad_argument" -#define CDBUS_ERROR_BADARG_S "Failed to parse argument %d: %s" -#define CDBUS_ERROR_BADWIN CDBUS_ERROR_PREFIX ".bad_window" -#define CDBUS_ERROR_BADWIN_S "Requested window %#010x not found." -#define CDBUS_ERROR_BADTGT CDBUS_ERROR_PREFIX ".bad_target" -#define CDBUS_ERROR_BADTGT_S "Target \"%s\" not found." -#define CDBUS_ERROR_FORBIDDEN CDBUS_ERROR_PREFIX ".forbidden" -#define CDBUS_ERROR_FORBIDDEN_S "Incorrect password, access denied." -#define CDBUS_ERROR_CUSTOM CDBUS_ERROR_PREFIX ".custom" -#define CDBUS_ERROR_CUSTOM_S "%s" - -#define cdbus_reply_err(ps, srcmsg, err_name, err_format, ...) \ - cdbus_reply_errm((ps), dbus_message_new_error_printf( \ - (srcmsg), (err_name), (err_format), ##__VA_ARGS__)) - -static DBusHandlerResult cdbus_process(DBusConnection *conn, DBusMessage *m, void *); - -static dbus_bool_t cdbus_callback_add_timeout(DBusTimeout *timeout, void *data); - -static void cdbus_callback_remove_timeout(DBusTimeout *timeout, void *data); - -static void cdbus_callback_timeout_toggled(DBusTimeout *timeout, void *data); - -static dbus_bool_t cdbus_callback_add_watch(DBusWatch *watch, void *data); - -static void cdbus_callback_remove_watch(DBusWatch *watch, void *data); - -static void cdbus_callback_watch_toggled(DBusWatch *watch, void *data); - -/** - * Initialize D-Bus connection. - */ -bool cdbus_init(session_t *ps, const char *uniq) { - auto cd = cmalloc(struct cdbus_data); - cd->dbus_service = NULL; - - // Set ps->dbus_data here because add_watch functions need it - ps->dbus_data = cd; - - DBusError err = {}; - - // Initialize - dbus_error_init(&err); - - // Connect to D-Bus - // Use dbus_bus_get_private() so we can fully recycle it ourselves - cd->dbus_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err); - if (dbus_error_is_set(&err)) { - log_error("D-Bus connection failed (%s).", err.message); - dbus_error_free(&err); - goto fail; - } - - if (!cd->dbus_conn) { - log_error("D-Bus connection failed for unknown reason."); - goto fail; - } - - // Avoid exiting on disconnect - dbus_connection_set_exit_on_disconnect(cd->dbus_conn, false); - - // Request service name - { - // Build service name - size_t service_len = strlen(CDBUS_SERVICE_NAME) + strlen(uniq) + 2; - char *service = ccalloc(service_len, char); - snprintf(service, service_len, "%s.%s", CDBUS_SERVICE_NAME, uniq); - - // Make a valid dbus name by converting non alphanumeric characters to - // underscore - char *tmp = service + strlen(CDBUS_SERVICE_NAME) + 1; - while (*tmp) { - if (!isalnum((unsigned char)*tmp)) { - *tmp = '_'; - } - tmp++; - } - cd->dbus_service = service; - - // Request for the name - int ret = dbus_bus_request_name(cd->dbus_conn, service, - DBUS_NAME_FLAG_DO_NOT_QUEUE, &err); - - if (dbus_error_is_set(&err)) { - log_error("Failed to obtain D-Bus name (%s).", err.message); - dbus_error_free(&err); - goto fail; - } - - if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret && - DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER != ret) { - log_error("Failed to become the primary owner of requested D-Bus " - "name (%d).", - ret); - goto fail; - } - } - - // Add watch handlers - if (!dbus_connection_set_watch_functions(cd->dbus_conn, cdbus_callback_add_watch, - cdbus_callback_remove_watch, - cdbus_callback_watch_toggled, ps, NULL)) { - log_error("Failed to add D-Bus watch functions."); - goto fail; - } - - // Add timeout handlers - if (!dbus_connection_set_timeout_functions( - cd->dbus_conn, cdbus_callback_add_timeout, cdbus_callback_remove_timeout, - cdbus_callback_timeout_toggled, ps, NULL)) { - log_error("Failed to add D-Bus timeout functions."); - goto fail; - } - - // Add match - dbus_bus_add_match(cd->dbus_conn, - "type='method_call',interface='" CDBUS_INTERFACE_NAME "'", &err); - if (dbus_error_is_set(&err)) { - log_error("Failed to add D-Bus match."); - dbus_error_free(&err); - goto fail; - } - dbus_connection_add_filter(cd->dbus_conn, cdbus_process, ps, NULL); - return true; -fail: - ps->dbus_data = NULL; - free(cd->dbus_service); - free(cd); - return false; -} - -/** - * Destroy D-Bus connection. - */ -void cdbus_destroy(session_t *ps) { - struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) { - // Release DBus name firstly - if (cd->dbus_service) { - DBusError err = {}; - dbus_error_init(&err); - - dbus_bus_release_name(cd->dbus_conn, cd->dbus_service, &err); - if (dbus_error_is_set(&err)) { - log_error("Failed to release DBus name (%s).", err.message); - dbus_error_free(&err); - } - free(cd->dbus_service); - } - - // Close and unref the connection - dbus_connection_close(cd->dbus_conn); - dbus_connection_unref(cd->dbus_conn); - } - free(cd); -} - -/** @name DBusTimeout handling - */ -///@{ - -typedef struct ev_dbus_timer { - ev_timer w; - DBusTimeout *t; -} ev_dbus_timer; - -/** - * Callback for handling a D-Bus timeout. - */ -static void -cdbus_callback_handle_timeout(EV_P attr_unused, ev_timer *w, int revents attr_unused) { - ev_dbus_timer *t = (void *)w; - dbus_timeout_handle(t->t); -} - -/** - * Callback for adding D-Bus timeout. - */ -static dbus_bool_t cdbus_callback_add_timeout(DBusTimeout *timeout, void *data) { - session_t *ps = data; - - auto t = ccalloc(1, ev_dbus_timer); - double i = dbus_timeout_get_interval(timeout) / 1000.0; - ev_timer_init(&t->w, cdbus_callback_handle_timeout, i, i); - t->t = timeout; - dbus_timeout_set_data(timeout, t, NULL); - - if (dbus_timeout_get_enabled(timeout)) - ev_timer_start(ps->loop, &t->w); - - return true; -} - -/** - * Callback for removing D-Bus timeout. - */ -static void cdbus_callback_remove_timeout(DBusTimeout *timeout, void *data) { - session_t *ps = data; - - ev_dbus_timer *t = dbus_timeout_get_data(timeout); - assert(t); - ev_timer_stop(ps->loop, &t->w); - free(t); -} - -/** - * Callback for toggling a D-Bus timeout. - */ -static void cdbus_callback_timeout_toggled(DBusTimeout *timeout, void *data) { - session_t *ps = data; - ev_dbus_timer *t = dbus_timeout_get_data(timeout); - - assert(t); - ev_timer_stop(ps->loop, &t->w); - if (dbus_timeout_get_enabled(timeout)) { - double i = dbus_timeout_get_interval(timeout) / 1000.0; - ev_timer_set(&t->w, i, i); - ev_timer_start(ps->loop, &t->w); - } -} - -///@} - -/** @name DBusWatch handling - */ -///@{ - -typedef struct ev_dbus_io { - ev_io w; - struct cdbus_data *cd; - DBusWatch *dw; -} ev_dbus_io; - -void cdbus_io_callback(EV_P attr_unused, ev_io *w, int revents) { - ev_dbus_io *dw = (void *)w; - DBusWatchFlags flags = 0; - if (revents & EV_READ) - flags |= DBUS_WATCH_READABLE; - if (revents & EV_WRITE) - flags |= DBUS_WATCH_WRITABLE; - dbus_watch_handle(dw->dw, flags); - while (dbus_connection_dispatch(dw->cd->dbus_conn) != DBUS_DISPATCH_COMPLETE) - ; -} - -/** - * Determine the poll condition of a DBusWatch. - */ -static inline int cdbus_get_watch_cond(DBusWatch *watch) { - const unsigned flags = dbus_watch_get_flags(watch); - int condition = 0; - if (flags & DBUS_WATCH_READABLE) - condition |= EV_READ; - if (flags & DBUS_WATCH_WRITABLE) - condition |= EV_WRITE; - - return condition; -} - -/** - * Callback for adding D-Bus watch. - */ -static dbus_bool_t cdbus_callback_add_watch(DBusWatch *watch, void *data) { - session_t *ps = data; - - auto w = ccalloc(1, ev_dbus_io); - w->dw = watch; - w->cd = ps->dbus_data; - ev_io_init(&w->w, cdbus_io_callback, dbus_watch_get_unix_fd(watch), - cdbus_get_watch_cond(watch)); - - // Leave disabled watches alone - if (dbus_watch_get_enabled(watch)) - ev_io_start(ps->loop, &w->w); - - dbus_watch_set_data(watch, w, NULL); - - // Always return true - return true; -} - -/** - * Callback for removing D-Bus watch. - */ -static void cdbus_callback_remove_watch(DBusWatch *watch, void *data) { - session_t *ps = data; - ev_dbus_io *w = dbus_watch_get_data(watch); - ev_io_stop(ps->loop, &w->w); - free(w); -} - -/** - * Callback for toggling D-Bus watch status. - */ -static void cdbus_callback_watch_toggled(DBusWatch *watch, void *data) { - session_t *ps = data; - ev_io *w = dbus_watch_get_data(watch); - if (dbus_watch_get_enabled(watch)) - ev_io_start(ps->loop, w); - else - ev_io_stop(ps->loop, w); -} - -///@} - -/** @name Message argument appending callbacks - */ -///@{ - -/** - * Callback to append a bool argument to a message. - */ -static bool cdbus_apdarg_bool(session_t *ps attr_unused, DBusMessage *msg, const void *data) { - assert(data); - - dbus_bool_t val = *(const bool *)data; - - if (!dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &val, DBUS_TYPE_INVALID)) { - log_error("Failed to append argument."); - return false; - } - - return true; -} - -/** - * Callback to append an int32 argument to a message. - */ -static bool cdbus_apdarg_int32(session_t *ps attr_unused, DBusMessage *msg, const void *data) { - if (!dbus_message_append_args(msg, DBUS_TYPE_INT32, data, DBUS_TYPE_INVALID)) { - log_error("Failed to append argument."); - return false; - } - - return true; -} - -/** - * Callback to append an uint32 argument to a message. - */ -static bool -cdbus_apdarg_uint32(session_t *ps attr_unused, DBusMessage *msg, const void *data) { - if (!dbus_message_append_args(msg, DBUS_TYPE_UINT32, data, DBUS_TYPE_INVALID)) { - log_error("Failed to append argument."); - return false; - } - - return true; -} - -/** - * Callback to append a double argument to a message. - */ -static bool -cdbus_apdarg_double(session_t *ps attr_unused, DBusMessage *msg, const void *data) { - if (!dbus_message_append_args(msg, DBUS_TYPE_DOUBLE, data, DBUS_TYPE_INVALID)) { - log_error("Failed to append argument."); - return false; - } - - return true; -} - -/** - * Callback to append a Window argument to a message. - */ -static bool cdbus_apdarg_wid(session_t *ps attr_unused, DBusMessage *msg, const void *data) { - assert(data); - cdbus_window_t val = *(const xcb_window_t *)data; - - if (!dbus_message_append_args(msg, CDBUS_TYPE_WINDOW, &val, DBUS_TYPE_INVALID)) { - log_error("Failed to append argument."); - return false; - } - - return true; -} - -/** - * Callback to append an cdbus_enum_t argument to a message. - */ -static bool cdbus_apdarg_enum(session_t *ps attr_unused, DBusMessage *msg, const void *data) { - assert(data); - if (!dbus_message_append_args(msg, CDBUS_TYPE_ENUM, data, DBUS_TYPE_INVALID)) { - log_error("Failed to append argument."); - return false; - } - - return true; -} - -/** - * Callback to append a string argument to a message. - */ -static bool -cdbus_apdarg_string(session_t *ps attr_unused, DBusMessage *msg, const void *data) { - const char *str = data; - if (!str) - str = ""; - - if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) { - log_error("Failed to append argument."); - return false; - } - - return true; -} - -/** - * Callback to append all window IDs to a message. - */ -static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data attr_unused) { - // Get the number of wids we are to include - unsigned count = 0; - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - ++count; - } - - if (!count) { - // Nothing to append - return true; - } - - // Allocate memory for an array of window IDs - auto arr = ccalloc(count, cdbus_window_t); - - // Build the array - cdbus_window_t *pcur = arr; - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - *pcur = w->id; - ++pcur; - } - assert(pcur == arr + count); - - // Append arguments - if (!dbus_message_append_args(msg, DBUS_TYPE_ARRAY, CDBUS_TYPE_WINDOW, &arr, - count, DBUS_TYPE_INVALID)) { - log_error("Failed to append argument."); - free(arr); - return false; - } - - free(arr); - return true; -} -///@} - -/** - * Send a D-Bus signal. - * - * @param ps current session - * @param name signal name - * @param func a function that modifies the built message, to, for example, - * add an argument - * @param data data pointer to pass to the function - */ -static bool cdbus_signal(session_t *ps, const char *name, - bool (*func)(session_t *ps, DBusMessage *msg, const void *data), - const void *data) { - struct cdbus_data *cd = ps->dbus_data; - DBusMessage *msg = NULL; - - // Create a signal - msg = dbus_message_new_signal(CDBUS_OBJECT_NAME, CDBUS_INTERFACE_NAME, name); - if (!msg) { - log_error("Failed to create D-Bus signal."); - return false; - } - - // Append arguments onto message - if (func && !func(ps, msg, data)) { - dbus_message_unref(msg); - return false; - } - - // Send the message and flush the connection - if (!dbus_connection_send(cd->dbus_conn, msg, NULL)) { - log_error("Failed to send D-Bus signal."); - dbus_message_unref(msg); - return false; - } - dbus_connection_flush(cd->dbus_conn); - - // Free the message - dbus_message_unref(msg); - - return true; -} - -/** - * Send a signal with a Window ID as argument. - */ -static inline bool cdbus_signal_wid(session_t *ps, const char *name, xcb_window_t wid) { - return cdbus_signal(ps, name, cdbus_apdarg_wid, &wid); -} - -/** - * Send a D-Bus reply. - * - * @param ps current session - * @param srcmsg original message - * @param func a function that modifies the built message, to, for example, - * add an argument - * @param data data pointer to pass to the function - */ -static bool cdbus_reply(session_t *ps, DBusMessage *srcmsg, - bool (*func)(session_t *ps, DBusMessage *msg, const void *data), - const void *data) { - struct cdbus_data *cd = ps->dbus_data; - DBusMessage *msg = NULL; - - // Create a reply - msg = dbus_message_new_method_return(srcmsg); - if (!msg) { - log_error("Failed to create D-Bus reply."); - return false; - } - - // Append arguments onto message - if (func && !func(ps, msg, data)) { - dbus_message_unref(msg); - return false; - } - - // Send the message and flush the connection - if (!dbus_connection_send(cd->dbus_conn, msg, NULL)) { - log_error("Failed to send D-Bus reply."); - dbus_message_unref(msg); - return false; - } - dbus_connection_flush(cd->dbus_conn); - - // Free the message - dbus_message_unref(msg); - - return true; -} - -/** - * Send a reply with a bool argument. - */ -static inline bool cdbus_reply_bool(session_t *ps, DBusMessage *srcmsg, bool bval) { - return cdbus_reply(ps, srcmsg, cdbus_apdarg_bool, &bval); -} - -/** - * Send a reply with an int32 argument. - */ -static inline bool cdbus_reply_int32(session_t *ps, DBusMessage *srcmsg, int32_t val) { - return cdbus_reply(ps, srcmsg, cdbus_apdarg_int32, &val); -} - -/** - * Send a reply with an int32 argument, cast from a long. - */ -static inline bool cdbus_reply_int32l(session_t *ps, DBusMessage *srcmsg, long val) { - int32_t tmp = (int32_t)val; - return cdbus_reply(ps, srcmsg, cdbus_apdarg_int32, &tmp); -} - -/** - * Send a reply with an uint32 argument. - */ -static inline bool cdbus_reply_uint32(session_t *ps, DBusMessage *srcmsg, uint32_t val) { - return cdbus_reply(ps, srcmsg, cdbus_apdarg_uint32, &val); -} - -/** - * Send a reply with a double argument. - */ -static inline bool cdbus_reply_double(session_t *ps, DBusMessage *srcmsg, double val) { - return cdbus_reply(ps, srcmsg, cdbus_apdarg_double, &val); -} - -/** - * Send a reply with a wid argument. - */ -static inline bool cdbus_reply_wid(session_t *ps, DBusMessage *srcmsg, xcb_window_t wid) { - return cdbus_reply(ps, srcmsg, cdbus_apdarg_wid, &wid); -} - -/** - * Send a reply with a string argument. - */ -static inline bool cdbus_reply_string(session_t *ps, DBusMessage *srcmsg, const char *str) { - return cdbus_reply(ps, srcmsg, cdbus_apdarg_string, str); -} - -/** - * Send a reply with a enum argument. - */ -static inline bool cdbus_reply_enum(session_t *ps, DBusMessage *srcmsg, cdbus_enum_t eval) { - return cdbus_reply(ps, srcmsg, cdbus_apdarg_enum, &eval); -} - -/** - * Send a D-Bus error reply. - * - * @param ps current session - * @param msg the new error DBusMessage - */ -static bool cdbus_reply_errm(session_t *ps, DBusMessage *msg) { - struct cdbus_data *cd = ps->dbus_data; - if (!msg) { - log_error("Failed to create D-Bus reply."); - return false; - } - - // Send the message and flush the connection - if (!dbus_connection_send(cd->dbus_conn, msg, NULL)) { - log_error("Failed to send D-Bus reply."); - dbus_message_unref(msg); - return false; - } - dbus_connection_flush(cd->dbus_conn); - - // Free the message - dbus_message_unref(msg); - - return true; -} - -/** - * Get n-th argument of a D-Bus message. - * - * @param count the position of the argument to get, starting from 0 - * @param type libdbus type number of the type - * @param pdest pointer to the target - * @return true if successful, false otherwise. - */ -static bool cdbus_msg_get_arg(DBusMessage *msg, int count, const int type, void *pdest) { - assert(count >= 0); - - DBusMessageIter iter = {}; - if (!dbus_message_iter_init(msg, &iter)) { - log_error("Message has no argument."); - return false; - } - - { - const int oldcount = count; - while (count) { - if (!dbus_message_iter_next(&iter)) { - log_error("Failed to find argument %d.", oldcount); - return false; - } - --count; - } - } - - if (type != dbus_message_iter_get_arg_type(&iter)) { - log_error("Argument has incorrect type."); - return false; - } - - dbus_message_iter_get_basic(&iter, pdest); - - return true; -} - -/** @name Message processing - */ -///@{ - -/** - * Process a list_win D-Bus request. - */ -static bool cdbus_process_list_win(session_t *ps, DBusMessage *msg) { - cdbus_reply(ps, msg, cdbus_apdarg_wids, NULL); - - return true; -} - -/** - * Process a win_get D-Bus request. - */ -static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) { - cdbus_window_t wid = XCB_NONE; - const char *target = NULL; - DBusError err = {}; - - if (!dbus_message_get_args(msg, &err, CDBUS_TYPE_WINDOW, &wid, DBUS_TYPE_STRING, - &target, DBUS_TYPE_INVALID)) { - log_error("Failed to parse argument of \"win_get\" (%s).", err.message); - dbus_error_free(&err); - return false; - } - - auto w = find_managed_win(ps, wid); - - if (!w) { - log_error("Window %#010x not found.", wid); - cdbus_reply_err(ps, msg, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); - return true; - } - -#define cdbus_m_win_get_do(tgt, apdarg_func) \ - if (!strcmp(#tgt, target)) { \ - apdarg_func(ps, msg, w->tgt); \ - return true; \ - } - - cdbus_m_win_get_do(base.id, cdbus_reply_wid); - - // next - if (!strcmp("next", target)) { - cdbus_reply_wid( - ps, msg, - (list_node_is_last(&ps->window_stack, &w->base.stack_neighbour) - ? 0 - : list_entry(w->base.stack_neighbour.next, struct win, stack_neighbour) - ->id)); - return true; - } - - // map_state - if (!strcmp("map_state", target)) { - cdbus_reply_bool(ps, msg, w->a.map_state); - return true; - } - - cdbus_m_win_get_do(mode, cdbus_reply_enum); - cdbus_m_win_get_do(client_win, cdbus_reply_wid); - cdbus_m_win_get_do(ever_damaged, cdbus_reply_bool); - cdbus_m_win_get_do(window_type, cdbus_reply_enum); - cdbus_m_win_get_do(wmwin, cdbus_reply_bool); - cdbus_m_win_get_do(leader, cdbus_reply_wid); - if (!strcmp("focused_raw", target)) { - cdbus_reply_bool(ps, msg, win_is_focused_raw(ps, w)); - return true; - } - cdbus_m_win_get_do(fade_force, cdbus_reply_enum); - cdbus_m_win_get_do(shadow_force, cdbus_reply_enum); - cdbus_m_win_get_do(focused_force, cdbus_reply_enum); - cdbus_m_win_get_do(invert_color_force, cdbus_reply_enum); - cdbus_m_win_get_do(name, cdbus_reply_string); - cdbus_m_win_get_do(class_instance, cdbus_reply_string); - cdbus_m_win_get_do(class_general, cdbus_reply_string); - cdbus_m_win_get_do(role, cdbus_reply_string); - - cdbus_m_win_get_do(opacity, cdbus_reply_double); - cdbus_m_win_get_do(opacity_target, cdbus_reply_double); - cdbus_m_win_get_do(has_opacity_prop, cdbus_reply_bool); - cdbus_m_win_get_do(opacity_prop, cdbus_reply_uint32); - cdbus_m_win_get_do(opacity_is_set, cdbus_reply_bool); - cdbus_m_win_get_do(opacity_set, cdbus_reply_double); - - cdbus_m_win_get_do(frame_opacity, cdbus_reply_double); - if (!strcmp("left_width", target)) { - cdbus_reply_int32(ps, msg, w->frame_extents.left); - return true; - } - if (!strcmp("right_width", target)) { - cdbus_reply_int32(ps, msg, w->frame_extents.right); - return true; - } - if (!strcmp("top_width", target)) { - cdbus_reply_int32(ps, msg, w->frame_extents.top); - return true; - } - if (!strcmp("bottom_width", target)) { - cdbus_reply_int32(ps, msg, w->frame_extents.bottom); - return true; - } - - cdbus_m_win_get_do(shadow, cdbus_reply_bool); - cdbus_m_win_get_do(invert_color, cdbus_reply_bool); - cdbus_m_win_get_do(blur_background, cdbus_reply_bool); -#undef cdbus_m_win_get_do - - log_error(CDBUS_ERROR_BADTGT_S, target); - cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); - - return true; -} - -/** - * Process a win_set D-Bus request. - */ -static bool cdbus_process_win_set(session_t *ps, DBusMessage *msg) { - cdbus_window_t wid = XCB_NONE; - const char *target = NULL; - DBusError err = {}; - - if (!dbus_message_get_args(msg, &err, CDBUS_TYPE_WINDOW, &wid, DBUS_TYPE_STRING, - &target, DBUS_TYPE_INVALID)) { - log_error("(): Failed to parse argument of \"win_set\" (%s).", err.message); - dbus_error_free(&err); - return false; - } - - auto w = find_managed_win(ps, wid); - - if (!w) { - log_error("Window %#010x not found.", wid); - cdbus_reply_err(ps, msg, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); - return true; - } - -#define cdbus_m_win_set_do(tgt, type, real_type) \ - if (!strcmp(MSTR(tgt), target)) { \ - real_type val; \ - if (!cdbus_msg_get_arg(msg, 2, type, &val)) \ - return false; \ - w->tgt = val; \ - goto cdbus_process_win_set_success; \ - } - - if (!strcmp("shadow_force", target)) { - cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) - return false; - win_set_shadow_force(ps, w, val); - goto cdbus_process_win_set_success; - } - - if (!strcmp("fade_force", target)) { - cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) - return false; - win_set_fade_force(w, val); - goto cdbus_process_win_set_success; - } - - if (!strcmp("focused_force", target)) { - cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) - return false; - win_set_focused_force(ps, w, val); - goto cdbus_process_win_set_success; - } - - if (!strcmp("invert_color_force", target)) { - cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) - return false; - win_set_invert_color_force(ps, w, val); - goto cdbus_process_win_set_success; - } -#undef cdbus_m_win_set_do - - log_error(CDBUS_ERROR_BADTGT_S, target); - cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); - - return true; - -cdbus_process_win_set_success: - if (!dbus_message_get_no_reply(msg)) - cdbus_reply_bool(ps, msg, true); - return true; -} - -/** - * Process a find_win D-Bus request. - */ -static bool cdbus_process_find_win(session_t *ps, DBusMessage *msg) { - const char *target = NULL; - - if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) - return false; - - xcb_window_t wid = XCB_NONE; - - // Find window by client window - if (!strcmp("client", target)) { - cdbus_window_t client = XCB_NONE; - if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_WINDOW, &client)) - return false; - auto w = find_toplevel(ps, client); - if (w) { - wid = w->base.id; - } - } - // Find focused window - else if (!strcmp("focused", target)) { - if (ps->active_win && ps->active_win->state != WSTATE_UNMAPPED) { - wid = ps->active_win->base.id; - } - } else { - log_error(CDBUS_ERROR_BADTGT_S, target); - cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); - - return true; - } - - cdbus_reply_wid(ps, msg, wid); - - return true; -} - -/** - * Process a opts_get D-Bus request. - */ -static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { - const char *target = NULL; - - if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) - return false; - -#define cdbus_m_opts_get_do(tgt, apdarg_func) \ - if (!strcmp(#tgt, target)) { \ - apdarg_func(ps, msg, ps->o.tgt); \ - return true; \ - } - -#define cdbus_m_opts_get_stub(tgt, apdarg_func, ret) \ - if (!strcmp(#tgt, target)) { \ - apdarg_func(ps, msg, ret); \ - return true; \ - } - - // version - if (!strcmp("version", target)) { - cdbus_reply_string(ps, msg, COMPTON_VERSION); - return true; - } - - // pid - if (!strcmp("pid", target)) { - cdbus_reply_int32(ps, msg, getpid()); - return true; - } - - // display - if (!strcmp("display", target)) { - cdbus_reply_string(ps, msg, DisplayString(ps->dpy)); - return true; - } - - cdbus_m_opts_get_stub(config_file, cdbus_reply_string, "Unknown"); - cdbus_m_opts_get_do(write_pid_path, cdbus_reply_string); - cdbus_m_opts_get_do(mark_wmwin_focused, cdbus_reply_bool); - cdbus_m_opts_get_do(mark_ovredir_focused, cdbus_reply_bool); - cdbus_m_opts_get_do(detect_rounded_corners, cdbus_reply_bool); - cdbus_m_opts_get_stub(paint_on_overlay, cdbus_reply_bool, ps->overlay != XCB_NONE); - // paint_on_overlay_id: Get ID of the X composite overlay window - if (!strcmp("paint_on_overlay_id", target)) { - cdbus_reply_uint32(ps, msg, ps->overlay); - return true; - } - cdbus_m_opts_get_do(unredir_if_possible, cdbus_reply_bool); - cdbus_m_opts_get_do(unredir_if_possible_delay, cdbus_reply_int32l); - cdbus_m_opts_get_do(redirected_force, cdbus_reply_enum); - cdbus_m_opts_get_do(stoppaint_force, cdbus_reply_enum); - cdbus_m_opts_get_do(logpath, cdbus_reply_string); - - cdbus_m_opts_get_do(refresh_rate, cdbus_reply_int32); - cdbus_m_opts_get_do(sw_opti, cdbus_reply_bool); - cdbus_m_opts_get_do(vsync, cdbus_reply_bool); - if (!strcmp("backend", target)) { - assert(ps->o.backend < sizeof(BACKEND_STRS) / sizeof(BACKEND_STRS[0])); - cdbus_reply_string(ps, msg, BACKEND_STRS[ps->o.backend]); - return true; - } - - cdbus_m_opts_get_do(shadow_red, cdbus_reply_double); - cdbus_m_opts_get_do(shadow_green, cdbus_reply_double); - cdbus_m_opts_get_do(shadow_blue, cdbus_reply_double); - cdbus_m_opts_get_do(shadow_radius, cdbus_reply_int32); - cdbus_m_opts_get_do(shadow_offset_x, cdbus_reply_int32); - cdbus_m_opts_get_do(shadow_offset_y, cdbus_reply_int32); - cdbus_m_opts_get_do(shadow_opacity, cdbus_reply_double); - cdbus_m_opts_get_do(xinerama_shadow_crop, cdbus_reply_bool); - - cdbus_m_opts_get_do(fade_delta, cdbus_reply_int32); - cdbus_m_opts_get_do(fade_in_step, cdbus_reply_double); - cdbus_m_opts_get_do(fade_out_step, cdbus_reply_double); - cdbus_m_opts_get_do(no_fading_openclose, cdbus_reply_bool); - - cdbus_m_opts_get_do(blur_method, cdbus_reply_bool); - cdbus_m_opts_get_do(blur_background_frame, cdbus_reply_bool); - cdbus_m_opts_get_do(blur_background_fixed, cdbus_reply_bool); - - cdbus_m_opts_get_do(inactive_dim, cdbus_reply_double); - cdbus_m_opts_get_do(inactive_dim_fixed, cdbus_reply_bool); - - cdbus_m_opts_get_do(max_brightness, cdbus_reply_double); - - cdbus_m_opts_get_do(use_ewmh_active_win, cdbus_reply_bool); - cdbus_m_opts_get_do(detect_transient, cdbus_reply_bool); - cdbus_m_opts_get_do(detect_client_leader, cdbus_reply_bool); - cdbus_m_opts_get_do(use_damage, cdbus_reply_bool); - -#ifdef CONFIG_OPENGL - cdbus_m_opts_get_do(glx_no_stencil, cdbus_reply_bool); - cdbus_m_opts_get_do(glx_no_rebind_pixmap, cdbus_reply_bool); -#endif - -#undef cdbus_m_opts_get_do -#undef cdbus_m_opts_get_stub - - log_error(CDBUS_ERROR_BADTGT_S, target); - cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); - - return true; -} - -// XXX Remove this after header clean up -void queue_redraw(session_t *ps); - -/** - * Process a opts_set D-Bus request. - */ -static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { - const char *target = NULL; - - if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) - return false; - -#define cdbus_m_opts_set_do(tgt, type, real_type) \ - if (!strcmp(#tgt, target)) { \ - real_type val; \ - if (!cdbus_msg_get_arg(msg, 1, type, &val)) \ - return false; \ - ps->o.tgt = val; \ - goto cdbus_process_opts_set_success; \ - } - - // fade_delta - if (!strcmp("fade_delta", target)) { - int32_t val = 0; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_INT32, &val)) { - return false; - } - if (val <= 0) { - return false; - } - ps->o.fade_delta = max2(val, 1); - goto cdbus_process_opts_set_success; - } - - // fade_in_step - if (!strcmp("fade_in_step", target)) { - double val = 0.0; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) - return false; - ps->o.fade_in_step = normalize_d(val); - goto cdbus_process_opts_set_success; - } - - // fade_out_step - if (!strcmp("fade_out_step", target)) { - double val = 0.0; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) - return false; - ps->o.fade_out_step = normalize_d(val); - goto cdbus_process_opts_set_success; - } - - // no_fading_openclose - if (!strcmp("no_fading_openclose", target)) { - dbus_bool_t val = FALSE; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) - return false; - opts_set_no_fading_openclose(ps, val); - goto cdbus_process_opts_set_success; - } - - // unredir_if_possible - if (!strcmp("unredir_if_possible", target)) { - dbus_bool_t val = FALSE; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) - return false; - if (ps->o.unredir_if_possible != val) { - ps->o.unredir_if_possible = val; - queue_redraw(ps); - } - goto cdbus_process_opts_set_success; - } - - // clear_shadow - if (!strcmp("clear_shadow", target)) { - goto cdbus_process_opts_set_success; - } - - // track_focus - if (!strcmp("track_focus", target)) { - goto cdbus_process_opts_set_success; - } - - // redirected_force - if (!strcmp("redirected_force", target)) { - cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_ENUM, &val)) - return false; - ps->o.redirected_force = val; - force_repaint(ps); - goto cdbus_process_opts_set_success; - } - - // stoppaint_force - cdbus_m_opts_set_do(stoppaint_force, CDBUS_TYPE_ENUM, cdbus_enum_t); - -#undef cdbus_m_opts_set_do - - log_error(CDBUS_ERROR_BADTGT_S, target); - cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); - - return true; - -cdbus_process_opts_set_success: - if (!dbus_message_get_no_reply(msg)) - cdbus_reply_bool(ps, msg, true); - return true; -} - -/** - * Process an Introspect D-Bus request. - */ -static bool cdbus_process_introspect(session_t *ps, DBusMessage *msg) { - static const char *str_introspect = - "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection " - "1.0//EN\"\n" - " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" - "<node name='" CDBUS_OBJECT_NAME "'>\n" - " <interface name='org.freedesktop.DBus.Introspectable'>\n" - " <method name='Introspect'>\n" - " <arg name='data' direction='out' type='s' />\n" - " </method>\n" - " </interface>\n" - " <interface name='org.freedesktop.DBus.Peer'>\n" - " <method name='Ping' />\n" - " <method name='GetMachineId'>\n" - " <arg name='machine_uuid' direction='out' type='s' />\n" - " </method>\n" - " </interface>\n" - " <interface name='" CDBUS_INTERFACE_NAME "'>\n" - " <signal name='win_added'>\n" - " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" - " </signal>\n" - " <signal name='win_destroyed'>\n" - " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" - " </signal>\n" - " <signal name='win_mapped'>\n" - " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" - " </signal>\n" - " <signal name='win_unmapped'>\n" - " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" - " </signal>\n" - " <signal name='win_focusin'>\n" - " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" - " </signal>\n" - " <signal name='win_focusout'>\n" - " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" - " </signal>\n" - " <method name='reset' />\n" - " <method name='repaint' />\n" - " </interface>\n" - "</node>\n"; - - cdbus_reply_string(ps, msg, str_introspect); - - return true; -} -///@} - -/** - * Process a message from D-Bus. - */ -static DBusHandlerResult -cdbus_process(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) { - session_t *ps = ud; - bool handled = false; - -#define cdbus_m_ismethod(method) \ - dbus_message_is_method_call(msg, CDBUS_INTERFACE_NAME, method) - - if (cdbus_m_ismethod("reset")) { - log_info("picom is resetting..."); - ev_break(ps->loop, EVBREAK_ALL); - if (!dbus_message_get_no_reply(msg)) - cdbus_reply_bool(ps, msg, true); - handled = true; - } else if (cdbus_m_ismethod("repaint")) { - force_repaint(ps); - if (!dbus_message_get_no_reply(msg)) - cdbus_reply_bool(ps, msg, true); - handled = true; - } else if (cdbus_m_ismethod("list_win")) { - handled = cdbus_process_list_win(ps, msg); - } else if (cdbus_m_ismethod("win_get")) { - handled = cdbus_process_win_get(ps, msg); - } else if (cdbus_m_ismethod("win_set")) { - handled = cdbus_process_win_set(ps, msg); - } else if (cdbus_m_ismethod("find_win")) { - handled = cdbus_process_find_win(ps, msg); - } else if (cdbus_m_ismethod("opts_get")) { - handled = cdbus_process_opts_get(ps, msg); - } else if (cdbus_m_ismethod("opts_set")) { - handled = cdbus_process_opts_set(ps, msg); - } -#undef cdbus_m_ismethod - else if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", - "Introspect")) { - handled = cdbus_process_introspect(ps, msg); - } else if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Peer", "Ping")) { - cdbus_reply(ps, msg, NULL, NULL); - handled = true; - } else if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Peer", - "GetMachineId")) { - char *uuid = dbus_get_local_machine_id(); - if (uuid) { - cdbus_reply_string(ps, msg, uuid); - dbus_free(uuid); - handled = true; - } - } else if (dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameAcquired") || - dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameLost")) { - handled = true; - } else { - if (DBUS_MESSAGE_TYPE_ERROR == dbus_message_get_type(msg)) { - log_error( - "Error message of path \"%s\" " - "interface \"%s\", member \"%s\", error \"%s\"", - dbus_message_get_path(msg), dbus_message_get_interface(msg), - dbus_message_get_member(msg), dbus_message_get_error_name(msg)); - } else { - log_error("Illegal message of type \"%s\", path \"%s\" " - "interface \"%s\", member \"%s\"", - cdbus_repr_msgtype(msg), dbus_message_get_path(msg), - dbus_message_get_interface(msg), - dbus_message_get_member(msg)); - } - if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) && - !dbus_message_get_no_reply(msg)) - cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S); - handled = true; - } - - // If the message could not be processed, and an reply is expected, return - // an empty reply. - if (!handled && DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) && - !dbus_message_get_no_reply(msg)) { - cdbus_reply_err(ps, msg, CDBUS_ERROR_UNKNOWN, CDBUS_ERROR_UNKNOWN_S); - handled = true; - } - - return handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; -} - -/** @name Core callbacks - */ -///@{ -void cdbus_ev_win_added(session_t *ps, struct win *w) { - struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_added", w->id); -} - -void cdbus_ev_win_destroyed(session_t *ps, struct win *w) { - struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_destroyed", w->id); -} - -void cdbus_ev_win_mapped(session_t *ps, struct win *w) { - struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_mapped", w->id); -} - -void cdbus_ev_win_unmapped(session_t *ps, struct win *w) { - struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_unmapped", w->id); -} - -void cdbus_ev_win_focusout(session_t *ps, struct win *w) { - struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_focusout", w->id); -} - -void cdbus_ev_win_focusin(session_t *ps, struct win *w) { - struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_focusin", w->id); -} -//!@} diff --git a/src/dbus.h b/src/dbus.h deleted file mode 100644 index 54a58af..0000000 --- a/src/dbus.h +++ /dev/null @@ -1,54 +0,0 @@ -// 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 : diff --git a/src/diagnostic.c b/src/diagnostic.c deleted file mode 100644 index d275b1a..0000000 --- a/src/diagnostic.c +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018 Yuxuan Shui <[email protected]> - -#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 : diff --git a/src/diagnostic.h b/src/diagnostic.h deleted file mode 100644 index c958589..0000000 --- a/src/diagnostic.h +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018 Yuxuan Shui <[email protected]> - -#pragma once -#include <stdbool.h> - -typedef struct session session_t; - -void print_diagnostics(session_t *, const char *config_file, bool compositor_running); diff --git a/src/err.h b/src/err.h deleted file mode 100644 index f989bf9..0000000 --- a/src/err.h +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2019 Yuxuan Shui <[email protected]> - -#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; -} diff --git a/src/event.c b/src/event.c deleted file mode 100644 index 5e4017f..0000000 --- a/src/event.c +++ /dev/null @@ -1,757 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2019, Yuxuan Shui <[email protected]> - -#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_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; - } - } -} diff --git a/src/event.h b/src/event.h deleted file mode 100644 index 629dec0..0000000 --- a/src/event.h +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2019, Yuxuan Shui <[email protected]> - -#include <xcb/xcb.h> - -#include "common.h" - -void ev_handle(session_t *ps, xcb_generic_event_t *ev); diff --git a/src/file_watch.c b/src/file_watch.c deleted file mode 100644 index faa8f68..0000000 --- a/src/file_watch.c +++ /dev/null @@ -1,188 +0,0 @@ -#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; -} diff --git a/src/file_watch.h b/src/file_watch.h deleted file mode 100644 index c249cd2..0000000 --- a/src/file_watch.h +++ /dev/null @@ -1,10 +0,0 @@ -#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 *); diff --git a/src/kernel.c b/src/kernel.c deleted file mode 100644 index 5151045..0000000 --- a/src/kernel.c +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> - -#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 : diff --git a/src/kernel.h b/src/kernel.h deleted file mode 100644 index 251d127..0000000 --- a/src/kernel.h +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> - -#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); -} diff --git a/src/list.h b/src/list.h deleted file mode 100644 index 19e2c2c..0000000 --- a/src/list.h +++ /dev/null @@ -1,108 +0,0 @@ -#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)) diff --git a/src/log.c b/src/log.c deleted file mode 100644 index 0b663e7..0000000 --- a/src/log.c +++ /dev/null @@ -1,376 +0,0 @@ -#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: diff --git a/src/log.h b/src/log.h deleted file mode 100644 index e40fe3c..0000000 --- a/src/log.h +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018 Yuxuan Shui <[email protected]> - -#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: diff --git a/src/meson.build b/src/meson.build deleted file mode 100644 index 0a882f9..0000000 --- a/src/meson.build +++ /dev/null @@ -1,97 +0,0 @@ -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 diff --git a/src/meta.h b/src/meta.h deleted file mode 100644 index 4314356..0000000 --- a/src/meta.h +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2019, Yuxuan Shui <[email protected]> - -#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() diff --git a/src/opengl.c b/src/opengl.c deleted file mode 100644 index 5d2d66c..0000000 --- a/src/opengl.c +++ /dev/null @@ -1,1514 +0,0 @@ -// 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 <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <xcb/render.h> -#include <xcb/xcb.h> - -#include "backend/gl/gl_common.h" -#include "backend/gl/glx.h" -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "kernel.h" -#include "log.h" -#include "region.h" -#include "string_utils.h" -#include "uthash_extra.h" -#include "utils.h" -#include "win.h" - -#include "opengl.h" - -#ifndef GL_TEXTURE_RECTANGLE -#define GL_TEXTURE_RECTANGLE 0x84F5 -#endif - -static inline XVisualInfo *get_visualinfo_from_visual(session_t *ps, xcb_visualid_t visual) { - XVisualInfo vreq = {.visualid = visual}; - int nitems = 0; - - return XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems); -} - -/** - * Initialize OpenGL. - */ -bool glx_init(session_t *ps, bool need_render) { - bool success = false; - XVisualInfo *pvis = NULL; - - // Check for GLX extension - if (!ps->glx_exists) { - log_error("No GLX extension."); - goto glx_init_end; - } - - // Get XVisualInfo - pvis = get_visualinfo_from_visual(ps, ps->vis); - if (!pvis) { - log_error("Failed to acquire XVisualInfo for current visual."); - goto glx_init_end; - } - - // Ensure the visual is double-buffered - if (need_render) { - int value = 0; - if (Success != glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) { - log_error("Root visual is not a GL visual."); - goto glx_init_end; - } - - if (Success != glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) { - log_error("Root visual is not a double buffered GL visual."); - goto glx_init_end; - } - } - - // Ensure GLX_EXT_texture_from_pixmap exists - if (need_render && !glxext.has_GLX_EXT_texture_from_pixmap) - goto glx_init_end; - - // Initialize GLX data structure - if (!ps->psglx) { - static const glx_session_t CGLX_SESSION_DEF = CGLX_SESSION_INIT; - ps->psglx = cmalloc(glx_session_t); - memcpy(ps->psglx, &CGLX_SESSION_DEF, sizeof(glx_session_t)); - - // +1 for the zero terminator - ps->psglx->blur_passes = ccalloc(ps->o.blur_kernel_count, glx_blur_pass_t); - - for (int i = 0; i < ps->o.blur_kernel_count; ++i) { - glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; - ppass->unifm_factor_center = -1; - ppass->unifm_offset_x = -1; - ppass->unifm_offset_y = -1; - } - - ps->psglx->round_passes = ccalloc(1, glx_round_pass_t); - glx_round_pass_t *ppass = ps->psglx->round_passes; - ppass->unifm_radius = -1; - ppass->unifm_texcoord = -1; - ppass->unifm_texsize = -1; - ppass->unifm_borderw = -1; - ppass->unifm_borderc = -1; - ppass->unifm_resolution = -1; - ppass->unifm_tex_scr = -1; - } - - glx_session_t *psglx = ps->psglx; - - if (!psglx->context) { - // Get GLX context -#ifndef DEBUG_GLX_DEBUG_CONTEXT - psglx->context = glXCreateContext(ps->dpy, pvis, None, GL_TRUE); -#else - { - GLXFBConfig fbconfig = get_fbconfig_from_visualinfo(ps, pvis); - if (!fbconfig) { - log_error("Failed to get GLXFBConfig for root visual " - "%#lx.", - pvis->visualid); - goto glx_init_end; - } - - f_glXCreateContextAttribsARB p_glXCreateContextAttribsARB = - (f_glXCreateContextAttribsARB)glXGetProcAddress( - (const GLubyte *)"glXCreateContextAttribsARB"); - if (!p_glXCreateContextAttribsARB) { - log_error("Failed to get glXCreateContextAttribsARB()."); - goto glx_init_end; - } - - static const int attrib_list[] = { - GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, None}; - psglx->context = p_glXCreateContextAttribsARB( - ps->dpy, fbconfig, NULL, GL_TRUE, attrib_list); - } -#endif - - if (!psglx->context) { - log_error("Failed to get GLX context."); - goto glx_init_end; - } - - // Attach GLX context - if (!glXMakeCurrent(ps->dpy, get_tgt_window(ps), psglx->context)) { - log_error("Failed to attach GLX context."); - goto glx_init_end; - } - -#ifdef DEBUG_GLX_DEBUG_CONTEXT - { - f_DebugMessageCallback p_DebugMessageCallback = - (f_DebugMessageCallback)glXGetProcAddress( - (const GLubyte *)"glDebugMessageCallback"); - if (!p_DebugMessageCallback) { - log_error("Failed to get glDebugMessageCallback(0."); - goto glx_init_end; - } - p_DebugMessageCallback(glx_debug_msg_callback, ps); - } -#endif - } - - // Ensure we have a stencil buffer. X Fixes does not guarantee rectangles - // in regions don't overlap, so we must use stencil buffer to make sure - // we don't paint a region for more than one time, I think? - if (need_render && !ps->o.glx_no_stencil) { - GLint val = 0; - glGetIntegerv(GL_STENCIL_BITS, &val); - if (!val) { - log_error("Target window doesn't have stencil buffer."); - goto glx_init_end; - } - } - - // Check GL_ARB_texture_non_power_of_two, requires a GLX context and - // must precede FBConfig fetching - if (need_render) - psglx->has_texture_non_power_of_two = - gl_has_extension("GL_ARB_texture_non_power_of_two"); - - // Render preparations - if (need_render) { - glx_on_root_change(ps); - - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glDisable(GL_BLEND); - - if (!ps->o.glx_no_stencil) { - // Initialize stencil buffer - glClear(GL_STENCIL_BUFFER_BIT); - glDisable(GL_STENCIL_TEST); - glStencilMask(0x1); - glStencilFunc(GL_EQUAL, 0x1, 0x1); - } - - // Clear screen - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // glXSwapBuffers(ps->dpy, get_tgt_window(ps)); - } - - success = true; - -glx_init_end: - XFree(pvis); - - if (!success) - glx_destroy(ps); - - return success; -} - -static void glx_free_prog_main(glx_prog_main_t *pprogram) { - if (!pprogram) - return; - if (pprogram->prog) { - glDeleteProgram(pprogram->prog); - pprogram->prog = 0; - } - pprogram->unifm_opacity = -1; - pprogram->unifm_invert_color = -1; - pprogram->unifm_tex = -1; -} - -/** - * Destroy GLX related resources. - */ -void glx_destroy(session_t *ps) { - if (!ps->psglx) - return; - - // Free all GLX resources of windows - win_stack_foreach_managed(w, &ps->window_stack) { - free_win_res_glx(ps, w); - } - - // Free GLSL shaders/programs - for (int i = 0; i < ps->o.blur_kernel_count; ++i) { - glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; - if (ppass->frag_shader) { - glDeleteShader(ppass->frag_shader); - } - if (ppass->prog) { - glDeleteProgram(ppass->prog); - } - } - free(ps->psglx->blur_passes); - - glx_round_pass_t *ppass = ps->psglx->round_passes; - if (ppass->frag_shader) { - glDeleteShader(ppass->frag_shader); - } - if (ppass->prog) { - glDeleteProgram(ppass->prog); - } - free(ps->psglx->round_passes); - - glx_free_prog_main(&ps->glx_prog_win); - - gl_check_err(); - - // Destroy GLX context - if (ps->psglx->context) { - glXMakeCurrent(ps->dpy, None, NULL); - glXDestroyContext(ps->dpy, ps->psglx->context); - ps->psglx->context = NULL; - } - - free(ps->psglx); - ps->psglx = NULL; - ps->argb_fbconfig = NULL; -} - -/** - * Callback to run on root window size change. - */ -void glx_on_root_change(session_t *ps) { - glViewport(0, 0, ps->root_width, ps->root_height); - - // Initialize matrix, copied from dcompmgr - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, ps->root_width, 0, ps->root_height, -1000.0, 1000.0); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); -} - -/** - * Initialize GLX blur filter. - */ -bool glx_init_blur(session_t *ps) { - assert(ps->o.blur_kernel_count > 0); - assert(ps->o.blur_kerns); - assert(ps->o.blur_kerns[0]); - - // Allocate PBO if more than one blur kernel is present - if (ps->o.blur_kernel_count > 1) { - // Try to generate a framebuffer - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - if (!fbo) { - log_error("Failed to generate Framebuffer. Cannot do multi-pass " - "blur with GLX" - " backend."); - return false; - } - glDeleteFramebuffers(1, &fbo); - } - - { - char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); - // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane - // Thanks to hiciu for reporting. - setlocale(LC_NUMERIC, "C"); - - static const char *FRAG_SHADER_BLUR_PREFIX = - "#version 110\n" - "%s" - "uniform float offset_x;\n" - "uniform float offset_y;\n" - "uniform float factor_center;\n" - "uniform %s tex_scr;\n" - "\n" - "void main() {\n" - " vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);\n"; - static const char *FRAG_SHADER_BLUR_ADD = - " sum += float(%.7g) * %s(tex_scr, vec2(gl_TexCoord[0].x + offset_x " - "* float(%d), gl_TexCoord[0].y + offset_y * float(%d)));\n"; - static const char *FRAG_SHADER_BLUR_SUFFIX = - " sum += %s(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y)) * " - "factor_center;\n" - " gl_FragColor = sum / (factor_center + float(%.7g));\n" - "}\n"; - - const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two; - const char *sampler_type = (use_texture_rect ? "sampler2DRect" : "sampler2D"); - const char *texture_func = (use_texture_rect ? "texture2DRect" : "texture2D"); - const char *shader_add = FRAG_SHADER_BLUR_ADD; - char *extension = NULL; - if (use_texture_rect) { - mstrextend(&extension, "#extension GL_ARB_texture_rectangle : " - "require\n"); - } - if (!extension) { - extension = strdup(""); - } - - for (int i = 0; i < ps->o.blur_kernel_count; ++i) { - auto kern = ps->o.blur_kerns[i]; - glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; - - // Build shader - int width = kern->w, height = kern->h; - int nele = width * height - 1; - assert(nele >= 0); - auto len = - strlen(FRAG_SHADER_BLUR_PREFIX) + strlen(sampler_type) + - strlen(extension) + - (strlen(shader_add) + strlen(texture_func) + 42) * (uint)nele + - strlen(FRAG_SHADER_BLUR_SUFFIX) + strlen(texture_func) + 12 + 1; - char *shader_str = ccalloc(len, char); - char *pc = shader_str; - sprintf(pc, FRAG_SHADER_BLUR_PREFIX, extension, sampler_type); - pc += strlen(pc); - assert(strlen(shader_str) < len); - - double sum = 0.0; - for (int j = 0; j < height; ++j) { - for (int k = 0; k < width; ++k) { - if (height / 2 == j && width / 2 == k) - continue; - double val = kern->data[j * width + k]; - if (val == 0) { - continue; - } - sum += val; - sprintf(pc, shader_add, val, texture_func, - k - width / 2, j - height / 2); - pc += strlen(pc); - assert(strlen(shader_str) < len); - } - } - - sprintf(pc, FRAG_SHADER_BLUR_SUFFIX, texture_func, sum); - assert(strlen(shader_str) < len); - ppass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); - free(shader_str); - - if (!ppass->frag_shader) { - log_error("Failed to create fragment shader %d.", i); - free(extension); - free(lc_numeric_old); - return false; - } - - // Build program - ppass->prog = gl_create_program(&ppass->frag_shader, 1); - if (!ppass->prog) { - log_error("Failed to create GLSL program."); - free(extension); - free(lc_numeric_old); - return false; - } - - // Get uniform addresses -#define P_GET_UNIFM_LOC(name, target) \ - { \ - ppass->target = glGetUniformLocation(ppass->prog, name); \ - if (ppass->target < 0) { \ - log_error("Failed to get location of %d-th uniform '" name \ - "'. Might be troublesome.", \ - i); \ - } \ - } - - P_GET_UNIFM_LOC("factor_center", unifm_factor_center); - P_GET_UNIFM_LOC("offset_x", unifm_offset_x); - P_GET_UNIFM_LOC("offset_y", unifm_offset_y); - -#undef P_GET_UNIFM_LOC - } - free(extension); - - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); - } - - gl_check_err(); - - return true; -} - -/** - * Initialize GLX rounded corners filter. - */ -bool glx_init_rounded_corners(session_t *ps) { - char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); - // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane - // Thanks to hiciu for reporting. - setlocale(LC_NUMERIC, "C"); - - static const char *FRAG_SHADER = - "#version 110\n" - "%s" // extensions - "uniform float u_radius;\n" - "uniform float u_borderw;\n" - "uniform vec4 u_borderc;\n" - "uniform vec2 u_texcoord;\n" - "uniform vec2 u_texsize;\n" - "uniform vec2 u_resolution;\n" - "uniform %s tex_scr;\n" // sampler2D | sampler2DRect - "\n" - "// https://www.shadertoy.com/view/ltS3zW\n" - "float RectSDF(vec2 p, vec2 b, float r) {\n" - " vec2 d = abs(p) - b + vec2(r);\n" - " return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n" - "}\n\n" - "void main()\n" - "{\n" - " vec2 coord = vec2(u_texcoord.x, " - "u_resolution.y-u_texsize.y-u_texcoord.y);\n" - " vec4 u_v4WndBgColor = %s(tex_scr, vec2(gl_TexCoord[0].st));\n" - " float u_fRadiusPx = u_radius;\n" - " float u_fHalfBorderThickness = u_borderw / 2.0;\n" - " vec4 u_v4BorderColor = u_borderc;\n" - " vec4 u_v4FillColor = vec4(0.0, 0.0, 0.0, 0.0);\n" - " vec4 v4FromColor = u_v4BorderColor; //Always the border " - "color. If no border, this still should be set\n" - " vec4 v4ToColor = u_v4WndBgColor; //Outside color is the " - "background texture\n" - "\n" - " vec2 u_v2HalfShapeSizePx = u_texsize/2.0 - " - "vec2(u_fHalfBorderThickness);\n" - " vec2 v_v2CenteredPos = (gl_FragCoord.xy - u_texsize.xy / 2.0 - " - "coord);\n" - "\n" - " float fDist = RectSDF(v_v2CenteredPos, u_v2HalfShapeSizePx, " - "u_fRadiusPx - u_fHalfBorderThickness);\n" - " if (u_fHalfBorderThickness > 0.0) {\n" - " if (fDist < 0.0) {\n" - " v4ToColor = u_v4FillColor;\n" - " }\n" - " fDist = abs(fDist) - u_fHalfBorderThickness;\n" - " } else {\n" - " v4FromColor = u_v4FillColor;\n" - " }\n" - " float fBlendAmount = smoothstep(-1.0, 1.0, fDist);\n" - " vec4 c = mix(v4FromColor, v4ToColor, fBlendAmount);\n" - "\n" - " // final color\n" - " gl_FragColor = c;\n" - "\n" - "}\n"; - - const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two; - const char *sampler_type = (use_texture_rect ? "sampler2DRect" : "sampler2D"); - const char *texture_func = (use_texture_rect ? "texture2DRect" : "texture2D"); - char *extension = NULL; - if (use_texture_rect) { - mstrextend(&extension, "#extension GL_ARB_texture_rectangle : " - "require\n"); - } - if (!extension) { - extension = strdup(""); - } - - bool success = false; - // Build rounded corners shader - auto ppass = ps->psglx->round_passes; - auto len = strlen(FRAG_SHADER) + strlen(extension) + strlen(sampler_type) + - strlen(texture_func) + 1; - char *shader_str = ccalloc(len, char); - - sprintf(shader_str, FRAG_SHADER, extension, sampler_type, texture_func); - assert(strlen(shader_str) < len); - - log_debug("Generated rounded corners shader:\n%s\n", shader_str); - - ppass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); - free(shader_str); - - if (!ppass->frag_shader) { - log_error("Failed to create rounded corners fragment shader."); - goto out; - } - - // Build program - ppass->prog = gl_create_program(&ppass->frag_shader, 1); - if (!ppass->prog) { - log_error("Failed to create GLSL program."); - goto out; - } - - // Get uniform addresses -#define P_GET_UNIFM_LOC(name, target) \ - { \ - ppass->target = glGetUniformLocation(ppass->prog, name); \ - if (ppass->target < 0) { \ - log_debug("Failed to get location of rounded corners uniform " \ - "'" name "'. Might be troublesome."); \ - } \ - } - P_GET_UNIFM_LOC("u_radius", unifm_radius); - P_GET_UNIFM_LOC("u_texcoord", unifm_texcoord); - P_GET_UNIFM_LOC("u_texsize", unifm_texsize); - P_GET_UNIFM_LOC("u_borderw", unifm_borderw); - P_GET_UNIFM_LOC("u_borderc", unifm_borderc); - P_GET_UNIFM_LOC("u_resolution", unifm_resolution); - P_GET_UNIFM_LOC("tex_scr", unifm_tex_scr); -#undef P_GET_UNIFM_LOC - - success = true; - -out: - free(extension); - - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); - - gl_check_err(); - - return success; -} - -/** - * Load a GLSL main program from shader strings. - */ -bool glx_load_prog_main(const char *vshader_str, const char *fshader_str, - glx_prog_main_t *pprogram) { - assert(pprogram); - - // Build program - pprogram->prog = gl_create_program_from_str(vshader_str, fshader_str); - if (!pprogram->prog) { - log_error("Failed to create GLSL program."); - return false; - } - - // Get uniform addresses -#define P_GET_UNIFM_LOC(name, target) \ - { \ - pprogram->target = glGetUniformLocation(pprogram->prog, name); \ - if (pprogram->target < 0) { \ - log_error("Failed to get location of uniform '" name \ - "'. Might be troublesome."); \ - } \ - } - P_GET_UNIFM_LOC("opacity", unifm_opacity); - P_GET_UNIFM_LOC("invert_color", unifm_invert_color); - P_GET_UNIFM_LOC("tex", unifm_tex); - P_GET_UNIFM_LOC("time", unifm_time); -#undef P_GET_UNIFM_LOC - - gl_check_err(); - - return true; -} - -static inline void glx_copy_region_to_tex(session_t *ps, GLenum tex_tgt, int basex, - int basey, int dx, int dy, int width, int height) { - if (width > 0 && height > 0) { - glCopyTexSubImage2D(tex_tgt, 0, dx - basex, dy - basey, dx, - ps->root_height - dy - height, width, height); - } -} - -static inline GLuint glx_gen_texture(GLenum tex_tgt, int width, int height) { - GLuint tex = 0; - glGenTextures(1, &tex); - if (!tex) { - return 0; - } - glEnable(tex_tgt); - glBindTexture(tex_tgt, tex); - glTexParameteri(tex_tgt, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(tex_tgt, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(tex_tgt, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - glBindTexture(tex_tgt, 0); - - return tex; -} - -/** - * Bind an OpenGL texture and fill it with pixel data from back buffer - */ -bool glx_bind_texture(session_t *ps attr_unused, glx_texture_t **pptex, int x, int y, - int width, int height) { - if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) { - return true; - } - - glx_texture_t *ptex = *pptex; - - // log_trace("Copying xy(%d %d) wh(%d %d) ptex(%p)", x, y, width, height, ptex); - - // Release texture if parameters are inconsistent - if (ptex && ptex->texture && (ptex->width != width || ptex->height != height)) { - free_texture(ps, &ptex); - } - - // Allocate structure - if (!ptex) { - ptex = ccalloc(1, glx_texture_t); - *pptex = ptex; - - ptex->width = width; - ptex->height = height; - ptex->target = GL_TEXTURE_RECTANGLE; - if (ps->psglx->has_texture_non_power_of_two) { - ptex->target = GL_TEXTURE_2D; - } - } - - // Create texture - if (!ptex->texture) { - ptex->texture = glx_gen_texture(ptex->target, width, height); - } - if (!ptex->texture) { - log_error("Failed to allocate texture."); - return false; - } - - // Read destination pixels into a texture - glEnable(ptex->target); - glBindTexture(ptex->target, ptex->texture); - if (width > 0 && height > 0) { - glx_copy_region_to_tex(ps, ptex->target, x, y, x, y, width, height); - } - - // Cleanup - glBindTexture(ptex->target, 0); - glDisable(ptex->target); - - gl_check_err(); - - return true; -} - -/** - * Bind a X pixmap to an OpenGL texture. - */ -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 *fbcfg) { - if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) - return true; - - if (!pixmap) { - log_error("Binding to an empty pixmap %#010x. This can't work.", pixmap); - return false; - } - - assert(fbcfg); - glx_texture_t *ptex = *pptex; - bool need_release = true; - - // Release pixmap if parameters are inconsistent - if (ptex && ptex->texture && ptex->pixmap != pixmap) { - glx_release_pixmap(ps, ptex); - } - - // Allocate structure - if (!ptex) { - static const glx_texture_t GLX_TEX_DEF = { - .texture = 0, - .glpixmap = 0, - .pixmap = 0, - .target = 0, - .width = 0, - .height = 0, - .y_inverted = false, - }; - - ptex = cmalloc(glx_texture_t); - memcpy(ptex, &GLX_TEX_DEF, sizeof(glx_texture_t)); - *pptex = ptex; - } - - // Create GLX pixmap - int depth = 0; - if (!ptex->glpixmap) { - need_release = false; - - // Retrieve pixmap parameters, if they aren't provided - if (!width || !height) { - auto r = xcb_get_geometry_reply( - ps->c, xcb_get_geometry(ps->c, pixmap), NULL); - if (!r) { - log_error("Failed to query info of pixmap %#010x.", pixmap); - return false; - } - if (r->depth > OPENGL_MAX_DEPTH) { - log_error("Requested depth %d higher than %d.", depth, - OPENGL_MAX_DEPTH); - return false; - } - depth = r->depth; - width = r->width; - height = r->height; - free(r); - } - - // Determine texture target, copied from compiz - // The assumption we made here is the target never changes based on any - // pixmap-specific parameters, and this may change in the future - GLenum tex_tgt = 0; - if (GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts && - ps->psglx->has_texture_non_power_of_two) - tex_tgt = GLX_TEXTURE_2D_EXT; - else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & fbcfg->texture_tgts) - tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; - else if (!(GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts)) - tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; - else - tex_tgt = GLX_TEXTURE_2D_EXT; - - log_debug("depth %d, tgt %#x, rgba %d", depth, tex_tgt, - (GLX_TEXTURE_FORMAT_RGBA_EXT == fbcfg->texture_fmt)); - - GLint attrs[] = { - GLX_TEXTURE_FORMAT_EXT, - fbcfg->texture_fmt, - GLX_TEXTURE_TARGET_EXT, - (GLint)tex_tgt, - 0, - }; - - ptex->glpixmap = glXCreatePixmap(ps->dpy, fbcfg->cfg, pixmap, attrs); - ptex->pixmap = pixmap; - ptex->target = - (GLX_TEXTURE_2D_EXT == tex_tgt ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE); - ptex->width = width; - ptex->height = height; - ptex->y_inverted = fbcfg->y_inverted; - } - if (!ptex->glpixmap) { - log_error("Failed to allocate GLX pixmap."); - return false; - } - - glEnable(ptex->target); - - // Create texture - if (!ptex->texture) { - need_release = false; - - GLuint texture = 0; - glGenTextures(1, &texture); - glBindTexture(ptex->target, texture); - - glTexParameteri(ptex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(ptex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - if (repeat) { - glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_REPEAT); - } else { - glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - glBindTexture(ptex->target, 0); - - ptex->texture = texture; - } - if (!ptex->texture) { - log_error("Failed to allocate texture."); - return false; - } - - glBindTexture(ptex->target, ptex->texture); - - // The specification requires rebinding whenever the content changes... - // We can't follow this, too slow. - if (need_release) - glXReleaseTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); - - glXBindTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL); - - // Cleanup - glBindTexture(ptex->target, 0); - glDisable(ptex->target); - - gl_check_err(); - - return true; -} - -/** - * @brief Release binding of a texture. - */ -void glx_release_pixmap(session_t *ps, glx_texture_t *ptex) { - // Release binding - if (ptex->glpixmap && ptex->texture) { - glBindTexture(ptex->target, ptex->texture); - glXReleaseTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); - glBindTexture(ptex->target, 0); - } - - // Free GLX Pixmap - if (ptex->glpixmap) { - glXDestroyPixmap(ps->dpy, ptex->glpixmap); - ptex->glpixmap = 0; - } - - gl_check_err(); -} - -/** - * Set clipping region on the target window. - */ -void glx_set_clip(session_t *ps, const region_t *reg) { - // Quit if we aren't using stencils - if (ps->o.glx_no_stencil) - return; - - glDisable(GL_STENCIL_TEST); - glDisable(GL_SCISSOR_TEST); - - if (!reg) - return; - - int nrects; - const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects); - - if (nrects == 1) { - glEnable(GL_SCISSOR_TEST); - glScissor(rects[0].x1, ps->root_height - rects[0].y2, - rects[0].x2 - rects[0].x1, rects[0].y2 - rects[0].y1); - } - - gl_check_err(); -} - -#define P_PAINTREG_START(var) \ - region_t reg_new; \ - int nrects; \ - const rect_t *rects; \ - assert(width >= 0 && height >= 0); \ - pixman_region32_init_rect(®_new, dx, dy, (uint)width, (uint)height); \ - pixman_region32_intersect(®_new, ®_new, (region_t *)reg_tgt); \ - rects = pixman_region32_rectangles(®_new, &nrects); \ - glBegin(GL_QUADS); \ - \ - for (int ri = 0; ri < nrects; ++ri) { \ - rect_t var = rects[ri]; - -#define P_PAINTREG_END() \ - } \ - glEnd(); \ - \ - pixman_region32_fini(®_new); - -/** - * Blur contents in a particular region. - * - * XXX seems to be way to complex for what it does - */ -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) { - assert(ps->psglx->blur_passes[0].prog); - const bool more_passes = ps->o.blur_kernel_count > 1; - const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST); - const bool have_stencil = glIsEnabled(GL_STENCIL_TEST); - bool ret = false; - - // Calculate copy region size - glx_blur_cache_t ibc = {.width = 0, .height = 0}; - if (!pbc) - pbc = &ibc; - - int mdx = dx, mdy = dy, mwidth = width, mheight = height; - // log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight); - - /* - if (ps->o.resize_damage > 0) { - int inc_x = 0, inc_y = 0; - for (int i = 0; i < MAX_BLUR_PASS; ++i) { - XFixed *kern = ps->o.blur_kerns[i]; - if (!kern) break; - inc_x += XFIXED_TO_DOUBLE(kern[0]) / 2; - inc_y += XFIXED_TO_DOUBLE(kern[1]) / 2; - } - inc_x = min2(ps->o.resize_damage, inc_x); - inc_y = min2(ps->o.resize_damage, inc_y); - - mdx = max2(dx - inc_x, 0); - mdy = max2(dy - inc_y, 0); - int mdx2 = min2(dx + width + inc_x, ps->root_width), - mdy2 = min2(dy + height + inc_y, ps->root_height); - mwidth = mdx2 - mdx; - mheight = mdy2 - mdy; - } - */ - - GLenum tex_tgt = GL_TEXTURE_RECTANGLE; - if (ps->psglx->has_texture_non_power_of_two) - tex_tgt = GL_TEXTURE_2D; - - // Free textures if size inconsistency discovered - if (mwidth != pbc->width || mheight != pbc->height) - free_glx_bc_resize(ps, pbc); - - // Generate FBO and textures if needed - if (!pbc->textures[0]) - pbc->textures[0] = glx_gen_texture(tex_tgt, mwidth, mheight); - GLuint tex_scr = pbc->textures[0]; - if (more_passes && !pbc->textures[1]) - pbc->textures[1] = glx_gen_texture(tex_tgt, mwidth, mheight); - pbc->width = mwidth; - pbc->height = mheight; - GLuint tex_scr2 = pbc->textures[1]; - if (more_passes && !pbc->fbo) - glGenFramebuffers(1, &pbc->fbo); - const GLuint fbo = pbc->fbo; - - if (!tex_scr || (more_passes && !tex_scr2)) { - log_error("Failed to allocate texture."); - goto glx_blur_dst_end; - } - if (more_passes && !fbo) { - log_error("Failed to allocate framebuffer."); - goto glx_blur_dst_end; - } - - // Read destination pixels into a texture - glEnable(tex_tgt); - glBindTexture(tex_tgt, tex_scr); - glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, mheight); - /* - if (tex_scr2) { - glBindTexture(tex_tgt, tex_scr2); - glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, dx - mdx); - glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy + height, - mwidth, mdy + mheight - dy - height); - glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy, dx - mdx, height); - glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, dx + width, dy, - mdx + mwidth - dx - width, height); - } */ - - // Texture scaling factor - GLfloat texfac_x = 1.0f, texfac_y = 1.0f; - if (tex_tgt == GL_TEXTURE_2D) { - texfac_x /= (GLfloat)mwidth; - texfac_y /= (GLfloat)mheight; - } - - // Paint it back - if (more_passes) { - glDisable(GL_STENCIL_TEST); - glDisable(GL_SCISSOR_TEST); - } - - bool last_pass = false; - for (int i = 0; i < ps->o.blur_kernel_count; ++i) { - last_pass = (i == ps->o.blur_kernel_count - 1); - const glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; - assert(ppass->prog); - - assert(tex_scr); - glBindTexture(tex_tgt, tex_scr); - - if (!last_pass) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, tex_scr2, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - log_error("Framebuffer attachment failed."); - goto glx_blur_dst_end; - } - } else { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDrawBuffer(GL_BACK); - if (have_scissors) - glEnable(GL_SCISSOR_TEST); - if (have_stencil) - glEnable(GL_STENCIL_TEST); - } - - // Color negation for testing... - // glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - // glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); - // glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR); - - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glUseProgram(ppass->prog); - if (ppass->unifm_offset_x >= 0) - glUniform1f(ppass->unifm_offset_x, texfac_x); - if (ppass->unifm_offset_y >= 0) - glUniform1f(ppass->unifm_offset_y, texfac_y); - if (ppass->unifm_factor_center >= 0) - glUniform1f(ppass->unifm_factor_center, factor_center); - - P_PAINTREG_START(crect) { - auto rx = (GLfloat)(crect.x1 - mdx) * texfac_x; - auto ry = (GLfloat)(mheight - (crect.y1 - mdy)) * texfac_y; - auto rxe = rx + (GLfloat)(crect.x2 - crect.x1) * texfac_x; - auto rye = ry - (GLfloat)(crect.y2 - crect.y1) * texfac_y; - auto rdx = (GLfloat)(crect.x1 - mdx); - auto rdy = (GLfloat)(mheight - crect.y1 + mdy); - if (last_pass) { - rdx = (GLfloat)crect.x1; - rdy = (GLfloat)(ps->root_height - crect.y1); - } - auto rdxe = rdx + (GLfloat)(crect.x2 - crect.x1); - auto rdye = rdy - (GLfloat)(crect.y2 - crect.y1); - - // log_trace("%f, %f, %f, %f -> %f, %f, %f, %f", rx, ry, - // rxe, rye, rdx, - // rdy, rdxe, rdye); - - glTexCoord2f(rx, ry); - glVertex3f(rdx, rdy, z); - - glTexCoord2f(rxe, ry); - glVertex3f(rdxe, rdy, z); - - glTexCoord2f(rxe, rye); - glVertex3f(rdxe, rdye, z); - - glTexCoord2f(rx, rye); - glVertex3f(rdx, rdye, z); - } - P_PAINTREG_END(); - - glUseProgram(0); - - // Swap tex_scr and tex_scr2 - { - GLuint tmp = tex_scr2; - tex_scr2 = tex_scr; - tex_scr = tmp; - } - } - - ret = true; - -glx_blur_dst_end: - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glBindTexture(tex_tgt, 0); - glDisable(tex_tgt); - if (have_scissors) - glEnable(GL_SCISSOR_TEST); - if (have_stencil) - glEnable(GL_STENCIL_TEST); - - if (&ibc == pbc) { - free_glx_bc(ps, pbc); - } - - gl_check_err(); - - return ret; -} - -// TODO(bhagwan) this is a mess and needs a more consistent way of getting the border -// pixel I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in -// xcb_create_window()) or a way to get the pixels from xcb_render_picture_t but the -// documentation for the xcb_xrender extension is literaly non existent... -// -// NOTE(yshui) There is no consistent way to get the "border" color of a X window. From -// the WM's perspective there are multiple ways to implement window borders. Using -// glReadPixel is probably the most reliable way. -void glx_read_border_pixel(int root_height, int root_width, int x, int y, int width, - int height, float *ppixel) { - assert(ppixel); - - // Reset the color so the shader doesn't use it - ppixel[0] = ppixel[1] = ppixel[2] = ppixel[3] = -1.0F; - - // First try bottom left corner past the - // circle radius (after the rounded corner ends) - auto screen_x = x; - auto screen_y = root_height - height - y; - - // X is out of bounds - // move to the right side - if (screen_x < 0) { - screen_x += width; - } - - // Y is out of bounds - // move to to top part - if (screen_y < 0) { - screen_y += height; - } - - // All corners are out of bounds, give up - if (screen_x < 0 || screen_y < 0 || screen_x >= root_width || screen_y >= root_height) { - return; - } - - // Invert Y-axis so we can query border color from texture (0,0) - glReadPixels(screen_x, screen_y, 1, 1, GL_RGBA, GL_FLOAT, (void *)ppixel); - - log_trace("xy(%d, %d), glxy(%d %d) wh(%d %d), border_col(%.2f, %.2f, %.2f, %.2f)", - x, y, screen_x, screen_y, width, height, (float)ppixel[0], - (float)ppixel[1], (float)ppixel[2], (float)ppixel[3]); - - gl_check_err(); -} - -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) { - assert(ps->psglx->round_passes->prog); - bool ret = false; - - // log_warn("dxy(%d, %d) wh(%d %d) rwh(%d %d) b(%d), f(%d)", - // dx, dy, width, height, ps->root_width, ps->root_height, w->g.border_width, - // w->focused); - - int mdx = dx, mdy = dy, mwidth = width, mheight = height; - log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight); - - if (w->g.border_width > 0) { - glx_read_border_pixel(ps->root_height, ps->root_width, dx, dy, width, - height, &w->border_col[0]); - } - - { - const glx_round_pass_t *ppass = ps->psglx->round_passes; - assert(ppass->prog); - - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - glUseProgram(ppass->prog); - - // If caller specified a texture use it as source - log_trace("ptex: %p wh(%d %d) %d %d", ptex, ptex->width, ptex->height, - ptex->target, ptex->texture); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(ptex->target, ptex->texture); - - if (ppass->unifm_tex_scr >= 0) { - glUniform1i(ppass->unifm_tex_scr, (GLint)0); - } - if (ppass->unifm_radius >= 0) { - glUniform1f(ppass->unifm_radius, cr); - } - if (ppass->unifm_texcoord >= 0) { - glUniform2f(ppass->unifm_texcoord, (float)dx, (float)dy); - } - if (ppass->unifm_texsize >= 0) { - glUniform2f(ppass->unifm_texsize, (float)mwidth, (float)mheight); - } - if (ppass->unifm_borderw >= 0) { - // Don't render rounded border if we don't know the border color - glUniform1f(ppass->unifm_borderw, - w->border_col[0] != -1. ? (GLfloat)w->g.border_width : 0); - } - if (ppass->unifm_borderc >= 0) { - glUniform4f(ppass->unifm_borderc, w->border_col[0], - w->border_col[1], w->border_col[2], w->border_col[3]); - } - if (ppass->unifm_resolution >= 0) { - glUniform2f(ppass->unifm_resolution, (float)ps->root_width, - (float)ps->root_height); - } - - // Painting - { - P_PAINTREG_START(crect) { - // texture-local coordinates - auto rx = (GLfloat)(crect.x1 - dx); - auto ry = (GLfloat)(crect.y1 - dy); - auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); - auto rye = ry + (GLfloat)(crect.y2 - crect.y1); - if (GL_TEXTURE_2D == ptex->target) { - rx = rx / (GLfloat)width; - ry = ry / (GLfloat)height; - rxe = rxe / (GLfloat)width; - rye = rye / (GLfloat)height; - } - - // coordinates for the texture in the target - auto rdx = (GLfloat)crect.x1; - auto rdy = (GLfloat)(ps->root_height - crect.y1); - auto rdxe = (GLfloat)rdx + (GLfloat)(crect.x2 - crect.x1); - auto rdye = (GLfloat)rdy - (GLfloat)(crect.y2 - crect.y1); - - // Invert Y if needed, this may not work as expected, - // though. I don't have such a FBConfig to test with. - ry = 1.0F - ry; - rye = 1.0F - rye; - - // log_trace("Rect %d (i:%d): %f, %f, %f, %f -> %f, %f, - // %f, %f", ri ,ptex ? ptex->y_inverted : -1, rx, ry, - // rxe, - // rye, rdx, rdy, rdxe, rdye); - - glTexCoord2f(rx, ry); - glVertex3f(rdx, rdy, z); - - glTexCoord2f(rxe, ry); - glVertex3f(rdxe, rdy, z); - - glTexCoord2f(rxe, rye); - glVertex3f(rdxe, rdye, z); - - glTexCoord2f(rx, rye); - glVertex3f(rdx, rdye, z); - } - P_PAINTREG_END(); - } - - glUseProgram(0); - glDisable(GL_BLEND); - } - - ret = true; - - glBindTexture(ptex->target, 0); - glDisable(ptex->target); - glDisable(GL_BLEND); - - gl_check_err(); - - return ret; -} - -bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, - GLfloat factor, const region_t *reg_tgt) { - // It's possible to dim in glx_render(), but it would be over-complicated - // considering all those mess in color negation and modulation - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(0.0f, 0.0f, 0.0f, factor); - - P_PAINTREG_START(crect) { - // XXX what does all of these variables mean? - GLint rdx = crect.x1; - GLint rdy = ps->root_height - crect.y1; - GLint rdxe = rdx + (crect.x2 - crect.x1); - GLint rdye = rdy - (crect.y2 - crect.y1); - - glVertex3i(rdx, rdy, z); - glVertex3i(rdxe, rdy, z); - glVertex3i(rdxe, rdye, z); - glVertex3i(rdx, rdye, z); - } - P_PAINTREG_END(); - - glColor4f(0.0f, 0.0f, 0.0f, 0.0f); - glDisable(GL_BLEND); - - gl_check_err(); - - return true; -} - -/** - * @brief Render a region with texture data. - */ -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) { - if (!ptex || !ptex->texture) { - log_error("Missing texture."); - return false; - } - - const bool has_prog = pprogram && pprogram->prog; - bool dual_texture = false; - - // It's required by legacy versions of OpenGL to enable texture target - // before specifying environment. Thanks to madsy for telling me. - glEnable(ptex->target); - - // Enable blending if needed - if (opacity < 1.0 || argb) { - - glEnable(GL_BLEND); - - // Needed for handling opacity of ARGB texture - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - - // This is all weird, but X Render is using premultiplied ARGB format, and - // we need to use those things to correct it. Thanks to derhass for help. - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glColor4d(opacity, opacity, opacity, opacity); - } - - if (!has_prog) { - // The default, fixed-function path - // Color negation - if (neg) { - // Simple color negation - if (!glIsEnabled(GL_BLEND)) { - glEnable(GL_COLOR_LOGIC_OP); - glLogicOp(GL_COPY_INVERTED); - } - // ARGB texture color negation - else if (argb) { - dual_texture = true; - - // Use two texture stages because the calculation is too - // complicated, thanks to madsy for providing code Texture - // stage 0 - glActiveTexture(GL_TEXTURE0); - - // Negation for premultiplied color: color = A - C - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_SUBTRACT); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); - - // Pass texture alpha through - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); - - // Texture stage 1 - glActiveTexture(GL_TEXTURE1); - glEnable(ptex->target); - glBindTexture(ptex->target, ptex->texture); - - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - - // Modulation with constant factor - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA); - - // Modulation with constant factor - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); - - glActiveTexture(GL_TEXTURE0); - } - // RGB blend color negation - else { - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - - // Modulation with constant factor - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, - GL_ONE_MINUS_SRC_COLOR); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); - - // Modulation with constant factor - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); - } - } - } else { - // Programmable path - assert(pprogram->prog); - glUseProgram(pprogram->prog); - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - if (pprogram->unifm_opacity >= 0) - glUniform1f(pprogram->unifm_opacity, (float)opacity); - if (pprogram->unifm_invert_color >= 0) - glUniform1i(pprogram->unifm_invert_color, neg); - if (pprogram->unifm_tex >= 0) - glUniform1i(pprogram->unifm_tex, 0); - if (pprogram->unifm_time >= 0) - glUniform1f(pprogram->unifm_time, (float)ts.tv_sec * 1000.0f + - (float)ts.tv_nsec / 1.0e6f); - } - - // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d", x, y, width, height, - // dx, dy, ptex->width, ptex->height, z); - - // Bind texture - glBindTexture(ptex->target, ptex->texture); - if (dual_texture) { - glActiveTexture(GL_TEXTURE1); - glBindTexture(ptex->target, ptex->texture); - glActiveTexture(GL_TEXTURE0); - } - - // Painting - { - P_PAINTREG_START(crect) { - // texture-local coordinates - auto rx = (GLfloat)(crect.x1 - dx + x); - auto ry = (GLfloat)(crect.y1 - dy + y); - auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); - auto rye = ry + (GLfloat)(crect.y2 - crect.y1); - // Rectangle textures have [0-w] [0-h] while 2D texture has [0-1] - // [0-1] Thanks to amonakov for pointing out! - if (GL_TEXTURE_2D == ptex->target) { - rx = rx / (GLfloat)ptex->width; - ry = ry / (GLfloat)ptex->height; - rxe = rxe / (GLfloat)ptex->width; - rye = rye / (GLfloat)ptex->height; - } - - // coordinates for the texture in the target - GLint rdx = crect.x1; - GLint rdy = ps->root_height - crect.y1; - GLint rdxe = rdx + (crect.x2 - crect.x1); - GLint rdye = rdy - (crect.y2 - crect.y1); - - // Invert Y if needed, this may not work as expected, though. I - // don't have such a FBConfig to test with. - if (!ptex->y_inverted) { - ry = 1.0f - ry; - rye = 1.0f - rye; - } - - // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", ri, rx, - // ry, rxe, rye, - // rdx, rdy, rdxe, rdye); - -#define P_TEXCOORD(cx, cy) \ - { \ - if (dual_texture) { \ - glMultiTexCoord2f(GL_TEXTURE0, cx, cy); \ - glMultiTexCoord2f(GL_TEXTURE1, cx, cy); \ - } else \ - glTexCoord2f(cx, cy); \ - } - P_TEXCOORD(rx, ry); - glVertex3i(rdx, rdy, z); - - P_TEXCOORD(rxe, ry); - glVertex3i(rdxe, rdy, z); - - P_TEXCOORD(rxe, rye); - glVertex3i(rdxe, rdye, z); - - P_TEXCOORD(rx, rye); - glVertex3i(rdx, rdye, z); - } - P_PAINTREG_END(); - } - - // Cleanup - glBindTexture(ptex->target, 0); - glColor4f(0.0f, 0.0f, 0.0f, 0.0f); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glDisable(GL_BLEND); - glDisable(GL_COLOR_LOGIC_OP); - glDisable(ptex->target); - - if (dual_texture) { - glActiveTexture(GL_TEXTURE1); - glBindTexture(ptex->target, 0); - glDisable(ptex->target); - glActiveTexture(GL_TEXTURE0); - } - - if (has_prog) - glUseProgram(0); - - gl_check_err(); - - return true; -} diff --git a/src/opengl.h b/src/opengl.h deleted file mode 100644 index dcd8697..0000000 --- a/src/opengl.h +++ /dev/null @@ -1,246 +0,0 @@ -// 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 -} diff --git a/src/options.c b/src/options.c deleted file mode 100644 index 6a7bb49..0000000 --- a/src/options.c +++ /dev/null @@ -1,1128 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> - -#include <getopt.h> -#include <locale.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <xcb/render.h> // for xcb_render_fixed_t, XXX - -#include "backend/backend.h" -#include "common.h" -#include "config.h" -#include "log.h" -#include "options.h" -#include "utils.h" -#include "win.h" - -#pragma GCC diagnostic error "-Wunused-parameter" - -/** - * Print usage text. - */ -static void usage(const char *argv0, int ret) { -#define WARNING_DISABLED " (DISABLED AT COMPILE TIME)" - static const char *usage_text = - "picom (" COMPTON_VERSION ")\n" - "Please report bugs to https://github.com/yshui/picom\n\n" - "usage: %s [options]\n" - "Options:\n" - "\n" - "-r radius\n" - " The blur radius for shadows. (default 12)\n" - "\n" - "-o opacity\n" - " The translucency for shadows. (default .75)\n" - "\n" - "-l left-offset\n" - " The left offset for shadows. (default -15)\n" - "\n" - "-t top-offset\n" - " The top offset for shadows. (default -15)\n" - "\n" - "-I fade-in-step\n" - " Opacity change between steps while fading in. (default 0.028)\n" - "\n" - "-O fade-out-step\n" - " Opacity change between steps while fading out. (default 0.03)\n" - "\n" - "-D fade-delta-time\n" - " The time between steps in a fade in milliseconds. (default 10)\n" - "\n" - "-m opacity\n" - " The opacity for menus. (default 1.0)\n" - "\n" - "-c\n" - " Enabled client-side shadows on windows.\n" - "\n" - "-C\n" - " Avoid drawing shadows on dock/panel windows.\n" - "\n" - "-z\n" - " Zero the part of the shadow's mask behind the window.\n" - "\n" - "-f\n" - " Fade windows in/out when opening/closing and when opacity\n" - " changes, unless --no-fading-openclose is used.\n" - "\n" - "-F\n" - " Equals to -f. Deprecated.\n" - "\n" - "--animations\n" - " Run animations for window geometry changes (movement and scaling).\n" - "\n" - "--animation-for-open-window\n" - " Which animation to run when opening a window.\n" - " Must be one of `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " (default: none).\n" - "\n" - "--animation-for-transient-window\n" - " Which animation to run when opening a transient window.\n" - " Must be one of `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " (default: none).\n" - "\n" - "--animation-for-unmap-window\n" - " Which animation to run when hiding (e.g. minimize) a window.\n" - " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " `slide-in`, `slide-out`\n" - " (default: auto).\n" - "\n" - "--animation-for-workspace-switch-in\n" - " Which animation to run on switching workspace for windows\n" - " comming into view.\n" - " IMPORTANT: window manager must set _NET_CURRENT_DESKTOP\n" - " before doing the hide/show of windows\n" - " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " `slide-in`, `slide-out`\n" - " (default: auto).\n" - "\n" - "--animation-for-workspace-switch-out\n" - " Which animation to run on switching workspace for windows\n" - " going out of view.\n" - " IMPORTANT: window manager must set _NET_CURRENT_DESKTOP\n" - " before doing the hide/show of windows\n" - " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n" - " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" - " `slide-in`, `slide-out`\n" - " (default: auto).\n" - "\n" - "--animation-stiffness\n" - " Stiffness (a.k.a. tension) parameter for animation (default: 200.0).\n" - "\n" - "--animation-dampening\n" - " Dampening (a.k.a. friction) parameter for animation (default: 25.0).\n" - "\n" - "--animation-window-mass\n" - " Mass parameter for animation (default: 1.0).\n" - "\n" - "--animation-delta\n" - " The time between steps in animation, in milliseconds. (> 0, defaults to 10).\n" - "\n" - "--animation-force-steps\n" - " Force animations to go step by step even if cpu usage is high \n" - " (default: false)\n" - "\n" - "--animation-clamping\n" - " Whether to clamp animations (default: true)\n" - "\n" - "-i opacity\n" - " Opacity of inactive windows. (0.1 - 1.0)\n" - "\n" - "-e opacity\n" - " Opacity of window titlebars and borders. (0.1 - 1.0)\n" - "\n" - "-G\n" - " Don't draw shadows on DND windows\n" - "\n" - "-b\n" - " Daemonize process.\n" - "\n" - "--show-all-xerrors\n" - " Show all X errors (for debugging).\n" - "\n" - "--config path\n" - " Look for configuration file at the path. Use /dev/null to avoid\n" - " loading configuration file." -#ifndef CONFIG_LIBCONFIG - WARNING_DISABLED -#endif - "\n\n" - "--write-pid-path path\n" - " Write process ID to a file.\n" - "\n" - "--shadow-color color\n" - " Color of shadow, as a hex RGB string (defaults to #000000)\n" - "\n" - "--shadow-red value\n" - " Red color value of shadow (0.0 - 1.0, defaults to 0).\n" - "\n" - "--shadow-green value\n" - " Green color value of shadow (0.0 - 1.0, defaults to 0).\n" - "\n" - "--shadow-blue value\n" - " Blue color value of shadow (0.0 - 1.0, defaults to 0).\n" - "\n" - "--inactive-opacity-override\n" - " Inactive opacity set by -i overrides value of _NET_WM_OPACITY.\n" - "\n" - "--inactive-dim value\n" - " Dim inactive windows. (0.0 - 1.0, defaults to 0)\n" - "\n" - "--active-opacity opacity\n" - " Default opacity for active windows. (0.0 - 1.0)\n" - "\n" - "--corner-radius value\n" - " Sets the radius of rounded window corners. When > 0, the compositor\n" - " will round the corners of windows. (defaults to 0).\n" - "\n" - "--rounded-corners-exclude condition\n" - " Exclude conditions for rounded corners.\n" - "\n" - "--mark-wmwin-focused\n" - " Try to detect WM windows and mark them as active.\n" - "\n" - "--shadow-exclude condition\n" - " Exclude conditions for shadows.\n" - "\n" - "--fade-exclude condition\n" - " Exclude conditions for fading.\n" - "\n" - "--mark-ovredir-focused\n" - " Mark windows that have no WM frame as active.\n" - "\n" - "--no-fading-openclose\n" - " Do not fade on window open/close.\n" - "\n" - "--no-fading-destroyed-argb\n" - " Do not fade destroyed ARGB windows with WM frame. Workaround of bugs\n" - " in Openbox, Fluxbox, etc.\n" - "\n" - "--shadow-ignore-shaped\n" - " Do not paint shadows on shaped windows. (Deprecated, use\n" - " --shadow-exclude \'bounding_shaped\' or\n" - " --shadow-exclude \'bounding_shaped && !rounded_corners\' instead.)\n" - "\n" - "--detect-rounded-corners\n" - " Try to detect windows with rounded corners and don't consider\n" - " them shaped windows. Affects --shadow-ignore-shaped,\n" - " --unredir-if-possible, and possibly others. You need to turn this\n" - " on manually if you want to match against rounded_corners in\n" - " conditions.\n" - "\n" - "--detect-client-opacity\n" - " Detect _NET_WM_OPACITY on client windows, useful for window\n" - " managers not passing _NET_WM_OPACITY of client windows to frame\n" - " windows.\n" - "\n" - "--refresh-rate val\n" - " Specify refresh rate of the screen. If not specified or 0, we\n" - " will try detecting this with X RandR extension.\n" - "\n" - "--vsync\n" - " Enable VSync\n" - "\n" - "--paint-on-overlay\n" - " Painting on X Composite overlay window.\n" - "\n" - "--use-ewmh-active-win\n" - " Use _NET_WM_ACTIVE_WINDOW on the root window to determine which\n" - " window is focused instead of using FocusIn/Out events.\n" - "\n" - "--unredir-if-possible\n" - " Unredirect all windows if a full-screen opaque window is\n" - " detected, to maximize performance for full-screen windows.\n" - "\n" - "--unredir-if-possible-delay ms\n" - " Delay before unredirecting the window, in milliseconds.\n" - " Defaults to 0.\n" - "\n" - "--unredir-if-possible-exclude condition\n" - " Conditions of windows that shouldn't be considered full-screen\n" - " for unredirecting screen.\n" - "\n" - "--focus-exclude condition\n" - " Specify a list of conditions of windows that should always be\n" - " considered focused.\n" - "\n" - "--inactive-dim-fixed\n" - " Use fixed inactive dim value.\n" - "\n" - "--max-brightness\n" - " Dims windows which average brightness is above this threshold.\n" - " Requires --no-use-damage.\n" - " Default: 1.0 or no dimming.\n" - "\n" - "--detect-transient\n" - " Use WM_TRANSIENT_FOR to group windows, and consider windows in\n" - " the same group focused at the same time.\n" - "\n" - "--detect-client-leader\n" - " Use WM_CLIENT_LEADER to group windows, and consider windows in\n" - " the same group focused at the same time. WM_TRANSIENT_FOR has\n" - " higher priority if --detect-transient is enabled, too.\n" - "\n" - "--blur-method\n" - " The algorithm used for background bluring. Available choices are:\n" - " 'none' to disable, 'gaussian', 'box' or 'kernel' for custom\n" - " convolution blur with --blur-kern.\n" - " Note: 'gaussian' and 'box' require --experimental-backends.\n" - "\n" - "--blur-size\n" - " The radius of the blur kernel for 'box' and 'gaussian' blur method.\n" - "\n" - "--blur-deviation\n" - " The standard deviation for the 'gaussian' blur method.\n" - "\n" - "--blur-strength\n" - " The strength level of the 'dual_kawase' blur method.\n" - "\n" - "--blur-background\n" - " Blur background of semi-transparent / ARGB windows. Bad in\n" - " performance. The switch name may change without prior\n" - " notifications.\n" - "\n" - "--blur-background-frame\n" - " Blur background of windows when the window frame is not opaque.\n" - " Implies --blur-background. Bad in performance. The switch name\n" - " may change.\n" - "\n" - "--blur-background-fixed\n" - " Use fixed blur strength instead of adjusting according to window\n" - " opacity.\n" - "\n" - "--blur-kern matrix\n" - " Specify the blur convolution kernel, with the following format:\n" - " WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5...\n" - " The element in the center must not be included, it will be forever\n" - " 1.0 or changing based on opacity, depending on whether you have\n" - " --blur-background-fixed.\n" - " A 7x7 Gaussian blur kernel looks like:\n" - " --blur-kern " - "'7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0." - "000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000849,0." - "029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.001723,0.059106,0." - "493069,0.493069,0.059106,0.001723,0.000849,0.029143,0.243117,0.493069,0." - "243117,0.029143,0.000849,0.000102,0.003494,0.029143,0.059106,0.029143,0." - "003494,0.000102,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0." - "000003'\n" - " Up to 4 blur kernels may be specified, separated with semicolon, for\n" - " multi-pass blur.\n" - " May also be one the predefined kernels: 3x3box (default), 5x5box,\n" - " 7x7box, 3x3gaussian, 5x5gaussian, 7x7gaussian, 9x9gaussian,\n" - " 11x11gaussian.\n" - "\n" - "--blur-background-exclude condition\n" - " Exclude conditions for background blur.\n" - "\n" - "--resize-damage integer\n" - " Resize damaged region by a specific number of pixels. A positive\n" - " value enlarges it while a negative one shrinks it. Useful for\n" - " fixing the line corruption issues of blur. May or may not\n" - " work with --glx-no-stencil. Shrinking doesn't function correctly.\n" - "\n" - "--invert-color-include condition\n" - " Specify a list of conditions of windows that should be painted with\n" - " inverted color. Resource-hogging, and is not well tested.\n" - "\n" - "--opacity-rule opacity:condition\n" - " Specify a list of opacity rules, in the format \"PERCENT:PATTERN\",\n" - " like \'50:name *= \"Firefox\"'. picom-trans is recommended over\n" - " this. Note we do not distinguish 100%% and unset, and we don't make\n" - " any guarantee about possible conflicts with other programs that set\n" - " _NET_WM_WINDOW_OPACITY on frame or client windows.\n" - "\n" - "--shadow-exclude-reg geometry\n" - " Specify a X geometry that describes the region in which shadow\n" - " should not be painted in, such as a dock window region.\n" - " Use --shadow-exclude-reg \'x10+0-0\', for example, if the 10 pixels\n" - " on the bottom of the screen should not have shadows painted on.\n" - "\n" - "--clip-shadow-above condition\n" - " Specify a list of conditions of windows to not paint a shadow over,\n" - " such as a dock window.\n" - "\n" - "--xinerama-shadow-crop\n" - " Crop shadow of a window fully on a particular Xinerama screen to the\n" - " screen.\n" - "\n" - "--backend backend\n" - " Choose backend. Possible choices are xrender, glx, and\n" - " xr_glx_hybrid." -#ifndef CONFIG_OPENGL - " (GLX BACKENDS DISABLED AT COMPILE TIME)" -#endif - "\n\n" - "--glx-no-stencil\n" - " GLX backend: Avoid using stencil buffer. Might cause issues\n" - " when rendering transparent content. My tests show a 15%% performance\n" - " boost.\n" - "\n" - "--glx-no-rebind-pixmap\n" - " GLX backend: Avoid rebinding pixmap on window damage. Probably\n" - " could improve performance on rapid window content changes, but is\n" - " known to break things on some drivers (LLVMpipe, xf86-video-intel,\n" - " etc.).\n" - "\n" - "--no-use-damage\n" - " Disable the use of damage information. This cause the whole screen to\n" - " be redrawn everytime, instead of the part of the screen that has\n" - " actually changed. Potentially degrades the performance, but might fix\n" - " some artifacts.\n" - "\n" - "--xrender-sync-fence\n" - " Additionally use X Sync fence to sync clients' draw calls. Needed\n" - " on nvidia-drivers with GLX backend for some users.\n" - "\n" - "--force-win-blend\n" - " Force all windows to be painted with blending. Useful if you have a\n" - " --glx-fshader-win that could turn opaque pixels transparent.\n" - "\n" - "--dbus\n" - " Enable remote control via D-Bus. See the D-BUS API section in the\n" - " man page for more details." -#ifndef CONFIG_DBUS - WARNING_DISABLED -#endif - "\n\n" - "--benchmark cycles\n" - " Benchmark mode. Repeatedly paint until reaching the specified cycles.\n" - "\n" - "--benchmark-wid window-id\n" - " Specify window ID to repaint in benchmark mode. If omitted or is 0,\n" - " the whole screen is repainted.\n" - "\n" - "--monitor-repaint\n" - " Highlight the updated area of the screen. For debugging the xrender\n" - " backend only.\n" - "\n" - "--debug-mode\n" - " Render into a separate window, and don't take over the screen. Useful\n" - " when you want to attach a debugger to picom\n" - "\n" - "--no-ewmh-fullscreen\n" - " Do not use EWMH to detect fullscreen windows. Reverts to checking\n" - " if a window is fullscreen based only on its size and coordinates.\n" - "\n" - "--transparent-clipping\n" - " Make transparent windows clip other windows like non-transparent windows\n" - " do, instead of blending on top of them\n"; - FILE *f = (ret ? stderr : stdout); - fprintf(f, usage_text, argv0); -#undef WARNING_DISABLED -} - -static const char *shortopts = "D:I:O:d:r:o:m:l:t:i:e:hscnfFCaSzGb"; -static const struct option longopts[] = { - {"help", no_argument, NULL, 'h'}, - {"config", required_argument, NULL, 256}, - {"shadow-radius", required_argument, NULL, 'r'}, - {"shadow-opacity", required_argument, NULL, 'o'}, - {"shadow-offset-x", required_argument, NULL, 'l'}, - {"shadow-offset-y", required_argument, NULL, 't'}, - {"fade-in-step", required_argument, NULL, 'I'}, - {"fade-out-step", required_argument, NULL, 'O'}, - {"fade-delta", required_argument, NULL, 'D'}, - {"menu-opacity", required_argument, NULL, 'm'}, - {"shadow", no_argument, NULL, 'c'}, - {"no-dock-shadow", no_argument, NULL, 'C'}, - {"clear-shadow", no_argument, NULL, 'z'}, - {"fading", no_argument, NULL, 'f'}, - {"inactive-opacity", required_argument, NULL, 'i'}, - {"frame-opacity", required_argument, NULL, 'e'}, - {"daemon", no_argument, NULL, 'b'}, - {"no-dnd-shadow", no_argument, NULL, 'G'}, - {"shadow-red", required_argument, NULL, 257}, - {"shadow-green", required_argument, NULL, 258}, - {"shadow-blue", required_argument, NULL, 259}, - {"inactive-opacity-override", no_argument, NULL, 260}, - {"inactive-dim", required_argument, NULL, 261}, - {"mark-wmwin-focused", no_argument, NULL, 262}, - {"shadow-exclude", required_argument, NULL, 263}, - {"mark-ovredir-focused", no_argument, NULL, 264}, - {"no-fading-openclose", no_argument, NULL, 265}, - {"shadow-ignore-shaped", no_argument, NULL, 266}, - {"detect-rounded-corners", no_argument, NULL, 267}, - {"detect-client-opacity", no_argument, NULL, 268}, - {"refresh-rate", required_argument, NULL, 269}, - {"vsync", optional_argument, NULL, 270}, - {"alpha-step", required_argument, NULL, 271}, - {"dbe", no_argument, NULL, 272}, - {"paint-on-overlay", no_argument, NULL, 273}, - {"sw-opti", no_argument, NULL, 274}, - {"vsync-aggressive", no_argument, NULL, 275}, - {"use-ewmh-active-win", no_argument, NULL, 276}, - {"respect-prop-shadow", no_argument, NULL, 277}, - {"unredir-if-possible", no_argument, NULL, 278}, - {"focus-exclude", required_argument, NULL, 279}, - {"inactive-dim-fixed", no_argument, NULL, 280}, - {"detect-transient", no_argument, NULL, 281}, - {"detect-client-leader", no_argument, NULL, 282}, - {"blur-background", no_argument, NULL, 283}, - {"blur-background-frame", no_argument, NULL, 284}, - {"blur-background-fixed", no_argument, NULL, 285}, - {"dbus", no_argument, NULL, 286}, - {"logpath", required_argument, NULL, 287}, - {"invert-color-include", required_argument, NULL, 288}, - {"opengl", no_argument, NULL, 289}, - {"backend", required_argument, NULL, 290}, - {"glx-no-stencil", no_argument, NULL, 291}, - {"benchmark", required_argument, NULL, 293}, - {"benchmark-wid", required_argument, NULL, 294}, - {"blur-background-exclude", required_argument, NULL, 296}, - {"active-opacity", required_argument, NULL, 297}, - {"glx-no-rebind-pixmap", no_argument, NULL, 298}, - {"glx-swap-method", required_argument, NULL, 299}, - {"fade-exclude", required_argument, NULL, 300}, - {"blur-kern", required_argument, NULL, 301}, - {"resize-damage", required_argument, NULL, 302}, - {"glx-use-gpushader4", no_argument, NULL, 303}, - {"opacity-rule", required_argument, NULL, 304}, - {"shadow-exclude-reg", required_argument, NULL, 305}, - {"paint-exclude", required_argument, NULL, 306}, - {"xinerama-shadow-crop", no_argument, NULL, 307}, - {"unredir-if-possible-exclude", required_argument, NULL, 308}, - {"unredir-if-possible-delay", required_argument, NULL, 309}, - {"write-pid-path", required_argument, NULL, 310}, - {"vsync-use-glfinish", no_argument, NULL, 311}, - {"xrender-sync", no_argument, NULL, 312}, - {"xrender-sync-fence", no_argument, NULL, 313}, - {"show-all-xerrors", no_argument, NULL, 314}, - {"no-fading-destroyed-argb", no_argument, NULL, 315}, - {"force-win-blend", no_argument, NULL, 316}, - {"glx-fshader-win", required_argument, NULL, 317}, - {"version", no_argument, NULL, 318}, - {"no-x-selection", no_argument, NULL, 319}, - {"no-name-pixmap", no_argument, NULL, 320}, - {"log-level", required_argument, NULL, 321}, - {"log-file", required_argument, NULL, 322}, - {"use-damage", no_argument, NULL, 323}, - {"no-use-damage", no_argument, NULL, 324}, - {"no-vsync", no_argument, NULL, 325}, - {"max-brightness", required_argument, NULL, 326}, - {"transparent-clipping", no_argument, NULL, 327}, - {"blur-method", required_argument, NULL, 328}, - {"blur-size", required_argument, NULL, 329}, - {"blur-deviation", required_argument, NULL, 330}, - {"blur-strength", required_argument, NULL, 331}, - {"shadow-color", required_argument, NULL, 332}, - {"corner-radius", required_argument, NULL, 333}, - {"rounded-corners-exclude", required_argument, NULL, 334}, - {"clip-shadow-above", required_argument, NULL, 335}, - {"experimental-backends", no_argument, NULL, 733}, - {"monitor-repaint", no_argument, NULL, 800}, - {"diagnostics", no_argument, NULL, 801}, - {"debug-mode", no_argument, NULL, 802}, - {"no-ewmh-fullscreen", no_argument, NULL, 803}, - {"animations", no_argument, NULL, 804}, - {"animation-stiffness", required_argument, NULL, 805}, - {"animation-dampening", required_argument, NULL, 806}, - {"animation-window-mass", required_argument, NULL, 807}, - {"animation-clamping", no_argument, NULL, 808}, - {"animation-for-open-window", required_argument, NULL, 809}, - {"animation-for-transient-window", required_argument, NULL, 810}, - // Must terminate with a NULL entry - {NULL, 0, NULL, 0}, -}; - -/// 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) { - int o = 0, longopt_idx = -1; - - // Pre-parse the commandline arguments to check for --config and invalid - // switches - // Must reset optind to 0 here in case we reread the commandline - // arguments - optind = 1; - *config_file = NULL; - *exit_code = 0; - while (-1 != (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { - if (o == 256) { - *config_file = strdup(optarg); - } else if (o == 'h') { - usage(argv[0], 0); - return true; - - } else if (o == 'b') { - *fork = true; - } else if (o == 'd') { - log_error("-d is removed, please use the DISPLAY " - "environment variable"); - goto err; - } else if (o == 314) { - *all_xerrors = true; - } else if (o == 318) { - printf("%s\n", COMPTON_VERSION); - return true; - } else if (o == 'S') { - log_error("-S is no longer available"); - goto err; - } else if (o == 320) { - log_error("--no-name-pixmap is no longer available"); - goto err; - } else if (o == '?' || o == ':') { - usage(argv[0], 1); - goto err; - } - } - - // Check for abundant positional arguments - if (optind < argc) { - // log is not initialized here yet - fprintf(stderr, "picom doesn't accept positional arguments.\n"); - goto err; - } - - return false; -err: - *exit_code = 1; - return true; -} - -/** - * Process arguments and configuration files. - */ -bool 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) { - - int o = 0, longopt_idx = -1; - - char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); - - // Enforce LC_NUMERIC locale "C" here to make sure dots are recognized - // instead of commas in atof(). - setlocale(LC_NUMERIC, "C"); - - // Parse commandline arguments. Range checking will be done later. - - bool failed = false; - const char *deprecation_message attr_unused = - "has been removed. If you encounter problems " - "without this feature, please feel free to " - "open a bug report."; - optind = 1; - while (-1 != (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { - switch (o) { -#define P_CASEBOOL(idx, option) \ - case idx: \ - opt->option = true; \ - break -#define P_CASELONG(idx, option) \ - case idx: \ - if (!parse_long(optarg, &opt->option)) { \ - exit(1); \ - } \ - break -#define P_CASEINT(idx, option) \ - case idx: \ - if (!parse_int(optarg, &opt->option)) { \ - exit(1); \ - } \ - break - - // clang-format off - // Short options - case 318: - case 'h': - // These options should cause us to exit early, - // so assert(false) here - assert(false); - break; - case 'd': - case 'b': - case 'S': - case 314: - case 320: - // These options are handled by get_early_config() - break; - P_CASEINT('D', fade_delta); - case 'I': opt->fade_in_step = normalize_d(atof(optarg)); break; - case 'O': opt->fade_out_step = normalize_d(atof(optarg)); break; - case 'c': shadow_enable = true; break; - case 'C': - log_error("Option `--no-dock-shadow`/`-C` has been removed. Please" - " use the wintype option `shadow` of `dock` instead."); - failed = true; break; - case 'G': - log_error("Option `--no-dnd-shadow`/`-G` has been removed. Please " - "use the wintype option `shadow` of `dnd` instead."); - failed = true; break; - case 'm':; - double tmp; - tmp = normalize_d(atof(optarg)); - winopt_mask[WINTYPE_DROPDOWN_MENU].opacity = true; - winopt_mask[WINTYPE_POPUP_MENU].opacity = true; - opt->wintype_option[WINTYPE_POPUP_MENU].opacity = tmp; - opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = tmp; - break; - case 'f': - case 'F': - fading_enable = true; - break; - P_CASEINT('r', shadow_radius); - case 'o': - opt->shadow_opacity = atof(optarg); - break; - P_CASEINT('l', shadow_offset_x); - P_CASEINT('t', shadow_offset_y); - case 'i': - opt->inactive_opacity = normalize_d(atof(optarg)); - break; - case 'e': opt->frame_opacity = atof(optarg); break; - case 'z': - log_warn("clear-shadow is removed, shadows are automatically " - "cleared now. If you want to prevent shadow from been " - "cleared under certain types of windows, you can use " - "the \"full-shadow\" per window type option."); - break; - case 'n': - case 'a': - case 's': - log_error("-n, -a, and -s have been removed."); - failed = true; break; - // Long options - case 256: - // --config - break; - case 332:; - // --shadow-color - struct color rgb; - rgb = hex_to_rgb(optarg); - opt->shadow_red = rgb.red; - opt->shadow_green = rgb.green; - opt->shadow_blue = rgb.blue; - break; - case 257: - // --shadow-red - opt->shadow_red = atof(optarg); - break; - case 258: - // --shadow-green - opt->shadow_green = atof(optarg); - break; - case 259: - // --shadow-blue - opt->shadow_blue = atof(optarg); - break; - P_CASEBOOL(260, inactive_opacity_override); - case 261: - // --inactive-dim - opt->inactive_dim = atof(optarg); - break; - P_CASEBOOL(262, mark_wmwin_focused); - case 263: - // --shadow-exclude - condlst_add(&opt->shadow_blacklist, optarg); - break; - P_CASEBOOL(264, mark_ovredir_focused); - P_CASEBOOL(265, no_fading_openclose); - P_CASEBOOL(266, shadow_ignore_shaped); - P_CASEBOOL(267, detect_rounded_corners); - P_CASEBOOL(268, detect_client_opacity); - P_CASEINT(269, refresh_rate); - case 270: - if (optarg) { - opt->vsync = parse_vsync(optarg); - log_warn("--vsync doesn't take argument anymore. \"%s\" " - "is interpreted as \"%s\" for compatibility, but " - "this will stop working soon", - optarg, opt->vsync ? "true" : "false"); - } else { - opt->vsync = true; - } - break; - case 271: - // --alpha-step - log_error("--alpha-step has been removed, we now tries to " - "make use of all alpha values"); - failed = true; break; - case 272: - log_error("--dbe has been removed"); - failed = true; break; - case 273: - log_error("--paint-on-overlay has been removed, the feature is enabled " - "whenever possible"); - failed = true; break; - P_CASEBOOL(274, sw_opti); - case 275: - // --vsync-aggressive - log_warn("--vsync-aggressive has been deprecated, please remove it" - " from the command line options"); - break; - P_CASEBOOL(276, use_ewmh_active_win); - case 277: - // --respect-prop-shadow - log_warn("--respect-prop-shadow option has been deprecated, its " - "functionality will always be enabled. Please remove it " - "from the command line options"); - break; - P_CASEBOOL(278, unredir_if_possible); - case 279: - // --focus-exclude - condlst_add(&opt->focus_blacklist, optarg); - break; - P_CASEBOOL(280, inactive_dim_fixed); - P_CASEBOOL(281, detect_transient); - P_CASEBOOL(282, detect_client_leader); - case 283: - // --blur_background - opt->blur_method = BLUR_METHOD_KERNEL; - break; - P_CASEBOOL(284, blur_background_frame); - P_CASEBOOL(285, blur_background_fixed); - P_CASEBOOL(286, dbus); - case 287: - log_warn("Please use --log-file instead of --logpath"); - // fallthrough - case 322: - // --logpath, --log-file - free(opt->logpath); - opt->logpath = strdup(optarg); - break; - case 288: - // --invert-color-include - condlst_add(&opt->invert_color_list, optarg); - break; - case 289: - // --opengl - opt->backend = BKEND_GLX; - break; - case 290: - // --backend - opt->backend = parse_backend(optarg); - if (opt->backend >= NUM_BKEND) - exit(1); - break; - P_CASEBOOL(291, glx_no_stencil); - P_CASEINT(293, benchmark); - case 294: - // --benchmark-wid - opt->benchmark_wid = (xcb_window_t)strtol(optarg, NULL, 0); - break; - case 296: - // --blur-background-exclude - condlst_add(&opt->blur_background_blacklist, optarg); - break; - case 297: - // --active-opacity - opt->active_opacity = normalize_d(atof(optarg)); - break; - P_CASEBOOL(298, glx_no_rebind_pixmap); - case 299: { - // --glx-swap-method - char *endptr; - long tmpval = strtol(optarg, &endptr, 10); - bool should_remove = true; - if (*endptr || !(*optarg)) { - // optarg is not a number, or an empty string - tmpval = -1; - } - if (strcmp(optarg, "undefined") != 0 && tmpval != 0) { - // If not undefined, we will use damage and buffer-age to - // limit the rendering area. - opt->use_damage = true; - should_remove = false; - } - log_warn("--glx-swap-method has been deprecated, your setting " - "\"%s\" should be %s.", - optarg, - !should_remove ? "replaced by `--use-damage`" : - "removed"); - break; - } - case 300: - // --fade-exclude - condlst_add(&opt->fade_blacklist, optarg); - break; - case 301: - // --blur-kern - opt->blur_kerns = parse_blur_kern_lst(optarg, &conv_kern_hasneg, - &opt->blur_kernel_count); - if (!opt->blur_kerns) { - exit(1); - } - break; - P_CASEINT(302, resize_damage); - case 303: - // --glx-use-gpushader4 - log_warn("--glx-use-gpushader4 is deprecated since v6." - " Please remove it from command line options."); - break; - case 304: - // --opacity-rule - if (!parse_rule_opacity(&opt->opacity_rules, optarg)) - exit(1); - break; - case 305: - // --shadow-exclude-reg - free(opt->shadow_exclude_reg_str); - opt->shadow_exclude_reg_str = strdup(optarg); - log_warn("--shadow-exclude-reg is deprecated. You are likely " - "better off using --clip-shadow-above anyway"); - break; - case 306: - // --paint-exclude - condlst_add(&opt->paint_blacklist, optarg); - break; - P_CASEBOOL(307, xinerama_shadow_crop); - case 308: - // --unredir-if-possible-exclude - condlst_add(&opt->unredir_if_possible_blacklist, optarg); - break; - P_CASELONG(309, unredir_if_possible_delay); - case 310: - // --write-pid-path - free(opt->write_pid_path); - opt->write_pid_path = strdup(optarg); - if (*opt->write_pid_path != '/') { - log_warn("--write-pid-path is not an absolute path"); - } - break; - P_CASEBOOL(311, vsync_use_glfinish); - case 312: - // --xrender-sync - log_error("Please use --xrender-sync-fence instead of --xrender-sync"); - failed = true; break; - P_CASEBOOL(313, xrender_sync_fence); - P_CASEBOOL(315, no_fading_destroyed_argb); - P_CASEBOOL(316, force_win_blend); - case 317: - opt->glx_fshader_win_str = strdup(optarg); - break; - case 321: { - enum log_level tmp_level = string_to_log_level(optarg); - if (tmp_level == LOG_LEVEL_INVALID) { - log_warn("Invalid log level, defaults to WARN"); - } else { - log_set_level_tls(tmp_level); - } - break; - } - P_CASEBOOL(319, no_x_selection); - P_CASEBOOL(323, use_damage); - case 324: - opt->use_damage = false; - break; - case 325: - opt->vsync = false; - break; - - case 326: - opt->max_brightness = atof(optarg); - break; - P_CASEBOOL(327, transparent_clipping); - case 328: { - // --blur-method - enum blur_method method = parse_blur_method(optarg); - if (method >= BLUR_METHOD_INVALID) { - log_warn("Invalid blur method %s, ignoring.", optarg); - } else { - opt->blur_method = method; - } - break; - } - case 329: - // --blur-size - opt->blur_radius = atoi(optarg); - break; - case 330: - // --blur-deviation - opt->blur_deviation = atof(optarg); - break; - case 331: - // --blur-strength - opt->blur_strength = atoi(optarg); - break; - case 333: - // --cornor-radius - opt->corner_radius = atoi(optarg); - break; - case 334: - // --rounded-corners-exclude - condlst_add(&opt->rounded_corners_blacklist, optarg); - break; - case 335: - // --clip-shadow-above - condlst_add(&opt->shadow_clip_list, optarg); - break; - P_CASEBOOL(733, experimental_backends); - P_CASEBOOL(800, monitor_repaint); - case 801: opt->print_diagnostics = true; break; - P_CASEBOOL(802, debug_mode); - P_CASEBOOL(803, no_ewmh_fullscreen); - P_CASEBOOL(804, animations); - case 805: - // --animation-stiffness - opt->animation_stiffness = atof(optarg); - break; - case 806: - // --animation-dampening - opt->animation_dampening = atof(optarg); - break; - case 807: - // --animation-window-masss - opt->animation_window_mass = atof(optarg); - break; - case 808: - // --animation-clamping - opt->animation_clamping = true; - break; - case 809: { - // --animation-for-open-window - enum open_window_animation animation = parse_open_window_animation(optarg); - if (animation >= OPEN_WINDOW_ANIMATION_INVALID) { - log_warn("Invalid open-window animation %s, ignoring.", optarg); - } else { - opt->animation_for_open_window = animation; - } - break; - } - case 810: { - // --animation-for-transient-window - enum open_window_animation animation = parse_open_window_animation(optarg); - if (animation >= OPEN_WINDOW_ANIMATION_INVALID) { - log_warn("Invalid transient-window animation %s, ignoring.", optarg); - } else { - opt->animation_for_transient_window = animation; - } - break; - } - default: usage(argv[0], 1); break; -#undef P_CASEBOOL - } - // clang-format on - - if (failed) { - // Parsing this option has failed, break the loop - break; - } - } - - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); - - if (failed) { - return false; - } - - if (opt->monitor_repaint && opt->backend != BKEND_XRENDER && - !opt->experimental_backends) { - log_warn("--monitor-repaint has no effect when backend is not xrender"); - } - - if (opt->experimental_backends && !backend_list[opt->backend]) { - log_error("Backend \"%s\" is not available as part of the experimental " - "backends.", - BACKEND_STRS[opt->backend]); - return false; - } - - if (opt->debug_mode && !opt->experimental_backends) { - log_error("Debug mode only works with the experimental backends."); - return false; - } - - if (opt->transparent_clipping && !opt->experimental_backends) { - log_error("Transparent clipping only works with the experimental " - "backends"); - return false; - } - - // Range checking and option assignments - opt->fade_delta = max2(opt->fade_delta, 1); - opt->shadow_radius = max2(opt->shadow_radius, 0); - opt->shadow_red = normalize_d(opt->shadow_red); - opt->shadow_green = normalize_d(opt->shadow_green); - opt->shadow_blue = normalize_d(opt->shadow_blue); - opt->inactive_dim = normalize_d(opt->inactive_dim); - opt->frame_opacity = normalize_d(opt->frame_opacity); - opt->shadow_opacity = normalize_d(opt->shadow_opacity); - opt->refresh_rate = normalize_i_range(opt->refresh_rate, 0, 300); - - opt->max_brightness = normalize_d(opt->max_brightness); - if (opt->max_brightness < 1.0) { - if (opt->use_damage) { - log_warn("--max-brightness requires --no-use-damage. Falling " - "back to 1.0"); - opt->max_brightness = 1.0; - } - - if (!opt->experimental_backends || opt->backend != BKEND_GLX) { - log_warn("--max-brightness requires the experimental glx " - "backend. Falling back to 1.0"); - opt->max_brightness = 1.0; - } - } - - // --blur-background-frame implies --blur-background - if (opt->blur_background_frame && opt->blur_method == BLUR_METHOD_NONE) { - opt->blur_method = BLUR_METHOD_KERNEL; - } - - // Apply default wintype options that are dependent on global options - set_default_winopts(opt, winopt_mask, shadow_enable, fading_enable, - opt->blur_method != BLUR_METHOD_NONE); - - // Other variables determined by options - - // Determine whether we track window grouping - if (opt->detect_transient || opt->detect_client_leader) { - opt->track_leader = true; - } - - // Fill default blur kernel - if (opt->blur_method == BLUR_METHOD_KERNEL && - (!opt->blur_kerns || !opt->blur_kerns[0])) { - opt->blur_kerns = parse_blur_kern_lst("3x3box", &conv_kern_hasneg, - &opt->blur_kernel_count); - CHECK(opt->blur_kerns); - CHECK(opt->blur_kernel_count); - } - - // Sanitize parameters for dual-filter kawase blur - if (opt->blur_method == BLUR_METHOD_DUAL_KAWASE) { - if (opt->blur_strength <= 0 && opt->blur_radius > 500) { - log_warn("Blur radius >500 not supported by dual_kawase method, " - "capping to 500."); - opt->blur_radius = 500; - } - if (opt->blur_strength > 20) { - log_warn("Blur strength >20 not supported by dual_kawase method, " - "capping to 20."); - opt->blur_strength = 20; - } - if (!opt->experimental_backends) { - log_warn("Dual-kawase blur is not implemented by the legacy " - "backends, you must use the `experimental-backends` " - "option."); - } - } - - if (opt->resize_damage < 0) { - log_warn("Negative --resize-damage will not work correctly."); - } - - if (opt->backend == BKEND_XRENDER && conv_kern_hasneg) { - log_warn("A convolution kernel with negative values may not work " - "properly under X Render backend."); - } - - if (opt->corner_radius > 0 && opt->experimental_backends) { - log_warn("Rounded corner is only supported on legacy backends, it " - "will be disabled"); - opt->corner_radius = 0; - } - - return true; -} - -// vim: set noet sw=8 ts=8 : diff --git a/src/options.h b/src/options.h deleted file mode 100644 index 08aa15e..0000000 --- a/src/options.h +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#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: diff --git a/src/picom.c b/src/picom.c deleted file mode 100644 index eb479f3..0000000 --- a/src/picom.c +++ /dev/null @@ -1,2790 +0,0 @@ -// 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 <X11/Xlib-xcb.h> -#include <X11/Xlib.h> -#include <X11/Xutil.h> -#include <X11/extensions/sync.h> -#include <errno.h> -#include <fcntl.h> -#include <inttypes.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include <xcb/composite.h> -#include <xcb/damage.h> -#include <xcb/glx.h> -#include <xcb/present.h> -#include <xcb/randr.h> -#include <xcb/render.h> -#include <xcb/sync.h> -#include <xcb/xfixes.h> -#include <xcb/xinerama.h> - -#include <ev.h> -#include <test.h> - -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "err.h" -#include "kernel.h" -#include "picom.h" -#ifdef CONFIG_OPENGL -#include "opengl.h" -#endif -#include "backend/backend.h" -#include "c2.h" -#include "config.h" -#include "diagnostic.h" -#include "log.h" -#include "region.h" -#include "render.h" -#include "types.h" -#include "utils.h" -#include "win.h" -#include "x.h" -#ifdef CONFIG_DBUS -#include "dbus.h" -#endif -#include "atom.h" -#include "event.h" -#include "file_watch.h" -#include "list.h" -#include "options.h" -#include "uthash_extra.h" - -/// Get session_t pointer from a pointer to a member of session_t -#define session_ptr(ptr, member) \ - ({ \ - const __typeof__(((session_t *)0)->member) *__mptr = (ptr); \ - (session_t *)((char *)__mptr - offsetof(session_t, member)); \ - }) - -static const long SWOPTI_TOLERANCE = 3000; - -static bool must_use redirect_start(session_t *ps); - -static void unredirect(session_t *ps); - -// === Global constants === - -/// Name strings for window types. -const char *const WINTYPES[NUM_WINTYPES] = { - "unknown", "desktop", "dock", "toolbar", "menu", - "utility", "splash", "dialog", "normal", "dropdown_menu", - "popup_menu", "tooltip", "notification", "combo", "dnd", -}; - -// clang-format off -/// Names of backends. -const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender", - [BKEND_GLX] = "glx", - [BKEND_XR_GLX_HYBRID] = "xr_glx_hybrid", - [BKEND_DUMMY] = "dummy", - NULL}; -// clang-format on - -// === Global variables === - -/// Pointer to current session, as a global variable. Only used by -/// xerror(), which could not have a pointer to current session passed in. -/// XXX Limit what xerror can access by not having this pointer -session_t *ps_g = NULL; - -void set_root_flags(session_t *ps, uint64_t flags) { - log_debug("Setting root flags: %" PRIu64, flags); - ps->root_flags |= flags; - ps->pending_updates = true; -} - -void quit(session_t *ps) { - ps->quit = true; - ev_break(ps->loop, EVBREAK_ALL); -} - -/** - * Free Xinerama screen info. - * - * XXX consider moving to x.c - */ -static inline void free_xinerama_info(session_t *ps) { - if (ps->xinerama_scr_regs) { - for (int i = 0; i < ps->xinerama_nscrs; ++i) - pixman_region32_fini(&ps->xinerama_scr_regs[i]); - free(ps->xinerama_scr_regs); - ps->xinerama_scr_regs = NULL; - } - ps->xinerama_nscrs = 0; -} - -/** - * Get current system clock in milliseconds. - */ -static inline int64_t get_time_ms(void) { - struct timespec tp; - clock_gettime(CLOCK_MONOTONIC, &tp); - return (int64_t)tp.tv_sec * 1000 + (int64_t)tp.tv_nsec / 1000000; -} - -// XXX Move to x.c -void cxinerama_upd_scrs(session_t *ps) { - // XXX Consider deprecating Xinerama, switch to RandR when necessary - free_xinerama_info(ps); - - if (!ps->o.xinerama_shadow_crop || !ps->xinerama_exists) - return; - - xcb_xinerama_is_active_reply_t *active = - xcb_xinerama_is_active_reply(ps->c, xcb_xinerama_is_active(ps->c), NULL); - if (!active || !active->state) { - free(active); - return; - } - free(active); - - auto xinerama_scrs = - xcb_xinerama_query_screens_reply(ps->c, xcb_xinerama_query_screens(ps->c), NULL); - if (!xinerama_scrs) { - return; - } - - xcb_xinerama_screen_info_t *scrs = - xcb_xinerama_query_screens_screen_info(xinerama_scrs); - ps->xinerama_nscrs = xcb_xinerama_query_screens_screen_info_length(xinerama_scrs); - - ps->xinerama_scr_regs = ccalloc(ps->xinerama_nscrs, region_t); - for (int i = 0; i < ps->xinerama_nscrs; ++i) { - const xcb_xinerama_screen_info_t *const s = &scrs[i]; - pixman_region32_init_rect(&ps->xinerama_scr_regs[i], s->x_org, s->y_org, - s->width, s->height); - } - free(xinerama_scrs); -} - -/** - * Find matched window. - * - * XXX move to win.c - */ -static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t wid) { - if (!wid || PointerRoot == wid || wid == ps->root || wid == ps->overlay) - return NULL; - - auto w = find_managed_win(ps, wid); - if (!w) - w = find_toplevel(ps, wid); - if (!w) - w = find_managed_window_or_parent(ps, wid); - return w; -} - -void queue_redraw(session_t *ps) { - // If --benchmark is used, redraw is always queued - if (!ps->redraw_needed && !ps->o.benchmark) { - ev_idle_start(ps->loop, &ps->draw_idle); - } - ps->redraw_needed = true; -} - -/** - * Get a region of the screen size. - */ -static inline void get_screen_region(session_t *ps, region_t *res) { - pixman_box32_t b = {.x1 = 0, .y1 = 0, .x2 = ps->root_width, .y2 = ps->root_height}; - pixman_region32_fini(res); - pixman_region32_init_rects(res, &b, 1); -} - -void add_damage(session_t *ps, const region_t *damage) { - // Ignore damage when screen isn't redirected - if (!ps->redirected) { - return; - } - - if (!damage) { - return; - } - log_trace("Adding damage: "); - dump_region(damage); - pixman_region32_union(ps->damage, ps->damage, (region_t *)damage); -} - -// === Fading === - -/** - * Get the time left before next fading point. - * - * In milliseconds. - */ -static double fade_timeout(session_t *ps) { - auto now = get_time_ms(); - if (ps->o.fade_delta + ps->fade_time < now) - return 0; - - auto diff = ps->o.fade_delta + ps->fade_time - now; - - diff = clamp(diff, 0, ps->o.fade_delta * 2); - - return (double)diff / 1000.0; -} - -/** - * Run fading on a window. - * - * @param steps steps of fading - * @return whether we are still in fading mode - */ -static bool run_fade(session_t *ps, struct managed_win **_w, long steps) { - auto w = *_w; - if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) { - // We are not fading - assert(w->opacity_target == w->opacity); - return false; - } - - if (!win_should_fade(ps, w)) { - log_debug("Window %#010x %s doesn't need fading", w->base.id, w->name); - w->opacity = w->opacity_target; - } - if (w->opacity == w->opacity_target) { - // We have reached target opacity. - // We don't call win_check_fade_finished here because that could destroy - // the window, but we still need the damage info from this window - log_debug("Fading finished for window %#010x %s", w->base.id, w->name); - return false; - } - - if (steps) { - log_trace("Window %#010x (%s) opacity was: %lf", w->base.id, w->name, - w->opacity); - if (w->opacity < w->opacity_target) { - w->opacity = clamp(w->opacity + ps->o.fade_in_step * (double)steps, - 0.0, w->opacity_target); - } else { - w->opacity = clamp(w->opacity - ps->o.fade_out_step * (double)steps, - w->opacity_target, 1); - } - log_trace("... updated to: %lf", w->opacity); - } - - // Note even if opacity == opacity_target here, we still want to run preprocess - // one last time to finish state transition. So return true in that case too. - return true; -} - -// === Error handling === - -void discard_ignore(session_t *ps, unsigned long sequence) { - while (ps->ignore_head) { - if (sequence > ps->ignore_head->sequence) { - ignore_t *next = ps->ignore_head->next; - free(ps->ignore_head); - ps->ignore_head = next; - if (!ps->ignore_head) { - ps->ignore_tail = &ps->ignore_head; - } - } else { - break; - } - } -} - -static int should_ignore(session_t *ps, unsigned long sequence) { - if (ps == NULL) { - // Do not ignore errors until the session has been initialized - return false; - } - discard_ignore(ps, sequence); - return ps->ignore_head && ps->ignore_head->sequence == sequence; -} - -// === Windows === - -/** - * Determine the event mask for a window. - */ -uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode) { - uint32_t evmask = 0; - struct managed_win *w = NULL; - - // Check if it's a mapped frame window - if (mode == WIN_EVMODE_FRAME || - ((w = find_managed_win(ps, wid)) && w->a.map_state == XCB_MAP_STATE_VIEWABLE)) { - evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; - if (!ps->o.use_ewmh_active_win) { - evmask |= XCB_EVENT_MASK_FOCUS_CHANGE; - } - } - - // Check if it's a mapped client window - if (mode == WIN_EVMODE_CLIENT || - ((w = find_toplevel(ps, wid)) && w->a.map_state == XCB_MAP_STATE_VIEWABLE)) { - evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE; - } - - return evmask; -} - -/** - * Update current active window based on EWMH _NET_ACTIVE_WIN. - * - * Does not change anything if we fail to get the attribute or the window - * returned could not be found. - */ -void update_ewmh_active_win(session_t *ps) { - // Search for the window - xcb_window_t wid = - wid_get_prop_window(ps->c, ps->root, ps->atoms->a_NET_ACTIVE_WINDOW); - auto w = find_win_all(ps, wid); - - // Mark the window focused. No need to unfocus the previous one. - if (w) { - win_set_focused(ps, w); - } -} - -/** - * Recheck currently focused window and set its <code>w->focused</code> - * to true. - * - * @param ps current session - * @return struct _win of currently focused window, NULL if not found - */ -static void recheck_focus(session_t *ps) { - // Use EWMH _NET_ACTIVE_WINDOW if enabled - if (ps->o.use_ewmh_active_win) { - update_ewmh_active_win(ps); - return; - } - - // Determine the currently focused window so we can apply appropriate - // opacity on it - xcb_window_t wid = XCB_NONE; - xcb_get_input_focus_reply_t *reply = - xcb_get_input_focus_reply(ps->c, xcb_get_input_focus(ps->c), NULL); - - if (reply) { - wid = reply->focus; - free(reply); - } - - auto w = find_win_all(ps, wid); - - log_trace("%#010" PRIx32 " (%#010lx \"%s\") focused.", wid, - (w ? w->base.id : XCB_NONE), (w ? w->name : NULL)); - - // And we set the focus state here - if (w) { - win_set_focused(ps, w); - return; - } -} - -/** - * Rebuild cached <code>screen_reg</code>. - */ -static void rebuild_screen_reg(session_t *ps) { - get_screen_region(ps, &ps->screen_reg); -} - -/** - * Rebuild <code>shadow_exclude_reg</code>. - */ -static void rebuild_shadow_exclude_reg(session_t *ps) { - bool ret = parse_geometry(ps, ps->o.shadow_exclude_reg_str, &ps->shadow_exclude_reg); - if (!ret) - exit(1); -} - -/// Free up all the images and deinit the backend -static void destroy_backend(session_t *ps) { - win_stack_foreach_managed_safe(w, &ps->window_stack) { - // Wrapping up fading in progress - if (win_skip_fading(ps, w)) { - // `w` is freed by win_skip_fading - continue; - } - - if (ps->backend_data) { - // Unmapped windows could still have shadow images, but not pixmap - // images - assert(!w->win_image || w->state != WSTATE_UNMAPPED); - if (win_check_flags_any(w, WIN_FLAGS_IMAGES_STALE) && - w->state == WSTATE_MAPPED) { - log_warn("Stale flags set for mapped window %#010x " - "during backend destruction", - w->base.id); - assert(false); - } - // Unmapped windows can still have stale flags set, because their - // stale flags aren't handled until they are mapped. - win_clear_flags(w, WIN_FLAGS_IMAGES_STALE); - win_release_images(ps->backend_data, w); - } - free_paint(ps, &w->paint); - } - - if (ps->backend_data && ps->root_image) { - ps->backend_data->ops->release_image(ps->backend_data, ps->root_image); - ps->root_image = NULL; - } - - if (ps->backend_data) { - // deinit backend - if (ps->backend_blur_context) { - ps->backend_data->ops->destroy_blur_context( - ps->backend_data, ps->backend_blur_context); - ps->backend_blur_context = NULL; - } - ps->backend_data->ops->deinit(ps->backend_data); - ps->backend_data = NULL; - } -} - -static bool initialize_blur(session_t *ps) { - struct kernel_blur_args kargs; - struct gaussian_blur_args gargs; - struct box_blur_args bargs; - struct dual_kawase_blur_args dkargs; - - void *args = NULL; - switch (ps->o.blur_method) { - case BLUR_METHOD_BOX: - bargs.size = ps->o.blur_radius; - args = (void *)&bargs; - break; - case BLUR_METHOD_KERNEL: - kargs.kernel_count = ps->o.blur_kernel_count; - kargs.kernels = ps->o.blur_kerns; - args = (void *)&kargs; - break; - case BLUR_METHOD_GAUSSIAN: - gargs.size = ps->o.blur_radius; - gargs.deviation = ps->o.blur_deviation; - args = (void *)&gargs; - break; - case BLUR_METHOD_DUAL_KAWASE: - dkargs.size = ps->o.blur_radius; - dkargs.strength = ps->o.blur_strength; - args = (void *)&dkargs; - break; - default: return true; - } - - ps->backend_blur_context = ps->backend_data->ops->create_blur_context( - ps->backend_data, ps->o.blur_method, args); - return ps->backend_blur_context != NULL; -} - -/// Init the backend and bind all the window pixmap to backend images -static bool initialize_backend(session_t *ps) { - if (ps->o.experimental_backends) { - assert(!ps->backend_data); - // Reinitialize win_data - assert(backend_list[ps->o.backend]); - ps->backend_data = backend_list[ps->o.backend]->init(ps); - if (!ps->backend_data) { - log_fatal("Failed to initialize backend, aborting..."); - quit(ps); - return false; - } - ps->backend_data->ops = backend_list[ps->o.backend]; - - if (!initialize_blur(ps)) { - log_fatal("Failed to prepare for background blur, aborting..."); - ps->backend_data->ops->deinit(ps->backend_data); - ps->backend_data = NULL; - quit(ps); - return false; - } - - // window_stack shouldn't include window that's - // not in the hash table at this point. Since - // there cannot be any fading windows. - HASH_ITER2(ps->windows, _w) { - if (!_w->managed) { - continue; - } - auto w = (struct managed_win *)_w; - assert(w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED); - // We need to reacquire image - log_debug("Marking window %#010x (%s) for update after " - "redirection", - w->base.id, w->name); - win_set_flags(w, WIN_FLAGS_IMAGES_STALE); - ps->pending_updates = true; - } - } - - // The old backends binds pixmap lazily, nothing to do here - return true; -} - -/// Handle configure event of the root window -static void configure_root(session_t *ps) { - auto r = XCB_AWAIT(xcb_get_geometry, ps->c, ps->root); - if (!r) { - log_fatal("Failed to fetch root geometry"); - abort(); - } - - log_info("Root configuration changed, new geometry: %dx%d", r->width, r->height); - bool has_root_change = false; - if (ps->redirected) { - // On root window changes - if (ps->o.experimental_backends) { - assert(ps->backend_data); - has_root_change = ps->backend_data->ops->root_change != NULL; - } else { - // Old backend can handle root change - has_root_change = true; - } - - if (!has_root_change) { - // deinit/reinit backend and free up resources if the backend - // cannot handle root change - destroy_backend(ps); - } - free_paint(ps, &ps->tgt_buffer); - } - - ps->root_width = r->width; - ps->root_height = r->height; - - auto prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_DESKTOP, - 1L, XCB_ATOM_CARDINAL, 32); - - ps->root_desktop_switch_direction = 0; - if (prop.nitems) { - ps->root_desktop_num = (int)*prop.c32; - } - - rebuild_screen_reg(ps); - rebuild_shadow_exclude_reg(ps); - - // Invalidate reg_ignore from the top - auto top_w = win_stack_find_next_managed(ps, &ps->window_stack); - if (top_w) { - rc_region_unref(&top_w->reg_ignore); - top_w->reg_ignore_valid = false; - } - - if (ps->redirected) { - for (int i = 0; i < ps->ndamage; i++) { - pixman_region32_clear(&ps->damage_ring[i]); - } - ps->damage = ps->damage_ring + ps->ndamage - 1; -#ifdef CONFIG_OPENGL - // GLX root change callback - if (BKEND_GLX == ps->o.backend && !ps->o.experimental_backends) { - glx_on_root_change(ps); - } -#endif - if (has_root_change) { - if (ps->backend_data != NULL) { - ps->backend_data->ops->root_change(ps->backend_data, ps); - } - // Old backend's root_change is not a specific function - } else { - if (!initialize_backend(ps)) { - log_fatal("Failed to re-initialize backend after root " - "change, aborting..."); - ps->quit = true; - /* TODO(yshui) only event handlers should request - * ev_break, otherwise it's too hard to keep track of what - * can break the event loop */ - ev_break(ps->loop, EVBREAK_ALL); - return; - } - - // Re-acquire the root pixmap. - root_damaged(ps); - } - force_repaint(ps); - } - return; -} - -static void handle_root_flags(session_t *ps) { - if ((ps->root_flags & ROOT_FLAGS_SCREEN_CHANGE) != 0) { - if (ps->o.xinerama_shadow_crop) { - cxinerama_upd_scrs(ps); - } - - if (ps->o.sw_opti && !ps->o.refresh_rate) { - update_refresh_rate(ps); - if (!ps->refresh_rate) { - log_warn("Refresh rate detection failed. swopti will be " - "temporarily disabled"); - } - } - ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE; - } - - if ((ps->root_flags & ROOT_FLAGS_CONFIGURED) != 0) { - configure_root(ps); - ps->root_flags &= ~(uint64_t)ROOT_FLAGS_CONFIGURED; - } -} - -static struct managed_win * -paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) { - // XXX need better, more general name for `fade_running`. It really - // means if fade is still ongoing after the current frame is rendered. - // Same goes for `animation_running`. - struct managed_win *bottom = NULL; - *fade_running = false; - *animation_running = false; - auto now = get_time_ms(); - - // Fading step calculation - long steps = 0L; - if (ps->fade_time) { - assert(now >= ps->fade_time); - steps = (now - ps->fade_time) / ps->o.fade_delta; - } else { - // Reset fade_time if unset - ps->fade_time = now; - steps = 0L; - } - ps->fade_time += steps * ps->o.fade_delta; - - double animation_delta = 0; - if (ps->o.animations) { - if (!ps->animation_time) - ps->animation_time = now; - - animation_delta = (double)(now - ps->animation_time) / - (ps->o.animation_delta*100); - - if (ps->o.animation_force_steps) - animation_delta = min2(animation_delta, ps->o.animation_delta/1000); - } - - // First, let's process fading - win_stack_foreach_managed_safe(w, &ps->window_stack) { - const winmode_t mode_old = w->mode; - const bool was_painted = w->to_paint; - const double opacity_old = w->opacity; - - // IMPORTANT: These window animation steps must happen before any other - // [pre]processing. This is because it changes the window's geometry. - if (ps->o.animations && - !isnan(w->animation_progress) && w->animation_progress != 1.0 && - ps->o.wintype_option[w->window_type].animation != 0 && - win_is_mapped_in_x(w)) - { - double neg_displacement_x = - w->animation_dest_center_x - w->animation_center_x; - double neg_displacement_y = - w->animation_dest_center_y - w->animation_center_y; - double neg_displacement_w = w->animation_dest_w - w->animation_w; - double neg_displacement_h = w->animation_dest_h - w->animation_h; - double acceleration_x = - (ps->o.animation_stiffness * neg_displacement_x - - ps->o.animation_dampening * w->animation_velocity_x) / - ps->o.animation_window_mass; - double acceleration_y = - (ps->o.animation_stiffness * neg_displacement_y - - ps->o.animation_dampening * w->animation_velocity_y) / - ps->o.animation_window_mass; - double acceleration_w = - (ps->o.animation_stiffness * neg_displacement_w - - ps->o.animation_dampening * w->animation_velocity_w) / - ps->o.animation_window_mass; - double acceleration_h = - (ps->o.animation_stiffness * neg_displacement_h - - ps->o.animation_dampening * w->animation_velocity_h) / - ps->o.animation_window_mass; - w->animation_velocity_x += acceleration_x * animation_delta; - w->animation_velocity_y += acceleration_y * animation_delta; - w->animation_velocity_w += acceleration_w * animation_delta; - w->animation_velocity_h += acceleration_h * animation_delta; - - // Animate window geometry - double new_animation_x = - w->animation_center_x + w->animation_velocity_x * animation_delta; - double new_animation_y = - w->animation_center_y + w->animation_velocity_y * animation_delta; - double new_animation_w = - w->animation_w + w->animation_velocity_w * animation_delta; - double new_animation_h = - w->animation_h + w->animation_velocity_h * animation_delta; - - // Negative new width/height causes segfault and it can happen - // when clamping disabled and shading a window - if (new_animation_h < 0) - new_animation_h = 0; - - if (new_animation_w < 0) - new_animation_w = 0; - - if (ps->o.animation_clamping) { - w->animation_center_x = clamp( - new_animation_x, - min2(w->animation_center_x, w->animation_dest_center_x), - max2(w->animation_center_x, w->animation_dest_center_x)); - w->animation_center_y = clamp( - new_animation_y, - min2(w->animation_center_y, w->animation_dest_center_y), - max2(w->animation_center_y, w->animation_dest_center_y)); - w->animation_w = - clamp(new_animation_w, - min2(w->animation_w, w->animation_dest_w), - max2(w->animation_w, w->animation_dest_w)); - w->animation_h = - clamp(new_animation_h, - min2(w->animation_h, w->animation_dest_h), - max2(w->animation_h, w->animation_dest_h)); - } else { - w->animation_center_x = new_animation_x; - w->animation_center_y = new_animation_y; - w->animation_w = new_animation_w; - w->animation_h = new_animation_h; - } - - // Now we are done doing the math; we just need to submit our - // changes (if there are any). - - struct win_geometry old_g = w->g; - double old_animation_progress = w->animation_progress; - new_animation_x = round(w->animation_center_x - w->animation_w * 0.5); - new_animation_y = round(w->animation_center_y - w->animation_h * 0.5); - new_animation_w = round(w->animation_w); - new_animation_h = round(w->animation_h); - - bool position_changed = - new_animation_x != old_g.x || new_animation_y != old_g.y; - bool size_changed = - new_animation_w != old_g.width || new_animation_h != old_g.height; - bool geometry_changed = position_changed || size_changed; - - // Mark past window region with damage - if (was_painted && geometry_changed) - add_damage_from_win(ps, w); - - double x_dist = w->animation_dest_center_x - w->animation_center_x; - double y_dist = w->animation_dest_center_y - w->animation_center_y; - double w_dist = w->animation_dest_w - w->animation_w; - double h_dist = w->animation_dest_h - w->animation_h; - w->animation_progress = - 1.0 - w->animation_inv_og_distance * - sqrt(x_dist * x_dist + y_dist * y_dist + - w_dist * w_dist + h_dist * h_dist); - - // When clamping disabled we don't want the overlayed image to - // fade in again because process is moving to negative value - if (w->animation_progress < old_animation_progress) - w->animation_progress = old_animation_progress; - - w->g.x = (int16_t)new_animation_x; - w->g.y = (int16_t)new_animation_y; - w->g.width = (uint16_t)new_animation_w; - w->g.height = (uint16_t)new_animation_h; - - // Submit window size change - if (size_changed) { - win_on_win_size_change(ps, w); - - pixman_region32_clear(&w->bounding_shape); - pixman_region32_fini(&w->bounding_shape); - pixman_region32_init_rect(&w->bounding_shape, 0, 0, - (uint)w->widthb, (uint)w->heightb); - - if (w->state != WSTATE_DESTROYING) - win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE); - - win_process_image_flags(ps, w); - } - // Mark new window region with damage - if (was_painted && geometry_changed) { - add_damage_from_win(ps, w); - w->reg_ignore_valid = false; - } - - // We can't check for 1 here as sometimes 1 = 0.999999999999999 - // in case of floating numbers - if (w->animation_progress >= 0.999999999) { - w->animation_progress = 1; - w->animation_velocity_x = 0.0; - w->animation_velocity_y = 0.0; - w->animation_velocity_w = 0.0; - w->animation_velocity_h = 0.0; - } - - if (!ps->root_desktop_switch_direction) { - if (w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) { - steps = 0; - double new_opacity = clamp( - w->opacity_target_old-w->animation_progress, - w->opacity_target, 1); - - if (new_opacity < w->opacity) - w->opacity = new_opacity; - - } else if (w->state == WSTATE_MAPPING) { - steps = 0; - double new_opacity = clamp( - w->animation_progress, - 0.0, w->opacity_target); - - if (new_opacity > w->opacity) - w->opacity = new_opacity; - } - } - - *animation_running = true; - } - - if (win_should_dim(ps, w) != w->dim) { - w->dim = win_should_dim(ps, w); - add_damage_from_win(ps, w); - } - - if (w->opacity != w->opacity_target) { - // Run fading - if (run_fade(ps, &w, steps)) { - *fade_running = true; - } - - // Add window to damaged area if its opacity changes - // If was_painted == false, and to_paint is also false, we don't care - // If was_painted == false, but to_paint is true, damage will be added in - // the loop below - if (was_painted && w->opacity != opacity_old) { - add_damage_from_win(ps, w); - } - - if (win_check_fade_finished(ps, w)) { - // the window has been destroyed because fading finished - continue; - } - - if (win_has_frame(w)) { - w->frame_opacity = ps->o.frame_opacity; - } else { - w->frame_opacity = 1.0; - } - - // Update window mode - w->mode = win_calc_mode(w); - - // Destroy all reg_ignore above when frame opaque state changes on - // SOLID mode - if (was_painted && w->mode != mode_old) { - w->reg_ignore_valid = false; - } - } - } - - if (*animation_running) - ps->animation_time = now; - - // Opacity will not change, from now on. - rc_region_t *last_reg_ignore = rc_region_new(); - - bool unredir_possible = false; - // Track whether it's the highest window to paint - bool is_highest = true; - bool reg_ignore_valid = true; - win_stack_foreach_managed(w, &ps->window_stack) { - __label__ skip_window; - bool to_paint = true; - // w->to_paint remembers whether this window is painted last time - const bool was_painted = w->to_paint; - - // Destroy reg_ignore if some window above us invalidated it - if (!reg_ignore_valid) { - rc_region_unref(&w->reg_ignore); - } - - // log_trace("%d %d %s", w->a.map_state, w->ever_damaged, w->name); - - // Give up if it's not damaged or invisible, or it's unmapped and its - // pixmap is gone (for example due to a ConfigureNotify), or when it's - // excluded - if (w->state == WSTATE_UNMAPPED || - unlikely(w->base.id == ps->debug_window || - w->client_win == ps->debug_window)) { - - if (!*fade_running || w->opacity == w->opacity_target) - to_paint = false; - - } else if (!w->ever_damaged && w->state != WSTATE_UNMAPPING && - w->state != WSTATE_DESTROYING) { - // Unmapping clears w->ever_damaged, but the fact that the window - // is fading out means it must have been damaged when it was still - // mapped (because unmap_win_start will skip fading if it wasn't), - // so we still need to paint it. - log_trace("Window %#010x (%s) will not be painted because it has " - "not received any damages", - w->base.id, w->name); - to_paint = false; - } else if (unlikely(w->g.x + w->g.width < 1 || w->g.y + w->g.height < 1 || - w->g.x >= ps->root_width || w->g.y >= ps->root_height)) { - log_trace("Window %#010x (%s) will not be painted because it is " - "positioned outside of the screen", - w->base.id, w->name); - to_paint = false; - } else if (unlikely((double)w->opacity * MAX_ALPHA < 1 && !w->blur_background)) { - /* TODO(yshui) for consistency, even a window has 0 opacity, we - * still probably need to blur its background, so to_paint - * shouldn't be false for them. */ - log_trace("Window %#010x (%s) will not be painted because it has " - "0 opacity", - w->base.id, w->name); - to_paint = false; - } else if (w->paint_excluded) { - log_trace("Window %#010x (%s) will not be painted because it is " - "excluded from painting", - w->base.id, w->name); - to_paint = false; - } else if (unlikely((w->flags & WIN_FLAGS_IMAGE_ERROR) != 0)) { - log_trace("Window %#010x (%s) will not be painted because it has " - "image errors", - w->base.id, w->name); - to_paint = false; - } - // log_trace("%s %d %d %d", w->name, to_paint, w->opacity, - // w->paint_excluded); - - // Add window to damaged area if its painting status changes - // or opacity changes - if (to_paint != was_painted) { - w->reg_ignore_valid = false; - add_damage_from_win(ps, w); - } - - // to_paint will never change after this point - if (!to_paint) { - goto skip_window; - } - - log_trace("Window %#010x (%s) will be painted", w->base.id, w->name); - - // Calculate shadow opacity - w->shadow_opacity = ps->o.shadow_opacity * w->opacity * ps->o.frame_opacity; - - // Generate ignore region for painting to reduce GPU load - if (!w->reg_ignore) { - w->reg_ignore = rc_region_ref(last_reg_ignore); - } - - // If the window is solid, or we enabled clipping for transparent windows, - // we add the window region to the ignored region - // Otherwise last_reg_ignore shouldn't change - if ((w->mode != WMODE_TRANS && !ps->o.force_win_blend) || - ps->o.transparent_clipping) { - // w->mode == WMODE_SOLID or WMODE_FRAME_TRANS - region_t *tmp = rc_region_new(); - if (w->mode == WMODE_SOLID) { - *tmp = - win_get_bounding_shape_global_without_corners_by_val(w); - } else { - // w->mode == WMODE_FRAME_TRANS - win_get_region_noframe_local_without_corners(w, tmp); - pixman_region32_intersect(tmp, tmp, &w->bounding_shape); - pixman_region32_translate(tmp, w->g.x, w->g.y); - } - - pixman_region32_union(tmp, tmp, last_reg_ignore); - rc_region_unref(&last_reg_ignore); - last_reg_ignore = tmp; - } - - // (Un)redirect screen - // We could definitely unredirect the screen when there's no window to - // paint, but this is typically unnecessary, may cause flickering when - // fading is enabled, and could create inconsistency when the wallpaper - // is not correctly set. - if (ps->o.unredir_if_possible && is_highest) { - if (w->mode == WMODE_SOLID && !ps->o.force_win_blend && - win_is_fullscreen(ps, w) && !w->unredir_if_possible_excluded) { - unredir_possible = true; - } - } - - // Unredirect screen if some window is requesting compositor bypass, even - // if that window is not on the top. - if (ps->o.unredir_if_possible && win_is_bypassing_compositor(ps, w) && - !w->unredir_if_possible_excluded) { - // Here we deviate from EWMH a bit. EWMH says we must not - // unredirect the screen if the window requesting bypassing would - // look different after unredirecting. Instead we always follow - // the request. - unredir_possible = true; - } - - w->prev_trans = bottom; - if (bottom) { - w->stacking_rank = bottom->stacking_rank + 1; - } else { - w->stacking_rank = 0; - } - bottom = w; - - // If the screen is not redirected and the window has redir_ignore set, - // this window should not cause the screen to become redirected - if (!(ps->o.wintype_option[w->window_type].redir_ignore && !ps->redirected)) { - is_highest = false; - } - - skip_window: - reg_ignore_valid = reg_ignore_valid && w->reg_ignore_valid; - w->reg_ignore_valid = true; - - // Avoid setting w->to_paint if w is freed - if (w) { - w->to_paint = to_paint; - } - } - - rc_region_unref(&last_reg_ignore); - - // If possible, unredirect all windows and stop painting - if (ps->o.redirected_force != UNSET) { - unredir_possible = !ps->o.redirected_force; - } else if (ps->o.unredir_if_possible && is_highest && !ps->redirected) { - // If there's no window to paint, and the screen isn't redirected, - // don't redirect it. - unredir_possible = true; - } - if (unredir_possible) { - if (ps->redirected) { - if (!ps->o.unredir_if_possible_delay || ps->tmout_unredir_hit) { - unredirect(ps); - } else if (!ev_is_active(&ps->unredir_timer)) { - ev_timer_set( - &ps->unredir_timer, - (double)ps->o.unredir_if_possible_delay / 1000.0, 0); - ev_timer_start(ps->loop, &ps->unredir_timer); - } - } - } else { - ev_timer_stop(ps->loop, &ps->unredir_timer); - if (!ps->redirected) { - if (!redirect_start(ps)) { - return NULL; - } - } - } - - return bottom; -} - -void root_damaged(session_t *ps) { - if (ps->root_tile_paint.pixmap) { - free_root_tile(ps); - } - - if (!ps->redirected) { - return; - } - - if (ps->backend_data) { - if (ps->root_image) { - ps->backend_data->ops->release_image(ps->backend_data, ps->root_image); - } - auto pixmap = x_get_root_back_pixmap(ps->c, ps->root, ps->atoms); - if (pixmap != XCB_NONE) { - ps->root_image = ps->backend_data->ops->bind_pixmap( - ps->backend_data, pixmap, x_get_visual_info(ps->c, ps->vis), false); - ps->backend_data->ops->set_image_property( - ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE, - ps->root_image, (int[]){ps->root_width, ps->root_height}); - } - } - - // Mark screen damaged - force_repaint(ps); -} - -/** - * Xlib error handler function. - */ -static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { - if (!should_ignore(ps_g, ev->serial)) { - x_print_error(ev->serial, ev->request_code, ev->minor_code, ev->error_code); - } - return 0; -} - -/** - * XCB error handler function. - */ -void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) { - if (!should_ignore(ps, err->sequence)) { - x_print_error(err->sequence, err->major_code, err->minor_code, err->error_code); - } -} - -/** - * Force a full-screen repaint. - */ -void force_repaint(session_t *ps) { - assert(pixman_region32_not_empty(&ps->screen_reg)); - queue_redraw(ps); - add_damage(ps, &ps->screen_reg); -} - -#ifdef CONFIG_DBUS -/** @name DBus hooks - */ -///@{ - -/** - * Set no_fading_openclose option. - * - * Don't affect fading already in progress - */ -void opts_set_no_fading_openclose(session_t *ps, bool newval) { - ps->o.no_fading_openclose = newval; -} - -//!@} -#endif - -/** - * Setup window properties, then register us with the compositor selection (_NET_WM_CM_S) - * - * @return 0 if success, 1 if compositor already running, -1 if error. - */ -static int register_cm(session_t *ps) { - assert(!ps->reg_win); - - ps->reg_win = x_new_id(ps->c); - auto e = xcb_request_check( - ps->c, xcb_create_window_checked(ps->c, XCB_COPY_FROM_PARENT, ps->reg_win, ps->root, - 0, 0, 1, 1, 0, XCB_NONE, ps->vis, 0, NULL)); - - if (e) { - log_fatal("Failed to create window."); - free(e); - return -1; - } - - const xcb_atom_t prop_atoms[] = { - ps->atoms->aWM_NAME, - ps->atoms->a_NET_WM_NAME, - ps->atoms->aWM_ICON_NAME, - }; - - const bool prop_is_utf8[] = {false, true, false}; - - // Set names and classes - for (size_t i = 0; i < ARR_SIZE(prop_atoms); i++) { - e = xcb_request_check( - ps->c, xcb_change_property_checked( - ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, prop_atoms[i], - prop_is_utf8[i] ? ps->atoms->aUTF8_STRING : XCB_ATOM_STRING, - 8, strlen("picom"), "picom")); - if (e) { - log_error_x_error(e, "Failed to set window property %d", - prop_atoms[i]); - free(e); - } - } - - const char picom_class[] = "picom\0picom"; - e = xcb_request_check( - ps->c, xcb_change_property_checked(ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, - ps->atoms->aWM_CLASS, XCB_ATOM_STRING, 8, - ARR_SIZE(picom_class), picom_class)); - if (e) { - log_error_x_error(e, "Failed to set the WM_CLASS property"); - free(e); - } - - // Set WM_CLIENT_MACHINE. As per EWMH, because we set _NET_WM_PID, we must also - // set WM_CLIENT_MACHINE. - { - const auto hostname_max = (unsigned long)sysconf(_SC_HOST_NAME_MAX); - char *hostname = malloc(hostname_max); - - if (gethostname(hostname, hostname_max) == 0) { - e = xcb_request_check( - ps->c, xcb_change_property_checked( - ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, - ps->atoms->aWM_CLIENT_MACHINE, XCB_ATOM_STRING, 8, - (uint32_t)strlen(hostname), hostname)); - if (e) { - log_error_x_error(e, "Failed to set the WM_CLIENT_MACHINE" - " property"); - free(e); - } - } else { - log_error_errno("Failed to get hostname"); - } - - free(hostname); - } - - // Set _NET_WM_PID - { - auto pid = getpid(); - xcb_change_property(ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, - ps->atoms->a_NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid); - } - - // Set COMPTON_VERSION - e = xcb_request_check( - ps->c, xcb_change_property_checked( - ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, - get_atom(ps->atoms, "COMPTON_VERSION"), XCB_ATOM_STRING, 8, - (uint32_t)strlen(COMPTON_VERSION), COMPTON_VERSION)); - if (e) { - log_error_x_error(e, "Failed to set COMPTON_VERSION."); - free(e); - } - - // Acquire X Selection _NET_WM_CM_S? - if (!ps->o.no_x_selection) { - const char register_prop[] = "_NET_WM_CM_S"; - xcb_atom_t atom; - - char *buf = NULL; - if (asprintf(&buf, "%s%d", register_prop, ps->scr) < 0) { - log_fatal("Failed to allocate memory"); - return -1; - } - atom = get_atom(ps->atoms, buf); - free(buf); - - xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply( - ps->c, xcb_get_selection_owner(ps->c, atom), NULL); - - if (reply && reply->owner != XCB_NONE) { - // Another compositor already running - free(reply); - return 1; - } - free(reply); - xcb_set_selection_owner(ps->c, ps->reg_win, atom, 0); - } - - return 0; -} - -/** - * Write PID to a file. - */ -static inline bool write_pid(session_t *ps) { - if (!ps->o.write_pid_path) { - return true; - } - - FILE *f = fopen(ps->o.write_pid_path, "w"); - if (unlikely(!f)) { - log_error("Failed to write PID to \"%s\".", ps->o.write_pid_path); - return false; - } - - fprintf(f, "%ld\n", (long)getpid()); - fclose(f); - - return true; -} - -/** - * Update refresh rate info with X Randr extension. - */ -void update_refresh_rate(session_t *ps) { - xcb_randr_get_screen_info_reply_t *randr_info = xcb_randr_get_screen_info_reply( - ps->c, xcb_randr_get_screen_info(ps->c, ps->root), NULL); - - if (!randr_info) - return; - ps->refresh_rate = randr_info->rate; - free(randr_info); - - if (ps->refresh_rate) - ps->refresh_intv = US_PER_SEC / ps->refresh_rate; - else - ps->refresh_intv = 0; -} - -/** - * Initialize refresh-rated based software optimization. - * - * @return true for success, false otherwise - */ -static bool swopti_init(session_t *ps) { - log_warn("--sw-opti is going to be deprecated. If you get real benefits from " - "using " - "this option, please open an issue to let us know."); - // Prepare refresh rate - // Check if user provides one - ps->refresh_rate = ps->o.refresh_rate; - if (ps->refresh_rate) - ps->refresh_intv = US_PER_SEC / ps->refresh_rate; - - // Auto-detect refresh rate otherwise - if (!ps->refresh_rate && ps->randr_exists) { - update_refresh_rate(ps); - } - - // Turn off vsync_sw if we can't get the refresh rate - if (!ps->refresh_rate) - return false; - - return true; -} - -/** - * Modify a struct timeval timeout value to render at a fixed pace. - * - * @param ps current session - * @param[in,out] ptv pointer to the timeout - */ -static double swopti_handle_timeout(session_t *ps) { - if (!ps->refresh_intv) - return 0; - - // Get the microsecond offset of the time when the we reach the timeout - // I don't think a 32-bit long could overflow here. - long offset = (get_time_timeval().tv_usec - ps->paint_tm_offset) % ps->refresh_intv; - // XXX this formula dones't work if refresh rate is not a whole number - if (offset < 0) - offset += ps->refresh_intv; - - // If the target time is sufficiently close to a refresh time, don't add - // an offset, to avoid certain blocking conditions. - if (offset < SWOPTI_TOLERANCE || offset > ps->refresh_intv - SWOPTI_TOLERANCE) - return 0; - - // Add an offset so we wait until the next refresh after timeout - return (double)(ps->refresh_intv - offset) / 1e6; -} - -/** - * Initialize X composite overlay window. - */ -static bool init_overlay(session_t *ps) { - xcb_composite_get_overlay_window_reply_t *reply = - xcb_composite_get_overlay_window_reply( - ps->c, xcb_composite_get_overlay_window(ps->c, ps->root), NULL); - if (reply) { - ps->overlay = reply->overlay_win; - free(reply); - } else { - ps->overlay = XCB_NONE; - } - if (ps->overlay != XCB_NONE) { - // Set window region of the overlay window, code stolen from - // compiz-0.8.8 - if (!XCB_AWAIT_VOID(xcb_shape_mask, ps->c, XCB_SHAPE_SO_SET, - XCB_SHAPE_SK_BOUNDING, ps->overlay, 0, 0, 0)) { - log_fatal("Failed to set the bounding shape of overlay, giving " - "up."); - return false; - } - if (!XCB_AWAIT_VOID(xcb_shape_rectangles, ps->c, XCB_SHAPE_SO_SET, - XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, - ps->overlay, 0, 0, 0, NULL)) { - log_fatal("Failed to set the input shape of overlay, giving up."); - return false; - } - - // Listen to Expose events on the overlay - xcb_change_window_attributes(ps->c, ps->overlay, XCB_CW_EVENT_MASK, - (const uint32_t[]){XCB_EVENT_MASK_EXPOSURE}); - - // Retrieve DamageNotify on root window if we are painting on an - // overlay - // root_damage = XDamageCreate(ps->dpy, root, XDamageReportNonEmpty); - - // Unmap the overlay, we will map it when needed in redirect_start - XCB_AWAIT_VOID(xcb_unmap_window, ps->c, ps->overlay); - } else { - log_error("Cannot get X Composite overlay window. Falling " - "back to painting on root window."); - } - log_debug("overlay = %#010x", ps->overlay); - - return true; -} - -static bool init_debug_window(session_t *ps) { - xcb_colormap_t colormap = x_new_id(ps->c); - ps->debug_window = x_new_id(ps->c); - - auto err = xcb_request_check( - ps->c, xcb_create_colormap_checked(ps->c, XCB_COLORMAP_ALLOC_NONE, colormap, - ps->root, ps->vis)); - if (err) { - goto err_out; - } - - err = xcb_request_check( - ps->c, xcb_create_window_checked(ps->c, (uint8_t)ps->depth, ps->debug_window, - ps->root, 0, 0, to_u16_checked(ps->root_width), - to_u16_checked(ps->root_height), 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, ps->vis, - XCB_CW_COLORMAP, (uint32_t[]){colormap, 0})); - if (err) { - goto err_out; - } - - err = xcb_request_check(ps->c, xcb_map_window(ps->c, ps->debug_window)); - if (err) { - goto err_out; - } - return true; - -err_out: - free(err); - return false; -} - -xcb_window_t session_get_target_window(session_t *ps) { - if (ps->o.debug_mode) { - return ps->debug_window; - } - return ps->overlay != XCB_NONE ? ps->overlay : ps->root; -} - -uint8_t session_redirection_mode(session_t *ps) { - if (ps->o.debug_mode) { - // If the backend is not rendering to the screen, we don't need to - // take over the screen. - assert(ps->o.experimental_backends); - return XCB_COMPOSITE_REDIRECT_AUTOMATIC; - } - if (ps->o.experimental_backends && !backend_list[ps->o.backend]->present) { - // if the backend doesn't render anything, we don't need to take over the - // screen. - return XCB_COMPOSITE_REDIRECT_AUTOMATIC; - } - return XCB_COMPOSITE_REDIRECT_MANUAL; -} - -/** - * Redirect all windows. - * - * @return whether the operation succeeded or not - */ -static bool redirect_start(session_t *ps) { - assert(!ps->redirected); - log_debug("Redirecting the screen."); - - // Map overlay window. Done firstly according to this: - // https://bugzilla.gnome.org/show_bug.cgi?id=597014 - if (ps->overlay != XCB_NONE) { - xcb_map_window(ps->c, ps->overlay); - } - - bool success = XCB_AWAIT_VOID(xcb_composite_redirect_subwindows, ps->c, ps->root, - session_redirection_mode(ps)); - if (!success) { - log_fatal("Another composite manager is already running " - "(and does not handle _NET_WM_CM_Sn correctly)"); - return false; - } - - x_sync(ps->c); - - if (!initialize_backend(ps)) { - return false; - } - - if (ps->o.experimental_backends) { - assert(ps->backend_data); - ps->ndamage = ps->backend_data->ops->max_buffer_age; - } else { - ps->ndamage = maximum_buffer_age(ps); - } - ps->damage_ring = ccalloc(ps->ndamage, region_t); - ps->damage = ps->damage_ring + ps->ndamage - 1; - - for (int i = 0; i < ps->ndamage; i++) { - pixman_region32_init(&ps->damage_ring[i]); - } - - // Must call XSync() here - x_sync(ps->c); - - ps->redirected = true; - ps->first_frame = true; - - // Re-detect driver since we now have a backend - ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root); - apply_driver_workarounds(ps, ps->drivers); - - root_damaged(ps); - - // Repaint the whole screen - force_repaint(ps); - log_debug("Screen redirected."); - return true; -} - -/** - * Unredirect all windows. - */ -static void unredirect(session_t *ps) { - assert(ps->redirected); - log_debug("Unredirecting the screen."); - - destroy_backend(ps); - - xcb_composite_unredirect_subwindows(ps->c, ps->root, session_redirection_mode(ps)); - // Unmap overlay window - if (ps->overlay != XCB_NONE) { - xcb_unmap_window(ps->c, ps->overlay); - } - - // Free the damage ring - for (int i = 0; i < ps->ndamage; ++i) { - pixman_region32_fini(&ps->damage_ring[i]); - } - ps->ndamage = 0; - free(ps->damage_ring); - ps->damage_ring = ps->damage = NULL; - - // Must call XSync() here - x_sync(ps->c); - - ps->redirected = false; - log_debug("Screen unredirected."); -} - -// Handle queued events before we go to sleep -static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents attr_unused) { - session_t *ps = session_ptr(w, event_check); - xcb_generic_event_t *ev; - while ((ev = xcb_poll_for_queued_event(ps->c))) { - ev_handle(ps, ev); - free(ev); - }; - // Flush because if we go into sleep when there is still - // requests in the outgoing buffer, they will not be sent - // for an indefinite amount of time. - // Use XFlush here too, we might still use some Xlib functions - // because OpenGL. - XFlush(ps->dpy); - xcb_flush(ps->c); - int err = xcb_connection_has_error(ps->c); - if (err) { - log_fatal("X11 server connection broke (error %d)", err); - exit(1); - } -} - -static void handle_new_windows(session_t *ps) { - list_foreach_safe(struct win, w, &ps->window_stack, stack_neighbour) { - if (w->is_new) { - auto new_w = fill_win(ps, w); - if (!new_w->managed) { - continue; - } - auto mw = (struct managed_win *)new_w; - if (mw->a.map_state == XCB_MAP_STATE_VIEWABLE) { - win_set_flags(mw, WIN_FLAGS_MAPPED); - - // This window might be damaged before we called fill_win - // and created the damage handle. And there is no way for - // us to find out. So just blindly mark it damaged - mw->ever_damaged = true; - } - } - } -} - -static void refresh_windows(session_t *ps) { - win_stack_foreach_managed(w, &ps->window_stack) { - win_process_update_flags(ps, w); - } -} - -static void refresh_images(session_t *ps) { - win_stack_foreach_managed(w, &ps->window_stack) { - win_process_image_flags(ps, w); - } -} - -/** - * Unredirection timeout callback. - */ -static void tmout_unredir_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) { - session_t *ps = session_ptr(w, unredir_timer); - ps->tmout_unredir_hit = true; - queue_redraw(ps); -} - -static void fade_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) { - session_t *ps = session_ptr(w, fade_timer); - queue_redraw(ps); -} - -static void animation_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) { - session_t *ps = session_ptr(w, animation_timer); - queue_redraw(ps); -} - -static void handle_pending_updates(EV_P_ struct session *ps) { - if (ps->pending_updates) { - log_debug("Delayed handling of events, entering critical section"); - auto e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); - if (e) { - log_fatal_x_error(e, "failed to grab x server"); - return quit(ps); - } - - ps->server_grabbed = true; - - // Catching up with X server - handle_queued_x_events(EV_A_ & ps->event_check, 0); - - // Call fill_win on new windows - handle_new_windows(ps); - - // Handle screen changes - // This HAS TO be called before refresh_windows, as handle_root_flags - // could call configure_root, which will release images and mark them - // stale. - handle_root_flags(ps); - - // Process window flags (window mapping) - refresh_windows(ps); - - { - auto r = xcb_get_input_focus_reply( - ps->c, xcb_get_input_focus(ps->c), NULL); - if (!ps->active_win || (r && r->focus != ps->active_win->base.id)) { - recheck_focus(ps); - } - free(r); - } - - // Process window flags (stale images) - refresh_images(ps); - - e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); - if (e) { - log_fatal_x_error(e, "failed to ungrab x server"); - return quit(ps); - } - - ps->server_grabbed = false; - ps->pending_updates = false; - log_debug("Exited critical section"); - } -} - -static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { - handle_pending_updates(EV_A_ ps); - - if (ps->first_frame) { - // If we are still rendering the first frame, if some of the windows are - // unmapped/destroyed during the above handle_pending_updates() call, they - // won't have pixmap before we rendered it, causing us to crash. - // But we will only render them if they are in fading. So we just skip - // fading for all windows here. - // - // Using foreach_safe here since skipping fading can cause window to be - // freed if it's destroyed. - win_stack_foreach_managed_safe(w, &ps->window_stack) { - auto _ attr_unused = win_skip_fading(ps, w); - } - } - - if (ps->o.benchmark) { - if (ps->o.benchmark_wid) { - auto w = find_managed_win(ps, ps->o.benchmark_wid); - if (!w) { - log_fatal("Couldn't find specified benchmark window."); - exit(1); - } - add_damage_from_win(ps, w); - } else { - force_repaint(ps); - } - } - - /* TODO(yshui) Have a stripped down version of paint_preprocess that is used when - * screen is not redirected. its sole purpose should be to decide whether the - * screen should be redirected. */ - bool fade_running = false; - bool animation_running = false; - bool was_redirected = ps->redirected; - auto bottom = paint_preprocess(ps, &fade_running, &animation_running); - ps->tmout_unredir_hit = false; - - if (!was_redirected && ps->redirected) { - // paint_preprocess redirected the screen, which might change the state of - // some of the windows (e.g. the window image might become stale). - // so we rerun _draw_callback to make sure the rendering decision we make - // is up-to-date, and all the new flags got handled. - // - // TODO(yshui) This is not ideal, we should try to avoid setting window - // flags in paint_preprocess. - log_debug("Re-run _draw_callback"); - return draw_callback_impl(EV_A_ ps, revents); - } - - // Start/stop fade timer depends on whether window are fading - if (!fade_running && ev_is_active(&ps->fade_timer)) { - ev_timer_stop(EV_A_ & ps->fade_timer); - } else if (fade_running && !ev_is_active(&ps->fade_timer)) { - ev_timer_set(&ps->fade_timer, fade_timeout(ps), 0); - ev_timer_start(EV_A_ & ps->fade_timer); - } - // Start/stop animation timer depends on whether windows are animating - if (!animation_running && ev_is_active(&ps->animation_timer)) { - ev_timer_stop(EV_A_ & ps->animation_timer); - } else if (animation_running && !ev_is_active(&ps->animation_timer)) { - ev_timer_set(&ps->animation_timer, 0, 0); - ev_timer_start(EV_A_ & ps->animation_timer); - } - - // If the screen is unredirected, free all_damage to stop painting - if (ps->redirected && ps->o.stoppaint_force != ON) { - static int paint = 0; - - log_trace("Render start, frame %d", paint); - if (ps->o.experimental_backends) { - paint_all_new(ps, bottom, false); - } else { - paint_all(ps, bottom, false); - } - log_trace("Render end"); - - ps->first_frame = false; - paint++; - if (ps->o.benchmark && paint >= ps->o.benchmark) { - exit(0); - } - } - - if (!fade_running) { - ps->fade_time = 0L; - } - if (!animation_running) { - ps->animation_time = 0L; - ps->root_desktop_switch_direction = 0; - } - - // TODO(yshui) Investigate how big the X critical section needs to be. There are - // suggestions that rendering should be in the critical section as well. - - ps->redraw_needed = false; -} - -static void draw_callback(EV_P_ ev_idle *w, int revents) { - // This function is not used if we are using --swopti - session_t *ps = session_ptr(w, draw_idle); - - draw_callback_impl(EV_A_ ps, revents); - - // Don't do painting non-stop unless we are in benchmark mode - if (!ps->o.benchmark) { - ev_idle_stop(EV_A_ & ps->draw_idle); - } -} - -static void delayed_draw_timer_callback(EV_P_ ev_timer *w, int revents) { - session_t *ps = session_ptr(w, delayed_draw_timer); - draw_callback_impl(EV_A_ ps, revents); - - // We might have stopped the ev_idle in delayed_draw_callback, - // so we restart it if we are in benchmark mode - if (ps->o.benchmark) - ev_idle_start(EV_A_ & ps->draw_idle); -} - -static void delayed_draw_callback(EV_P_ ev_idle *w, int revents) { - // This function is only used if we are using --swopti - session_t *ps = session_ptr(w, draw_idle); - assert(ps->redraw_needed); - assert(!ev_is_active(&ps->delayed_draw_timer)); - - double delay = swopti_handle_timeout(ps); - if (delay < 1e-6) { - if (!ps->o.benchmark) { - ev_idle_stop(EV_A_ & ps->draw_idle); - } - return draw_callback_impl(EV_A_ ps, revents); - } - - // This is a little bit hacky. When we get to this point in code, we need - // to update the screen , but we will only be updating after a delay, So - // we want to stop the ev_idle, so this callback doesn't get call repeatedly - // during the delay, we also want queue_redraw to not restart the ev_idle. - // So we stop ev_idle and leave ps->redraw_needed to be true. (effectively, - // ps->redraw_needed means if redraw is needed or if draw is in progress). - // - // We do this anyway even if we are in benchmark mode. That means we will - // have to restart draw_idle after the draw actually happened when we are in - // benchmark mode. - ev_idle_stop(EV_A_ & ps->draw_idle); - - ev_timer_set(&ps->delayed_draw_timer, delay, 0); - ev_timer_start(EV_A_ & ps->delayed_draw_timer); -} - -static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) { - session_t *ps = (session_t *)w; - xcb_generic_event_t *ev = xcb_poll_for_event(ps->c); - if (ev) { - ev_handle(ps, ev); - free(ev); - } -} - -/** - * Turn on the program reset flag. - * - * This will result in the compostior resetting itself after next paint. - */ -static void reset_enable(EV_P_ ev_signal *w attr_unused, int revents attr_unused) { - log_info("picom is resetting..."); - ev_break(EV_A_ EVBREAK_ALL); -} - -static void exit_enable(EV_P attr_unused, ev_signal *w, int revents attr_unused) { - session_t *ps = session_ptr(w, int_signal); - log_info("picom is quitting..."); - quit(ps); -} - -static void config_file_change_cb(void *_ps) { - auto ps = (struct session *)_ps; - reset_enable(ps->loop, NULL, 0); -} - -/** - * Initialize a session. - * - * @param argc number of commandline arguments - * @param argv commandline arguments - * @param dpy the X Display - * @param config_file the path to the config file - * @param all_xerros whether we should report all X errors - * @param fork whether we will fork after initialization - */ -static session_t *session_init(int argc, char **argv, Display *dpy, - const char *config_file, bool all_xerrors, bool fork) { - static const session_t s_def = { - .backend_data = NULL, - .dpy = NULL, - .scr = 0, - .c = NULL, - .vis = 0, - .depth = 0, - .root = XCB_NONE, - .root_height = 0, - .root_width = 0, - // .root_damage = XCB_NONE, - .overlay = XCB_NONE, - .root_tile_fill = false, - .root_tile_paint = PAINT_INIT, - .tgt_picture = XCB_NONE, - .tgt_buffer = PAINT_INIT, - .reg_win = XCB_NONE, -#ifdef CONFIG_OPENGL - .glx_prog_win = GLX_PROG_MAIN_INIT, -#endif - .redirected = false, - .alpha_picts = NULL, - .fade_time = 0L, - .animation_time = 0L, - .ignore_head = NULL, - .ignore_tail = NULL, - .quit = false, - - .expose_rects = NULL, - .size_expose = 0, - .n_expose = 0, - - .windows = NULL, - .active_win = NULL, - .active_leader = XCB_NONE, - - .black_picture = XCB_NONE, - .cshadow_picture = XCB_NONE, - .white_picture = XCB_NONE, - .gaussian_map = NULL, - - .refresh_rate = 0, - .refresh_intv = 0UL, - .paint_tm_offset = 0L, - -#ifdef CONFIG_VSYNC_DRM - .drm_fd = -1, -#endif - - .xfixes_event = 0, - .xfixes_error = 0, - .damage_event = 0, - .damage_error = 0, - .render_event = 0, - .render_error = 0, - .composite_event = 0, - .composite_error = 0, - .composite_opcode = 0, - .shape_exists = false, - .shape_event = 0, - .shape_error = 0, - .randr_exists = 0, - .randr_event = 0, - .randr_error = 0, - .glx_exists = false, - .glx_event = 0, - .glx_error = 0, - .xrfilter_convolution_exists = false, - - .atoms_wintypes = {0}, - .track_atom_lst = NULL, - -#ifdef CONFIG_DBUS - .dbus_data = NULL, -#endif - }; - - auto stderr_logger = stderr_logger_new(); - if (stderr_logger) { - // stderr logger might fail to create if we are already - // daemonized. - log_add_target_tls(stderr_logger); - } - - // Allocate a session and copy default values into it - session_t *ps = cmalloc(session_t); - *ps = s_def; - list_init_head(&ps->window_stack); - ps->loop = EV_DEFAULT; - pixman_region32_init(&ps->screen_reg); - - ps->ignore_tail = &ps->ignore_head; - - ps->o.show_all_xerrors = all_xerrors; - - // Use the same Display across reset, primarily for resource leak checking - ps->dpy = dpy; - ps->c = XGetXCBConnection(ps->dpy); - - const xcb_query_extension_reply_t *ext_info; - - ps->previous_xerror_handler = XSetErrorHandler(xerror); - - ps->scr = DefaultScreen(ps->dpy); - - auto screen = x_screen_of_display(ps->c, ps->scr); - ps->vis = screen->root_visual; - ps->depth = screen->root_depth; - ps->root = screen->root; - ps->root_width = screen->width_in_pixels; - ps->root_height = screen->height_in_pixels; - - // Start listening to events on root earlier to catch all possible - // root geometry changes - auto e = xcb_request_check( - ps->c, xcb_change_window_attributes_checked( - ps->c, ps->root, XCB_CW_EVENT_MASK, - (const uint32_t[]){XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | - XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | - XCB_EVENT_MASK_PROPERTY_CHANGE})); - if (e) { - log_error_x_error(e, "Failed to setup root window event mask"); - } - - xcb_prefetch_extension_data(ps->c, &xcb_render_id); - xcb_prefetch_extension_data(ps->c, &xcb_composite_id); - xcb_prefetch_extension_data(ps->c, &xcb_damage_id); - xcb_prefetch_extension_data(ps->c, &xcb_shape_id); - xcb_prefetch_extension_data(ps->c, &xcb_xfixes_id); - xcb_prefetch_extension_data(ps->c, &xcb_randr_id); - xcb_prefetch_extension_data(ps->c, &xcb_xinerama_id); - xcb_prefetch_extension_data(ps->c, &xcb_present_id); - xcb_prefetch_extension_data(ps->c, &xcb_sync_id); - xcb_prefetch_extension_data(ps->c, &xcb_glx_id); - - ext_info = xcb_get_extension_data(ps->c, &xcb_render_id); - if (!ext_info || !ext_info->present) { - log_fatal("No render extension"); - exit(1); - } - ps->render_event = ext_info->first_event; - ps->render_error = ext_info->first_error; - - ext_info = xcb_get_extension_data(ps->c, &xcb_composite_id); - if (!ext_info || !ext_info->present) { - log_fatal("No composite extension"); - exit(1); - } - ps->composite_opcode = ext_info->major_opcode; - ps->composite_event = ext_info->first_event; - ps->composite_error = ext_info->first_error; - - { - xcb_composite_query_version_reply_t *reply = xcb_composite_query_version_reply( - ps->c, - xcb_composite_query_version(ps->c, XCB_COMPOSITE_MAJOR_VERSION, - XCB_COMPOSITE_MINOR_VERSION), - NULL); - - if (!reply || (reply->major_version == 0 && reply->minor_version < 2)) { - log_fatal("Your X server doesn't have Composite >= 0.2 support, " - "we cannot proceed."); - exit(1); - } - free(reply); - } - - ext_info = xcb_get_extension_data(ps->c, &xcb_damage_id); - if (!ext_info || !ext_info->present) { - log_fatal("No damage extension"); - exit(1); - } - ps->damage_event = ext_info->first_event; - ps->damage_error = ext_info->first_error; - xcb_discard_reply(ps->c, xcb_damage_query_version(ps->c, XCB_DAMAGE_MAJOR_VERSION, - XCB_DAMAGE_MINOR_VERSION) - .sequence); - - ext_info = xcb_get_extension_data(ps->c, &xcb_xfixes_id); - if (!ext_info || !ext_info->present) { - log_fatal("No XFixes extension"); - exit(1); - } - ps->xfixes_event = ext_info->first_event; - ps->xfixes_error = ext_info->first_error; - xcb_discard_reply(ps->c, xcb_xfixes_query_version(ps->c, XCB_XFIXES_MAJOR_VERSION, - XCB_XFIXES_MINOR_VERSION) - .sequence); - - ps->damaged_region = x_new_id(ps->c); - if (!XCB_AWAIT_VOID(xcb_xfixes_create_region, ps->c, ps->damaged_region, 0, NULL)) { - log_fatal("Failed to create a XFixes region"); - goto err; - } - - ext_info = xcb_get_extension_data(ps->c, &xcb_glx_id); - if (ext_info && ext_info->present) { - ps->glx_exists = true; - ps->glx_error = ext_info->first_error; - ps->glx_event = ext_info->first_event; - } - - // Parse configuration file - win_option_mask_t winopt_mask[NUM_WINTYPES] = {{0}}; - bool shadow_enabled = false, fading_enable = false, hasneg = false; - char *config_file_to_free = NULL; - config_file = config_file_to_free = parse_config( - &ps->o, config_file, &shadow_enabled, &fading_enable, &hasneg, winopt_mask); - - if (IS_ERR(config_file_to_free)) { - return NULL; - } - - // Parse all of the rest command line options - if (!get_cfg(&ps->o, argc, argv, shadow_enabled, fading_enable, hasneg, winopt_mask)) { - log_fatal("Failed to get configuration, usually mean you have specified " - "invalid options."); - return NULL; - } - - if (ps->o.logpath) { - auto l = file_logger_new(ps->o.logpath); - if (l) { - log_info("Switching to log file: %s", ps->o.logpath); - if (stderr_logger) { - log_remove_target_tls(stderr_logger); - stderr_logger = NULL; - } - log_add_target_tls(l); - stderr_logger = NULL; - } else { - log_error("Failed to setup log file %s, I will keep using stderr", - ps->o.logpath); - } - } - - if (strstr(argv[0], "compton")) { - log_warn("This compositor has been renamed to \"picom\", the \"compton\" " - "binary will not be installed in the future."); - } - - ps->atoms = init_atoms(ps->c); - ps->atoms_wintypes[WINTYPE_UNKNOWN] = 0; -#define SET_WM_TYPE_ATOM(x) \ - ps->atoms_wintypes[WINTYPE_##x] = ps->atoms->a_NET_WM_WINDOW_TYPE_##x - SET_WM_TYPE_ATOM(DESKTOP); - SET_WM_TYPE_ATOM(DOCK); - SET_WM_TYPE_ATOM(TOOLBAR); - SET_WM_TYPE_ATOM(MENU); - SET_WM_TYPE_ATOM(UTILITY); - SET_WM_TYPE_ATOM(SPLASH); - SET_WM_TYPE_ATOM(DIALOG); - SET_WM_TYPE_ATOM(NORMAL); - SET_WM_TYPE_ATOM(DROPDOWN_MENU); - SET_WM_TYPE_ATOM(POPUP_MENU); - SET_WM_TYPE_ATOM(TOOLTIP); - SET_WM_TYPE_ATOM(NOTIFICATION); - SET_WM_TYPE_ATOM(COMBO); - SET_WM_TYPE_ATOM(DND); -#undef SET_WM_TYPE_ATOM - - // Get needed atoms for c2 condition lists - if (!(c2_list_postprocess(ps, ps->o.unredir_if_possible_blacklist) && - c2_list_postprocess(ps, ps->o.paint_blacklist) && - c2_list_postprocess(ps, ps->o.shadow_blacklist) && - c2_list_postprocess(ps, ps->o.shadow_clip_list) && - c2_list_postprocess(ps, ps->o.fade_blacklist) && - c2_list_postprocess(ps, ps->o.blur_background_blacklist) && - c2_list_postprocess(ps, ps->o.invert_color_list) && - c2_list_postprocess(ps, ps->o.opacity_rules) && - c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) && - c2_list_postprocess(ps, ps->o.focus_blacklist))) { - log_error("Post-processing of conditionals failed, some of your rules " - "might not work"); - } - - ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius); - sum_kernel_preprocess(ps->gaussian_map); - - rebuild_shadow_exclude_reg(ps); - - // Query X Shape - ext_info = xcb_get_extension_data(ps->c, &xcb_shape_id); - if (ext_info && ext_info->present) { - ps->shape_event = ext_info->first_event; - ps->shape_error = ext_info->first_error; - ps->shape_exists = true; - } - - ext_info = xcb_get_extension_data(ps->c, &xcb_randr_id); - if (ext_info && ext_info->present) { - ps->randr_exists = true; - ps->randr_event = ext_info->first_event; - ps->randr_error = ext_info->first_error; - } - - ext_info = xcb_get_extension_data(ps->c, &xcb_present_id); - if (ext_info && ext_info->present) { - auto r = xcb_present_query_version_reply( - ps->c, - xcb_present_query_version(ps->c, XCB_PRESENT_MAJOR_VERSION, - XCB_PRESENT_MINOR_VERSION), - NULL); - if (r) { - ps->present_exists = true; - free(r); - } - } - - // Query X Sync - ext_info = xcb_get_extension_data(ps->c, &xcb_sync_id); - if (ext_info && ext_info->present) { - ps->xsync_error = ext_info->first_error; - ps->xsync_event = ext_info->first_event; - // Need X Sync 3.1 for fences - auto r = xcb_sync_initialize_reply( - ps->c, - xcb_sync_initialize(ps->c, XCB_SYNC_MAJOR_VERSION, XCB_SYNC_MINOR_VERSION), - NULL); - if (r && (r->major_version > 3 || - (r->major_version == 3 && r->minor_version >= 1))) { - ps->xsync_exists = true; - free(r); - } - } - - ps->sync_fence = XCB_NONE; - if (ps->xsync_exists) { - ps->sync_fence = x_new_id(ps->c); - e = xcb_request_check( - ps->c, xcb_sync_create_fence(ps->c, ps->root, ps->sync_fence, 0)); - if (e) { - if (ps->o.xrender_sync_fence) { - log_error_x_error(e, "Failed to create a XSync fence. " - "xrender-sync-fence will be " - "disabled"); - ps->o.xrender_sync_fence = false; - } - ps->sync_fence = XCB_NONE; - free(e); - } - } else if (ps->o.xrender_sync_fence) { - log_error("XSync extension not found. No XSync fence sync is " - "possible. (xrender-sync-fence can't be enabled)"); - ps->o.xrender_sync_fence = false; - } - - // Query X RandR - if ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop) { - if (!ps->randr_exists) { - log_fatal("No XRandR extension. sw-opti, refresh-rate or " - "xinerama-shadow-crop " - "cannot be enabled."); - goto err; - } - } - - // Query X Xinerama extension - if (ps->o.xinerama_shadow_crop) { - ext_info = xcb_get_extension_data(ps->c, &xcb_xinerama_id); - ps->xinerama_exists = ext_info && ext_info->present; - } - - rebuild_screen_reg(ps); - - bool compositor_running = false; - if (session_redirection_mode(ps) == XCB_COMPOSITE_REDIRECT_MANUAL) { - // We are running in the manual redirection mode, meaning we are running - // as a proper compositor. So we need to register us as a compositor, etc. - - // We are also here when --diagnostics is set. We want to be here because - // that gives us more diagnostic information. - - // Create registration window - int ret = register_cm(ps); - if (ret == -1) { - exit(1); - } - - compositor_running = ret == 1; - if (compositor_running) { - // Don't take the overlay when there is another compositor - // running, so we don't disrupt it. - - // If we are printing diagnostic, we will continue a bit further - // to get more diagnostic information, otherwise we will exit. - if (!ps->o.print_diagnostics) { - log_fatal("Another composite manager is already running"); - exit(1); - } - } else { - if (!init_overlay(ps)) { - goto err; - } - } - } else { - // We are here if we don't really function as a compositor, so we are not - // taking over the screen, and we don't need to register as a compositor - - // If we are in debug mode, we need to create a window for rendering if - // the backend supports presenting. - - // The old backends doesn't have a automatic redirection mode - log_info("The compositor is started in automatic redirection mode."); - assert(ps->o.experimental_backends); - - if (backend_list[ps->o.backend]->present) { - // If the backend has `present`, we couldn't be in automatic - // redirection mode unless we are in debug mode. - assert(ps->o.debug_mode); - if (!init_debug_window(ps)) { - goto err; - } - } - } - - ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root); - apply_driver_workarounds(ps, ps->drivers); - - // Initialize filters, must be preceded by OpenGL context creation - if (!ps->o.experimental_backends && !init_render(ps)) { - log_fatal("Failed to initialize the backend"); - exit(1); - } - - if (ps->o.print_diagnostics) { - print_diagnostics(ps, config_file, compositor_running); - free(config_file_to_free); - exit(0); - } - - ps->file_watch_handle = file_watch_init(ps->loop); - if (ps->file_watch_handle && config_file) { - file_watch_add(ps->file_watch_handle, config_file, config_file_change_cb, ps); - } - - free(config_file_to_free); - - if (bkend_use_glx(ps) && !ps->o.experimental_backends) { - auto gl_logger = gl_string_marker_logger_new(); - if (gl_logger) { - log_info("Enabling gl string marker"); - log_add_target_tls(gl_logger); - } - } - - if (ps->o.experimental_backends) { - if (ps->o.monitor_repaint && !backend_list[ps->o.backend]->fill) { - log_warn("--monitor-repaint is not supported by the backend, " - "disabling"); - ps->o.monitor_repaint = false; - } - } - - // Initialize software optimization - if (ps->o.sw_opti) - ps->o.sw_opti = swopti_init(ps); - - // Monitor screen changes if vsync_sw is enabled and we are using - // an auto-detected refresh rate, or when Xinerama features are enabled - if (ps->randr_exists && - ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop)) - xcb_randr_select_input(ps->c, ps->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE); - - cxinerama_upd_scrs(ps); - - { - xcb_render_create_picture_value_list_t pa = { - .subwindowmode = IncludeInferiors, - }; - - ps->root_picture = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, ps->root, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); - if (ps->overlay != XCB_NONE) { - ps->tgt_picture = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, ps->overlay, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); - } else - ps->tgt_picture = ps->root_picture; - } - - ev_io_init(&ps->xiow, x_event_callback, ConnectionNumber(ps->dpy), EV_READ); - ev_io_start(ps->loop, &ps->xiow); - ev_init(&ps->unredir_timer, tmout_unredir_callback); - if (ps->o.sw_opti) - ev_idle_init(&ps->draw_idle, delayed_draw_callback); - else - ev_idle_init(&ps->draw_idle, draw_callback); - - ev_init(&ps->fade_timer, fade_timer_callback); - ev_init(&ps->animation_timer, animation_timer_callback); - ev_init(&ps->delayed_draw_timer, delayed_draw_timer_callback); - - // Set up SIGUSR1 signal handler to reset program - ev_signal_init(&ps->usr1_signal, reset_enable, SIGUSR1); - ev_signal_init(&ps->int_signal, exit_enable, SIGINT); - ev_signal_start(ps->loop, &ps->usr1_signal); - ev_signal_start(ps->loop, &ps->int_signal); - - // xcb can read multiple events from the socket when a request with reply is - // made. - // - // Use an ev_prepare to make sure we cannot accidentally forget to handle them - // before we go to sleep. - // - // If we don't drain the queue before goes to sleep (i.e. blocking on socket - // input), we will be sleeping with events available in queue. Which might - // cause us to block indefinitely because arrival of new events could be - // dependent on processing of existing events (e.g. if we don't process damage - // event and do damage subtract, new damage event won't be generated). - // - // So we make use of a ev_prepare handle, which is called right before libev - // goes into sleep, to handle all the queued X events. - ev_prepare_init(&ps->event_check, handle_queued_x_events); - // Make sure nothing can cause xcb to read from the X socket after events are - // handled and before we going to sleep. - ev_set_priority(&ps->event_check, EV_MINPRI); - ev_prepare_start(ps->loop, &ps->event_check); - - // Initialize DBus. We need to do this early, because add_win might call dbus - // functions - if (ps->o.dbus) { -#ifdef CONFIG_DBUS - cdbus_init(ps, DisplayString(ps->dpy)); - if (!ps->dbus_data) { - ps->o.dbus = false; - } -#else - log_fatal("DBus support not compiled in!"); - exit(1); -#endif - } - - e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); - if (e) { - log_fatal_x_error(e, "Failed to grab X server"); - free(e); - goto err; - } - - ps->server_grabbed = true; - - // We are going to pull latest information from X server now, events sent by X - // earlier is irrelavant at this point. - // A better solution is probably grabbing the server from the very start. But I - // think there still could be race condition that mandates discarding the events. - x_discard_events(ps->c); - - xcb_query_tree_reply_t *query_tree_reply = - xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, ps->root), NULL); - - e = xcb_request_check(ps->c, xcb_ungrab_server(ps->c)); - if (e) { - log_fatal_x_error(e, "Failed to ungrab server"); - free(e); - goto err; - } - - ps->server_grabbed = false; - - if (query_tree_reply) { - xcb_window_t *children; - int nchildren; - - children = xcb_query_tree_children(query_tree_reply); - nchildren = xcb_query_tree_children_length(query_tree_reply); - - for (int i = 0; i < nchildren; i++) { - add_win_above(ps, children[i], i ? children[i - 1] : XCB_NONE); - } - free(query_tree_reply); - } - - log_debug("Initial stack:"); - list_foreach(struct win, w, &ps->window_stack, stack_neighbour) { - log_debug("%#010x", w->id); - } - - ps->pending_updates = true; - - write_pid(ps); - - if (fork && stderr_logger) { - // Remove the stderr logger if we will fork - log_remove_target_tls(stderr_logger); - } - return ps; -err: - free(ps); - return NULL; -} - -/** - * Destroy a session. - * - * Does not close the X connection or free the <code>session_t</code> - * structure, though. - * - * @param ps session to destroy - */ -static void session_destroy(session_t *ps) { - if (ps->redirected) { - unredirect(ps); - } - -#ifdef CONFIG_OPENGL - free(ps->argb_fbconfig); - ps->argb_fbconfig = NULL; -#endif - - file_watch_destroy(ps->loop, ps->file_watch_handle); - ps->file_watch_handle = NULL; - - // Stop listening to events on root window - xcb_change_window_attributes(ps->c, ps->root, XCB_CW_EVENT_MASK, - (const uint32_t[]){0}); - -#ifdef CONFIG_DBUS - // Kill DBus connection - if (ps->o.dbus) { - assert(ps->dbus_data); - cdbus_destroy(ps); - } -#endif - - // Free window linked list - - list_foreach_safe(struct win, w, &ps->window_stack, stack_neighbour) { - if (!w->destroyed) { - win_ev_stop(ps, w); - HASH_DEL(ps->windows, w); - } - - if (w->managed) { - auto mw = (struct managed_win *)w; - free_win_res(ps, mw); - } - free(w); - } - list_init_head(&ps->window_stack); - - // Free blacklists - free_wincondlst(&ps->o.shadow_blacklist); - free_wincondlst(&ps->o.shadow_clip_list); - free_wincondlst(&ps->o.fade_blacklist); - free_wincondlst(&ps->o.focus_blacklist); - free_wincondlst(&ps->o.invert_color_list); - free_wincondlst(&ps->o.blur_background_blacklist); - free_wincondlst(&ps->o.opacity_rules); - free_wincondlst(&ps->o.paint_blacklist); - free_wincondlst(&ps->o.unredir_if_possible_blacklist); - free_wincondlst(&ps->o.rounded_corners_blacklist); - - // Free tracked atom list - { - latom_t *next = NULL; - for (latom_t *this = ps->track_atom_lst; this; this = next) { - next = this->next; - free(this); - } - - ps->track_atom_lst = NULL; - } - - // Free ignore linked list - { - ignore_t *next = NULL; - for (ignore_t *ign = ps->ignore_head; ign; ign = next) { - next = ign->next; - - free(ign); - } - - // Reset head and tail - ps->ignore_head = NULL; - ps->ignore_tail = &ps->ignore_head; - } - - // Free tgt_{buffer,picture} and root_picture - if (ps->tgt_buffer.pict == ps->tgt_picture) - ps->tgt_buffer.pict = XCB_NONE; - - if (ps->tgt_picture == ps->root_picture) - ps->tgt_picture = XCB_NONE; - else - free_picture(ps->c, &ps->tgt_picture); - - free_picture(ps->c, &ps->root_picture); - free_paint(ps, &ps->tgt_buffer); - - pixman_region32_fini(&ps->screen_reg); - free(ps->expose_rects); - - free(ps->o.write_pid_path); - free(ps->o.logpath); - for (int i = 0; i < ps->o.blur_kernel_count; ++i) { - free(ps->o.blur_kerns[i]); - } - free(ps->o.blur_kerns); - free(ps->o.glx_fshader_win_str); - free_xinerama_info(ps); - -#ifdef CONFIG_VSYNC_DRM - // Close file opened for DRM VSync - if (ps->drm_fd >= 0) { - close(ps->drm_fd); - ps->drm_fd = -1; - } -#endif - - // Release overlay window - if (ps->overlay) { - xcb_composite_release_overlay_window(ps->c, ps->overlay); - ps->overlay = XCB_NONE; - } - - if (ps->sync_fence != XCB_NONE) { - xcb_sync_destroy_fence(ps->c, ps->sync_fence); - ps->sync_fence = XCB_NONE; - } - - // Free reg_win - if (ps->reg_win != XCB_NONE) { - xcb_destroy_window(ps->c, ps->reg_win); - ps->reg_win = XCB_NONE; - } - - if (ps->debug_window != XCB_NONE) { - xcb_destroy_window(ps->c, ps->debug_window); - ps->debug_window = XCB_NONE; - } - - if (ps->damaged_region != XCB_NONE) { - xcb_xfixes_destroy_region(ps->c, ps->damaged_region); - ps->damaged_region = XCB_NONE; - } - - if (ps->o.experimental_backends) { - // backend is deinitialized in unredirect() - assert(ps->backend_data == NULL); - } else { - deinit_render(ps); - } - -#if CONFIG_OPENGL - if (glx_has_context(ps)) { - // GLX context created, but not for rendering - glx_destroy(ps); - } -#endif - - // Flush all events - x_sync(ps->c); - ev_io_stop(ps->loop, &ps->xiow); - free_conv(ps->gaussian_map); - destroy_atoms(ps->atoms); - -#ifdef DEBUG_XRC - // Report about resource leakage - xrc_report_xid(); -#endif - - XSetErrorHandler(ps->previous_xerror_handler); - - // Stop libev event handlers - ev_timer_stop(ps->loop, &ps->unredir_timer); - ev_timer_stop(ps->loop, &ps->fade_timer); - ev_timer_stop(ps->loop, &ps->animation_timer); - ev_idle_stop(ps->loop, &ps->draw_idle); - ev_prepare_stop(ps->loop, &ps->event_check); - ev_signal_stop(ps->loop, &ps->usr1_signal); - ev_signal_stop(ps->loop, &ps->int_signal); -} - -/** - * Do the actual work. - * - * @param ps current session - */ -static void session_run(session_t *ps) { - if (ps->o.sw_opti) - ps->paint_tm_offset = get_time_timeval().tv_usec; - - // In benchmark mode, we want draw_idle handler to always be active - if (ps->o.benchmark) { - ev_idle_start(ps->loop, &ps->draw_idle); - } else { - // Let's draw our first frame! - queue_redraw(ps); - } - ev_run(ps->loop, 0); -} - -/** - * The function that everybody knows. - */ -int main(int argc, char **argv) { - // Set locale so window names with special characters are interpreted - // correctly - setlocale(LC_ALL, ""); - - // Initialize logging system for early logging - log_init_tls(); - - { - auto stderr_logger = stderr_logger_new(); - if (stderr_logger) { - log_add_target_tls(stderr_logger); - } - } - - int exit_code; - char *config_file = NULL; - bool all_xerrors = false, need_fork = false; - if (get_early_config(argc, argv, &config_file, &all_xerrors, &need_fork, &exit_code)) { - return exit_code; - } - - int pfds[2]; - if (need_fork) { - if (pipe2(pfds, O_CLOEXEC)) { - perror("pipe2"); - return 1; - } - auto pid = fork(); - if (pid < 0) { - perror("fork"); - return 1; - } - if (pid > 0) { - // We are the parent - close(pfds[1]); - // We wait for the child to tell us it has finished initialization - // by sending us something via the pipe. - int tmp; - if (read(pfds[0], &tmp, sizeof tmp) <= 0) { - // Failed to read, the child has most likely died - // We can probably waitpid() here. - return 1; - } else { - // We are done - return 0; - } - } - // We are the child - close(pfds[0]); - } - - // Main loop - bool quit = false; - int ret_code = 0; - char *pid_file = NULL; - - do { - Display *dpy = XOpenDisplay(NULL); - if (!dpy) { - log_fatal("Can't open display."); - ret_code = 1; - break; - } - XSetEventQueueOwner(dpy, XCBOwnsEventQueue); - - // Reinit logging system so we don't get leftovers from previous sessions - // or early logging. - log_deinit_tls(); - log_init_tls(); - - ps_g = session_init(argc, argv, dpy, config_file, all_xerrors, need_fork); - if (!ps_g) { - log_fatal("Failed to create new session."); - ret_code = 1; - break; - } - if (need_fork) { - // Finishing up daemonization - // Close files - if (fclose(stdout) || fclose(stderr) || fclose(stdin)) { - log_fatal("Failed to close standard input/output"); - ret_code = 1; - break; - } - // Make us the session and process group leader so we don't get - // killed when our parent die. - setsid(); - // Notify the parent that we are done. This might cause the parent - // to quit, so only do this after setsid() - int tmp = 1; - write(pfds[1], &tmp, sizeof tmp); - close(pfds[1]); - // We only do this once - need_fork = false; - } - session_run(ps_g); - quit = ps_g->quit; - if (quit && ps_g->o.write_pid_path) { - pid_file = strdup(ps_g->o.write_pid_path); - } - session_destroy(ps_g); - free(ps_g); - ps_g = NULL; - if (dpy) { - XCloseDisplay(dpy); - } - } while (!quit); - - free(config_file); - if (pid_file) { - log_trace("remove pid file %s", pid_file); - unlink(pid_file); - free(pid_file); - } - - log_deinit_tls(); - - return ret_code; -} diff --git a/src/picom.h b/src/picom.h deleted file mode 100644 index 25f7580..0000000 --- a/src/picom.h +++ /dev/null @@ -1,117 +0,0 @@ -// 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 update_refresh_rate(session_t *ps); - -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); -} diff --git a/src/picom.modulemap b/src/picom.modulemap deleted file mode 100644 index 787c4ff..0000000 --- a/src/picom.modulemap +++ /dev/null @@ -1,214 +0,0 @@ -// 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" -} diff --git a/src/region.h b/src/region.h deleted file mode 100644 index bda66e2..0000000 --- a/src/region.h +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018 Yuxuan Shui <[email protected]> -#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); -} diff --git a/src/render.c b/src/render.c deleted file mode 100644 index ac9b40e..0000000 --- a/src/render.c +++ /dev/null @@ -1,1500 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> - -#include <stdlib.h> -#include <string.h> -#include <xcb/composite.h> -#include <xcb/render.h> -#include <xcb/sync.h> -#include <xcb/xcb_image.h> -#include <xcb/xcb_renderutil.h> - -#include "common.h" -#include "options.h" - -#ifdef CONFIG_OPENGL -#include "backend/gl/glx.h" -#include "opengl.h" - -#ifndef GLX_BACK_BUFFER_AGE_EXT -#define GLX_BACK_BUFFER_AGE_EXT 0x20F4 -#endif - -#endif - -#include "compiler.h" -#include "config.h" -#include "kernel.h" -#include "log.h" -#include "region.h" -#include "types.h" -#include "utils.h" -#include "vsync.h" -#include "win.h" -#include "x.h" - -#include "backend/backend.h" -#include "backend/backend_common.h" -#include "render.h" - -#define XRFILTER_CONVOLUTION "convolution" -#define XRFILTER_GAUSSIAN "gaussian" -#define XRFILTER_BINOMIAL "binomial" - -/** - * Bind texture in paint_t if we are using GLX backend. - */ -static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int hei, - bool repeat, int depth, xcb_visualid_t visual, bool force) { -#ifdef CONFIG_OPENGL - // XXX This is a mess. But this will go away after the backend refactor. - if (!ppaint->pixmap) - return false; - - struct glx_fbconfig_info *fbcfg; - if (!visual) { - assert(depth == 32); - if (!ps->argb_fbconfig) { - ps->argb_fbconfig = - glx_find_fbconfig(ps->dpy, ps->scr, - (struct xvisual_info){.red_size = 8, - .green_size = 8, - .blue_size = 8, - .alpha_size = 8, - .visual_depth = 32}); - } - if (!ps->argb_fbconfig) { - log_error("Failed to find appropriate FBConfig for 32 bit depth"); - return false; - } - fbcfg = ps->argb_fbconfig; - } else { - auto m = x_get_visual_info(ps->c, visual); - if (m.visual_depth < 0) { - return false; - } - - if (depth && depth != m.visual_depth) { - log_error("Mismatching visual depth: %d != %d", depth, m.visual_depth); - return false; - } - - if (!ppaint->fbcfg) { - ppaint->fbcfg = glx_find_fbconfig(ps->dpy, ps->scr, m); - } - if (!ppaint->fbcfg) { - log_error("Failed to find appropriate FBConfig for X pixmap"); - return false; - } - fbcfg = ppaint->fbcfg; - } - - if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap)) - return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei, - repeat, fbcfg); -#else - (void)ps; - (void)ppaint; - (void)wid; - (void)hei; - (void)repeat; - (void)depth; - (void)visual; - (void)force; -#endif - return true; -} - -/** - * Check if current backend uses XRender for rendering. - */ -static inline bool bkend_use_xrender(session_t *ps) { - return BKEND_XRENDER == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend; -} - -int maximum_buffer_age(session_t *ps) { - if (bkend_use_glx(ps) && ps->o.use_damage) { - return CGLX_MAX_BUFFER_AGE; - } - return 1; -} - -static int get_buffer_age(session_t *ps) { -#ifdef CONFIG_OPENGL - if (bkend_use_glx(ps)) { - if (!glxext.has_GLX_EXT_buffer_age && ps->o.use_damage) { - log_warn("GLX_EXT_buffer_age not supported by your driver," - "`use-damage` has to be disabled"); - ps->o.use_damage = false; - } - if (ps->o.use_damage) { - unsigned int val; - glXQueryDrawable(ps->dpy, get_tgt_window(ps), - GLX_BACK_BUFFER_AGE_EXT, &val); - return (int)val ?: -1; - } - return -1; - } -#endif - return ps->o.use_damage ? 1 : -1; -} - -/** - * Reset filter on a <code>Picture</code>. - */ -static inline void xrfilter_reset(session_t *ps, xcb_render_picture_t p) { -#define FILTER "Nearest" - xcb_render_set_picture_filter(ps->c, p, strlen(FILTER), FILTER, 0, NULL); -#undef FILTER -} - -/// Set the input/output clip region of the target buffer (not the actual target!) -static inline void attr_nonnull(1, 2) set_tgt_clip(session_t *ps, region_t *reg) { - switch (ps->o.backend) { - case BKEND_XRENDER: - case BKEND_XR_GLX_HYBRID: - x_set_picture_clip_region(ps->c, ps->tgt_buffer.pict, 0, 0, reg); - break; -#ifdef CONFIG_OPENGL - case BKEND_GLX: glx_set_clip(ps, reg); break; -#endif - default: assert(false); - } -} - -/** - * Destroy a <code>Picture</code>. - */ -void free_picture(xcb_connection_t *c, xcb_render_picture_t *p) { - if (*p) { - xcb_render_free_picture(c, *p); - *p = XCB_NONE; - } -} - -/** - * Free paint_t. - */ -void free_paint(session_t *ps, paint_t *ppaint) { -#ifdef CONFIG_OPENGL - free_paint_glx(ps, ppaint); -#endif - free_picture(ps->c, &ppaint->pict); - if (ppaint->pixmap) - xcb_free_pixmap(ps->c, ppaint->pixmap); - ppaint->pixmap = XCB_NONE; -} - -uint32_t -make_circle(int cx, int cy, int radius, uint32_t max_ntraps, xcb_render_trapezoid_t traps[]) { - uint32_t n = 0, k = 0; - int y1, y2; - double w; - while (k < max_ntraps) { - y1 = (int)(-radius * cos(M_PI * k / max_ntraps)); - traps[n].top = (cy + y1) * 65536; - traps[n].left.p1.y = (cy + y1) * 65536; - traps[n].right.p1.y = (cy + y1) * 65536; - w = sqrt(radius * radius - y1 * y1) * 65536; - traps[n].left.p1.x = (int)((cx * 65536) - w); - traps[n].right.p1.x = (int)((cx * 65536) + w); - - do { - k++; - y2 = (int)(-radius * cos(M_PI * k / max_ntraps)); - } while (y1 == y2); - - traps[n].bottom = (cy + y2) * 65536; - traps[n].left.p2.y = (cy + y2) * 65536; - traps[n].right.p2.y = (cy + y2) * 65536; - w = sqrt(radius * radius - y2 * y2) * 65536; - traps[n].left.p2.x = (int)((cx * 65536) - w); - traps[n].right.p2.x = (int)((cx * 65536) + w); - n++; - } - return n; -} - -uint32_t make_rectangle(int x, int y, int wid, int hei, xcb_render_trapezoid_t traps[]) { - traps[0].top = y * 65536; - traps[0].left.p1.y = y * 65536; - traps[0].left.p1.x = x * 65536; - traps[0].left.p2.y = (y + hei) * 65536; - traps[0].left.p2.x = x * 65536; - traps[0].bottom = (y + hei) * 65536; - traps[0].right.p1.x = (x + wid) * 65536; - traps[0].right.p1.y = y * 65536; - traps[0].right.p2.x = (x + wid) * 65536; - traps[0].right.p2.y = (y + hei) * 65536; - return 1; -} - -uint32_t make_rounded_window_shape(xcb_render_trapezoid_t traps[], uint32_t max_ntraps, - int cr, int wid, int hei) { - uint32_t n = make_circle(cr, cr, cr, max_ntraps, traps); - n += make_circle(wid - cr, cr, cr, max_ntraps, traps + n); - n += make_circle(wid - cr, hei - cr, cr, max_ntraps, traps + n); - n += make_circle(cr, hei - cr, cr, max_ntraps, traps + n); - n += make_rectangle(0, cr, wid, hei - 2 * cr, traps + n); - n += make_rectangle(cr, 0, wid - 2 * cr, cr, traps + n); - n += make_rectangle(cr, hei - cr, wid - 2 * cr, cr, traps + n); - return n; -} - -void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, int fullwid, - int fullhei, double opacity, bool argb, bool neg, int cr, - xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint, - const glx_prog_main_t *pprogram, clip_t *clip) { - switch (ps->o.backend) { - case BKEND_XRENDER: - case BKEND_XR_GLX_HYBRID: { - auto alpha_step = (int)(opacity * MAX_ALPHA); - xcb_render_picture_t alpha_pict = ps->alpha_picts[alpha_step]; - if (alpha_step != 0) { - if (cr) { - xcb_render_picture_t p_tmp = x_create_picture_with_standard( - ps->c, ps->root, fullwid, fullhei, - XCB_PICT_STANDARD_ARGB_32, 0, 0); - xcb_render_color_t trans = { - .red = 0, .blue = 0, .green = 0, .alpha = 0}; - const xcb_rectangle_t rect = { - .x = 0, - .y = 0, - .width = to_u16_checked(fullwid), - .height = to_u16_checked(fullhei)}; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, - p_tmp, trans, 1, &rect); - - uint32_t max_ntraps = to_u32_checked(cr); - xcb_render_trapezoid_t traps[4 * max_ntraps + 3]; - - uint32_t n = make_rounded_window_shape( - traps, max_ntraps, cr, fullwid, fullhei); - - xcb_render_trapezoids( - ps->c, XCB_RENDER_PICT_OP_OVER, alpha_pict, p_tmp, - x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), - 0, 0, n, traps); - - xcb_render_composite( - ps->c, XCB_RENDER_PICT_OP_OVER, pict, p_tmp, - ps->tgt_buffer.pict, to_i16_checked(x), - to_i16_checked(y), to_i16_checked(x), to_i16_checked(y), - to_i16_checked(dx), to_i16_checked(dy), - to_u16_checked(wid), to_u16_checked(hei)); - - xcb_render_free_picture(ps->c, p_tmp); - - } else { - xcb_render_picture_t p_tmp = alpha_pict; - if (clip) { - p_tmp = x_create_picture_with_standard( - ps->c, ps->root, wid, hei, - XCB_PICT_STANDARD_ARGB_32, 0, 0); - - xcb_render_color_t black = { - .red = 255, .blue = 255, .green = 255, .alpha = 255}; - const xcb_rectangle_t rect = { - .x = 0, - .y = 0, - .width = to_u16_checked(wid), - .height = to_u16_checked(hei)}; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, - p_tmp, black, 1, &rect); - if (alpha_pict) { - xcb_render_composite( - ps->c, XCB_RENDER_PICT_OP_SRC, - alpha_pict, XCB_NONE, p_tmp, 0, 0, 0, - 0, 0, 0, to_u16_checked(wid), - to_u16_checked(hei)); - } - xcb_render_composite( - ps->c, XCB_RENDER_PICT_OP_OUT_REVERSE, - clip->pict, XCB_NONE, p_tmp, 0, 0, 0, 0, - to_i16_checked(clip->x), to_i16_checked(clip->y), - to_u16_checked(wid), to_u16_checked(hei)); - } - uint8_t op = ((!argb && !alpha_pict && !clip) - ? XCB_RENDER_PICT_OP_SRC - : XCB_RENDER_PICT_OP_OVER); - - xcb_render_composite( - ps->c, op, pict, p_tmp, ps->tgt_buffer.pict, - to_i16_checked(x), to_i16_checked(y), 0, 0, - to_i16_checked(dx), to_i16_checked(dy), - to_u16_checked(wid), to_u16_checked(hei)); - if (clip) { - xcb_render_free_picture(ps->c, p_tmp); - } - } - } - break; - } -#ifdef CONFIG_OPENGL - case BKEND_GLX: - glx_render(ps, ptex, x, y, dx, dy, wid, hei, ps->psglx->z, opacity, argb, - neg, reg_paint, pprogram); - ps->psglx->z += 1; - break; -#endif - default: assert(0); - } -#ifndef CONFIG_OPENGL - (void)neg; - (void)ptex; - (void)reg_paint; - (void)pprogram; -#endif -} - -static inline void -paint_region(session_t *ps, const struct managed_win *w, int x, int y, int wid, int hei, - double opacity, const region_t *reg_paint, xcb_render_picture_t pict) { - const int dx = (w ? w->g.x : 0) + x; - const int dy = (w ? w->g.y : 0) + y; - const int fullwid = w ? w->widthb : 0; - const int fullhei = w ? w->heightb : 0; - const bool argb = (w && (win_has_alpha(w) || ps->o.force_win_blend)); - const bool neg = (w && w->invert_color); - - render(ps, x, y, dx, dy, wid, hei, fullwid, fullhei, opacity, argb, neg, - w ? w->corner_radius : 0, pict, - (w ? w->paint.ptex : ps->root_tile_paint.ptex), reg_paint, -#ifdef CONFIG_OPENGL - w ? &ps->glx_prog_win : NULL -#else - NULL -#endif - , - XCB_NONE); -} - -/** - * Check whether a paint_t contains enough data. - */ -static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) { - // Don't check for presence of Pixmap here, because older X Composite doesn't - // provide it - if (!ppaint) - return false; - - if (bkend_use_xrender(ps) && !ppaint->pict) - return false; - -#ifdef CONFIG_OPENGL - if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, XCB_NONE)) - return false; -#endif - - return true; -} - -/** - * Paint a window itself and dim it if asked. - */ -void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) { - // Fetch Pixmap - if (!w->paint.pixmap) { - w->paint.pixmap = x_new_id(ps->c); - set_ignore_cookie(ps, xcb_composite_name_window_pixmap(ps->c, w->base.id, - w->paint.pixmap)); - } - - xcb_drawable_t draw = w->paint.pixmap; - if (!draw) { - log_error("Failed to get pixmap from window %#010x (%s), window won't be " - "visible", - w->base.id, w->name); - return; - } - - // XRender: Build picture - if (bkend_use_xrender(ps) && !w->paint.pict) { - xcb_render_create_picture_value_list_t pa = { - .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS, - }; - - w->paint.pict = x_create_picture_with_pictfmt_and_pixmap( - ps->c, w->pictfmt, draw, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); - } - - // GLX: Build texture - // Let glx_bind_pixmap() determine pixmap size, because if the user - // is resizing windows, the width and height we get may not be up-to-date, - // causing the jittering issue M4he reported in #7. - if (!paint_bind_tex(ps, &w->paint, 0, 0, false, 0, w->a.visual, - (!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) { - log_error("Failed to bind texture for window %#010x.", w->base.id); - } - w->pixmap_damaged = false; - - if (!paint_isvalid(ps, &w->paint)) { - log_error("Window %#010x is missing painting data.", w->base.id); - return; - } - - const int x = w->g.x; - const int y = w->g.y; - const uint16_t wid = to_u16_checked(w->widthb); - const uint16_t hei = to_u16_checked(w->heightb); - - xcb_render_picture_t pict = w->paint.pict; - - // Invert window color, if required - if (bkend_use_xrender(ps) && w->invert_color) { - xcb_render_picture_t newpict = x_create_picture_with_pictfmt( - ps->c, ps->root, wid, hei, w->pictfmt, 0, NULL); - if (newpict) { - // Apply clipping region to save some CPU - if (reg_paint) { - region_t reg; - pixman_region32_init(®); - pixman_region32_copy(®, (region_t *)reg_paint); - pixman_region32_translate(®, -x, -y); - // FIXME XFixesSetPictureClipRegion(ps->dpy, newpict, 0, - // 0, reg); - pixman_region32_fini(®); - } - - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, pict, XCB_NONE, - newpict, 0, 0, 0, 0, 0, 0, wid, hei); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_DIFFERENCE, - ps->white_picture, XCB_NONE, newpict, 0, 0, - 0, 0, 0, 0, wid, hei); - // We use an extra PictOpInReverse operation to get correct - // pixel alpha. There could be a better solution. - if (win_has_alpha(w)) - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_IN_REVERSE, - pict, XCB_NONE, newpict, 0, 0, 0, 0, - 0, 0, wid, hei); - pict = newpict; - } - } - - if (w->frame_opacity == 1) { - paint_region(ps, w, 0, 0, wid, hei, w->opacity, reg_paint, pict); - } else { - // Painting parameters - const margin_t extents = win_calc_frame_extents(w); - const auto t = extents.top; - const auto l = extents.left; - const auto b = extents.bottom; - const auto r = extents.right; - -#define COMP_BDR(cx, cy, cwid, chei) \ - paint_region(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity * w->opacity, \ - reg_paint, pict) - - // Sanitize the margins, in case some broken WM makes - // top_width + bottom_width > height in some cases. - - do { - // top - int body_height = hei; - // ctop = checked top - // Make sure top margin is smaller than height - int ctop = min2(body_height, t); - if (ctop > 0) - COMP_BDR(0, 0, wid, ctop); - - body_height -= ctop; - if (body_height <= 0) - break; - - // bottom - // cbot = checked bottom - // Make sure bottom margin is not too large - int cbot = min2(body_height, b); - if (cbot > 0) - COMP_BDR(0, hei - cbot, wid, cbot); - - // Height of window exclude the margin - body_height -= cbot; - if (body_height <= 0) - break; - - // left - int body_width = wid; - int cleft = min2(body_width, l); - if (cleft > 0) - COMP_BDR(0, ctop, cleft, body_height); - - body_width -= cleft; - if (body_width <= 0) - break; - - // right - int cright = min2(body_width, r); - if (cright > 0) - COMP_BDR(wid - cright, ctop, cright, body_height); - - body_width -= cright; - if (body_width <= 0) - break; - - // body - paint_region(ps, w, cleft, ctop, body_width, body_height, - w->opacity, reg_paint, pict); - } while (0); - } - -#undef COMP_BDR - - if (pict != w->paint.pict) - free_picture(ps->c, &pict); - - // Dimming the window if needed - if (w->dim) { - double dim_opacity = ps->o.inactive_dim; - if (!ps->o.inactive_dim_fixed) - dim_opacity *= w->opacity; - - switch (ps->o.backend) { - case BKEND_XRENDER: - case BKEND_XR_GLX_HYBRID: { - auto cval = (uint16_t)(0xffff * dim_opacity); - - // Premultiply color - xcb_render_color_t color = { - .red = 0, - .green = 0, - .blue = 0, - .alpha = cval, - }; - - xcb_rectangle_t rect = { - .x = to_i16_checked(x), - .y = to_i16_checked(y), - .width = wid, - .height = hei, - }; - - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_OVER, - ps->tgt_buffer.pict, color, 1, &rect); - } break; -#ifdef CONFIG_OPENGL - case BKEND_GLX: - glx_dim_dst(ps, x, y, wid, hei, (int)(ps->psglx->z - 0.7), - (float)dim_opacity, reg_paint); - break; -#endif - default: assert(false); - } - } -} - -extern const char *background_props_str[]; - -static bool get_root_tile(session_t *ps) { - /* - if (ps->o.paint_on_overlay) { - return ps->root_picture; - } */ - - assert(!ps->root_tile_paint.pixmap); - ps->root_tile_fill = false; - - bool fill = false; - xcb_pixmap_t pixmap = x_get_root_back_pixmap(ps->c, ps->root, ps->atoms); - - // Make sure the pixmap we got is valid - if (pixmap && !x_validate_pixmap(ps->c, pixmap)) - pixmap = XCB_NONE; - - // Create a pixmap if there isn't any - if (!pixmap) { - pixmap = x_create_pixmap(ps->c, (uint8_t)ps->depth, ps->root, 1, 1); - if (pixmap == XCB_NONE) { - log_error("Failed to create pixmaps for root tile."); - return false; - } - fill = true; - } - - // Create Picture - xcb_render_create_picture_value_list_t pa = { - .repeat = true, - }; - ps->root_tile_paint.pict = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, pixmap, XCB_RENDER_CP_REPEAT, &pa); - - // Fill pixmap if needed - if (fill) { - xcb_render_color_t col; - xcb_rectangle_t rect; - - col.red = col.green = col.blue = 0x8080; - col.alpha = 0xffff; - - rect.x = rect.y = 0; - rect.width = rect.height = 1; - - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, - ps->root_tile_paint.pict, col, 1, &rect); - } - - ps->root_tile_fill = fill; - ps->root_tile_paint.pixmap = pixmap; -#ifdef CONFIG_OPENGL - if (BKEND_GLX == ps->o.backend) - return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0, ps->vis, false); -#endif - - return true; -} - -/** - * Paint root window content. - */ -static void paint_root(session_t *ps, const region_t *reg_paint) { - // If there is no root tile pixmap, try getting one. - // If that fails, give up. - if (!ps->root_tile_paint.pixmap && !get_root_tile(ps)) - return; - - paint_region(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint, - ps->root_tile_paint.pict); -} - -/** - * Generate shadow <code>Picture</code> for a window. - */ -static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacity) { - const int width = w->widthb; - const int height = w->heightb; - // log_trace("(): building shadow for %s %d %d", w->name, width, height); - - 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(ps->c, ps->gaussian_map, opacity, width, height); - if (!shadow_image) { - log_error("failed to make shadow"); - return XCB_NONE; - } - - shadow_pixmap = - x_create_pixmap(ps->c, 8, ps->root, shadow_image->width, shadow_image->height); - shadow_pixmap_argb = - x_create_pixmap(ps->c, 32, ps->root, 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( - ps->c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL); - shadow_picture_argb = x_create_picture_with_standard_and_pixmap( - ps->c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL); - if (!shadow_picture || !shadow_picture_argb) - goto shadow_picture_err; - - gc = x_new_id(ps->c); - xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL); - - xcb_image_put(ps->c, shadow_pixmap, gc, shadow_image, 0, 0, 0); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, ps->cshadow_picture, - shadow_picture, shadow_picture_argb, 0, 0, 0, 0, 0, 0, - shadow_image->width, shadow_image->height); - - assert(!w->shadow_paint.pixmap); - w->shadow_paint.pixmap = shadow_pixmap_argb; - assert(!w->shadow_paint.pict); - w->shadow_paint.pict = shadow_picture_argb; - - xcb_free_gc(ps->c, gc); - xcb_image_destroy(shadow_image); - xcb_free_pixmap(ps->c, shadow_pixmap); - xcb_render_free_picture(ps->c, shadow_picture); - - return true; - -shadow_picture_err: - if (shadow_image) - xcb_image_destroy(shadow_image); - if (shadow_pixmap) - xcb_free_pixmap(ps->c, shadow_pixmap); - if (shadow_pixmap_argb) - xcb_free_pixmap(ps->c, shadow_pixmap_argb); - if (shadow_picture) - xcb_render_free_picture(ps->c, shadow_picture); - if (shadow_picture_argb) - xcb_render_free_picture(ps->c, shadow_picture_argb); - if (gc) - xcb_free_gc(ps->c, gc); - - return false; -} - -/** - * Paint the shadow of a window. - */ -static inline void -win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { - // Bind shadow pixmap to GLX texture if needed - paint_bind_tex(ps, &w->shadow_paint, 0, 0, false, 32, 0, false); - - if (!paint_isvalid(ps, &w->shadow_paint)) { - log_error("Window %#010x is missing shadow data.", w->base.id); - return; - } - - xcb_render_picture_t td = XCB_NONE; - bool should_clip = - (w->corner_radius > 0) && (!ps->o.wintype_option[w->window_type].full_shadow); - if (should_clip) { - if (ps->o.backend == BKEND_XRENDER || ps->o.backend == BKEND_XR_GLX_HYBRID) { - uint32_t max_ntraps = to_u32_checked(w->corner_radius); - xcb_render_trapezoid_t traps[4 * max_ntraps + 3]; - uint32_t n = make_rounded_window_shape( - traps, max_ntraps, w->corner_radius, w->widthb, w->heightb); - - td = x_create_picture_with_standard( - ps->c, ps->root, w->widthb, w->heightb, - XCB_PICT_STANDARD_ARGB_32, 0, 0); - xcb_render_color_t trans = { - .red = 0, .blue = 0, .green = 0, .alpha = 0}; - const xcb_rectangle_t rect = {.x = 0, - .y = 0, - .width = to_u16_checked(w->widthb), - .height = to_u16_checked(w->heightb)}; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, - trans, 1, &rect); - - auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0); - xcb_render_trapezoids( - ps->c, XCB_RENDER_PICT_OP_OVER, solid, td, - x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0, - 0, n, traps); - xcb_render_free_picture(ps->c, solid); - } else { - // Not implemented - } - } - - clip_t clip = { - .pict = td, - .x = -(w->shadow_dx), - .y = -(w->shadow_dy), - }; - render(ps, 0, 0, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, w->shadow_width, - w->shadow_height, w->widthb, w->heightb, w->shadow_opacity, true, false, 0, - w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, NULL, - should_clip ? &clip : NULL); - if (td) { - xcb_render_free_picture(ps->c, td); - } -} - -/** - * @brief Blur an area on a buffer. - * - * @param ps current session - * @param tgt_buffer a buffer as both source and destination - * @param x x pos - * @param y y pos - * @param wid width - * @param hei height - * @param blur_kerns blur kernels, ending with a NULL, guaranteed to have at - * least one kernel - * @param reg_clip a clipping region to be applied on intermediate buffers - * - * @return true if successful, false otherwise - */ -static bool -xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y, - uint16_t wid, uint16_t hei, struct x_convolution_kernel **blur_kerns, - int nkernels, const region_t *reg_clip, xcb_render_picture_t rounded) { - assert(blur_kerns); - assert(blur_kerns[0]); - - // Directly copying from tgt_buffer to it does not work, so we create a - // Picture in the middle. - xcb_render_picture_t tmp_picture = - x_create_picture_with_visual(ps->c, ps->root, wid, hei, ps->vis, 0, NULL); - - if (!tmp_picture) { - log_error("Failed to build intermediate Picture."); - return false; - } - - if (reg_clip && tmp_picture) - x_set_picture_clip_region(ps->c, tmp_picture, 0, 0, reg_clip); - - xcb_render_picture_t src_pict = tgt_buffer, dst_pict = tmp_picture; - for (int i = 0; i < nkernels; ++i) { - xcb_render_fixed_t *convolution_blur = blur_kerns[i]->kernel; - // `x / 65536.0` converts from X fixed point to double - int kwid = (int)((double)convolution_blur[0] / 65536.0), - khei = (int)((double)convolution_blur[1] / 65536.0); - bool rd_from_tgt = (tgt_buffer == src_pict); - - // 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( - ps->c, src_pict, strlen(XRFILTER_CONVOLUTION), XRFILTER_CONVOLUTION, - (uint32_t)(kwid * khei + 2), convolution_blur); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, - dst_pict, (rd_from_tgt ? x : 0), - (rd_from_tgt ? y : 0), 0, 0, (rd_from_tgt ? 0 : x), - (rd_from_tgt ? 0 : y), wid, hei); - xrfilter_reset(ps, src_pict); - - { - xcb_render_picture_t tmp = src_pict; - src_pict = dst_pict; - dst_pict = tmp; - } - } - - if (src_pict != tgt_buffer) - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, rounded, - tgt_buffer, 0, 0, 0, 0, x, y, wid, hei); - - free_picture(ps->c, &tmp_picture); - - return true; -} - -/** - * Blur the background of a window. - */ -static inline void -win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t tgt_buffer, - const region_t *reg_paint) { - const int16_t x = w->g.x; - const int16_t y = w->g.y; - const auto wid = to_u16_checked(w->widthb); - const auto hei = to_u16_checked(w->heightb); - const int cr = w ? w->corner_radius : 0; - - double factor_center = 1.0; - // Adjust blur strength according to window opacity, to make it appear - // better during fading - if (!ps->o.blur_background_fixed) { - double pct = 1.0 - w->opacity * (1.0 - 1.0 / 9.0); - factor_center = pct * 8.0 / (1.1 - pct); - } - - switch (ps->o.backend) { - case BKEND_XRENDER: - case BKEND_XR_GLX_HYBRID: { - // Normalize blur kernels - for (int i = 0; i < ps->o.blur_kernel_count; i++) { - // Note: `x * 65536` converts double `x` to a X fixed point - // representation. `x / 65536` is the other way. - auto kern_src = ps->o.blur_kerns[i]; - auto kern_dst = ps->blur_kerns_cache[i]; - - assert(!kern_dst || (kern_src->w == kern_dst->kernel[0] / 65536 && - kern_src->h == kern_dst->kernel[1] / 65536)); - - // Skip for fixed factor_center if the cache exists already - if (ps->o.blur_background_fixed && kern_dst) { - continue; - } - - x_create_convolution_kernel(kern_src, factor_center, - &ps->blur_kerns_cache[i]); - } - - xcb_render_picture_t td = XCB_NONE; - if (cr) { - uint32_t max_ntraps = to_u32_checked(cr); - xcb_render_trapezoid_t traps[4 * max_ntraps + 3]; - uint32_t n = - make_rounded_window_shape(traps, max_ntraps, cr, wid, hei); - - td = x_create_picture_with_standard( - ps->c, ps->root, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0); - xcb_render_color_t trans = { - .red = 0, .blue = 0, .green = 0, .alpha = 0}; - const xcb_rectangle_t rect = {.x = 0, - .y = 0, - .width = to_u16_checked(wid), - .height = to_u16_checked(hei)}; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, - trans, 1, &rect); - - auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0); - - xcb_render_trapezoids( - ps->c, XCB_RENDER_PICT_OP_OVER, solid, td, - x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0, - 0, n, traps); - xcb_render_free_picture(ps->c, solid); - } - - // Minimize the region we try to blur, if the window itself is not - // opaque, only the frame is. - region_t reg_blur = win_get_bounding_shape_global_by_val(w); - if (w->mode == WMODE_FRAME_TRANS && !ps->o.force_win_blend) { - region_t reg_noframe; - pixman_region32_init(®_noframe); - win_get_region_noframe_local(w, ®_noframe); - pixman_region32_translate(®_noframe, w->g.x, w->g.y); - pixman_region32_subtract(®_blur, ®_blur, ®_noframe); - pixman_region32_fini(®_noframe); - } - - // Translate global coordinates to local ones - pixman_region32_translate(®_blur, -x, -y); - xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, - ps->o.blur_kernel_count, ®_blur, td); - if (td) { - xcb_render_free_picture(ps->c, td); - } - pixman_region32_clear(®_blur); - } break; -#ifdef CONFIG_OPENGL - case BKEND_GLX: - // TODO(compton) Handle frame opacity - glx_blur_dst(ps, x, y, wid, hei, (float)ps->psglx->z - 0.5f, - (float)factor_center, reg_paint, &w->glx_blur_cache); - break; -#endif - default: assert(0); - } -#ifndef CONFIG_OPENGL - (void)reg_paint; -#endif -} - -/// paint all windows -/// region = ?? -/// region_real = the damage region -void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { - if (ps->o.xrender_sync_fence || (ps->drivers & DRIVER_NVIDIA)) { - 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; - } - } - - region_t region; - pixman_region32_init(®ion); - int buffer_age = get_buffer_age(ps); - if (buffer_age == -1 || buffer_age > ps->ndamage || ignore_damage) { - pixman_region32_copy(®ion, &ps->screen_reg); - } else { - for (int i = 0; i < get_buffer_age(ps); i++) { - auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage; - pixman_region32_union(®ion, ®ion, &ps->damage_ring[curr]); - } - } - - if (!pixman_region32_not_empty(®ion)) { - return; - } - -#ifdef DEBUG_REPAINT - static struct timespec last_paint = {0}; -#endif - - if (ps->o.resize_damage > 0) { - resize_region_in_place(®ion, ps->o.resize_damage, ps->o.resize_damage); - } - - // Remove the damaged area out of screen - pixman_region32_intersect(®ion, ®ion, &ps->screen_reg); - - if (!paint_isvalid(ps, &ps->tgt_buffer)) { - if (!ps->tgt_buffer.pixmap) { - free_paint(ps, &ps->tgt_buffer); - ps->tgt_buffer.pixmap = - x_create_pixmap(ps->c, (uint8_t)ps->depth, ps->root, - ps->root_width, ps->root_height); - if (ps->tgt_buffer.pixmap == XCB_NONE) { - log_fatal("Failed to allocate a screen-sized pixmap for" - "painting"); - exit(1); - } - } - - if (BKEND_GLX != ps->o.backend) - ps->tgt_buffer.pict = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, ps->tgt_buffer.pixmap, 0, 0); - } - - if (BKEND_XRENDER == ps->o.backend) { - x_set_picture_clip_region(ps->c, ps->tgt_picture, 0, 0, ®ion); - } - -#ifdef CONFIG_OPENGL - if (bkend_use_glx(ps)) { - ps->psglx->z = 0.0; - } -#endif - - region_t reg_tmp, *reg_paint; - pixman_region32_init(®_tmp); - if (t) { - // Calculate the region upon which the root window is to be - // painted based on the ignore region of the lowest window, if - // available - pixman_region32_subtract(®_tmp, ®ion, t->reg_ignore); - reg_paint = ®_tmp; - } else { - reg_paint = ®ion; - } - - // Region on screen we don't want any shadows on - region_t reg_shadow_clip; - pixman_region32_init(®_shadow_clip); - - set_tgt_clip(ps, reg_paint); - paint_root(ps, reg_paint); - - // Windows are sorted from bottom to top - // Each window has a reg_ignore, which is the region obscured by all the - // windows on top of that window. This is used to reduce the number of - // pixels painted. - // - // Whether this is beneficial is to be determined XXX - for (auto w = t; w; w = w->prev_trans) { - region_t bshape_no_corners = - win_get_bounding_shape_global_without_corners_by_val(w); - region_t bshape_corners = win_get_bounding_shape_global_by_val(w); - // Painting shadow - if (w->shadow) { - // Lazy shadow building - if (!w->shadow_paint.pixmap) - if (!win_build_shadow(ps, w, 1)) - log_error("build shadow failed"); - - // Shadow doesn't need to be painted underneath the body - // of the windows above. Because no one can see it - pixman_region32_subtract(®_tmp, ®ion, w->reg_ignore); - - // Mask out the region we don't want shadow on - if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) - pixman_region32_subtract(®_tmp, ®_tmp, - &ps->shadow_exclude_reg); - if (pixman_region32_not_empty(®_shadow_clip)) { - pixman_region32_subtract(®_tmp, ®_tmp, ®_shadow_clip); - } - - // Might be worth while to crop the region to shadow - // border - assert(w->shadow_width >= 0 && w->shadow_height >= 0); - pixman_region32_intersect_rect( - ®_tmp, ®_tmp, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, - (uint)w->shadow_width, (uint)w->shadow_height); - - // Mask out the body of the window from the shadow if - // needed Doing it here instead of in make_shadow() for - // saving GPU power and handling shaped windows (XXX - // unconfirmed) - if (!ps->o.wintype_option[w->window_type].full_shadow) - pixman_region32_subtract(®_tmp, ®_tmp, &bshape_no_corners); - - 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( - ®_tmp, ®_tmp, - &ps->xinerama_scr_regs[w->xinerama_scr]); - - // Detect if the region is empty before painting - if (pixman_region32_not_empty(®_tmp)) { - set_tgt_clip(ps, ®_tmp); - win_paint_shadow(ps, w, ®_tmp); - } - } - - // Only clip shadows above visible windows - if (w->opacity * MAX_ALPHA >= 1) { - if (w->clip_shadow_above) { - // Add window bounds to shadow-clip region - pixman_region32_union(®_shadow_clip, ®_shadow_clip, - &bshape_corners); - } else { - // Remove overlapping window bounds from shadow-clip - // region - pixman_region32_subtract( - ®_shadow_clip, ®_shadow_clip, &bshape_corners); - } - } - - // Calculate the paint region based on the reg_ignore of the current - // window and its bounding region. - // Remember, reg_ignore is the union of all windows above the current - // window. - pixman_region32_subtract(®_tmp, ®ion, w->reg_ignore); - pixman_region32_intersect(®_tmp, ®_tmp, &bshape_corners); - pixman_region32_fini(&bshape_corners); - pixman_region32_fini(&bshape_no_corners); - - if (pixman_region32_not_empty(®_tmp)) { - set_tgt_clip(ps, ®_tmp); - -#ifdef CONFIG_OPENGL - // If rounded corners backup the region first - if (w->corner_radius > 0 && ps->o.backend == BKEND_GLX) { - const int16_t x = w->g.x; - const int16_t y = w->g.y; - const auto wid = to_u16_checked(w->widthb); - const auto hei = to_u16_checked(w->heightb); - glx_bind_texture(ps, &w->glx_texture_bg, x, y, wid, hei); - } -#endif - - // Blur window background - if (w->blur_background && - (w->mode == WMODE_TRANS || - (ps->o.blur_background_frame && w->mode == WMODE_FRAME_TRANS) || - ps->o.force_win_blend)) { - win_blur_background(ps, w, ps->tgt_buffer.pict, ®_tmp); - } - - // Painting the window - paint_one(ps, w, ®_tmp); - -#ifdef CONFIG_OPENGL - // Rounded corners for XRender is implemented inside render() - // Round window corners - if (w->corner_radius > 0 && ps->o.backend == BKEND_GLX) { - const auto wid = to_u16_checked(w->widthb); - const auto hei = to_u16_checked(w->heightb); - glx_round_corners_dst(ps, w, w->glx_texture_bg, w->g.x, - w->g.y, wid, hei, - (float)ps->psglx->z - 0.5F, - (float)w->corner_radius, ®_tmp); - } -#endif - } - } - - // Free up all temporary regions - pixman_region32_fini(®_tmp); - pixman_region32_fini(®_shadow_clip); - - // 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); - - // Do this as early as possible - set_tgt_clip(ps, &ps->screen_reg); - - if (ps->o.vsync) { - // Make sure all previous requests are processed to achieve best - // effect - x_sync(ps->c); -#ifdef CONFIG_OPENGL - if (glx_has_context(ps)) { - if (ps->o.vsync_use_glfinish) - glFinish(); - else - glFlush(); - glXWaitX(); - } -#endif - } - - if (ps->vsync_wait) { - ps->vsync_wait(ps); - } - - auto rwidth = to_u16_checked(ps->root_width); - auto rheight = to_u16_checked(ps->root_height); - switch (ps->o.backend) { - case BKEND_XRENDER: - if (ps->o.monitor_repaint) { - // Copy the screen content to a new picture, and highlight the - // paint region. This is not very efficient, but since it's for - // debug only, we don't really care - - // First we create a new picture, and copy content from the buffer - // to it - auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis); - xcb_render_picture_t new_pict = x_create_picture_with_pictfmt( - ps->c, ps->root, rwidth, rheight, pictfmt, 0, NULL); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, - ps->tgt_buffer.pict, XCB_NONE, new_pict, 0, - 0, 0, 0, 0, 0, rwidth, rheight); - - // Next, we set the region of paint and highlight it - x_set_picture_clip_region(ps->c, new_pict, 0, 0, ®ion); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, ps->white_picture, - ps->alpha_picts[MAX_ALPHA / 2], new_pict, 0, - 0, 0, 0, 0, 0, rwidth, rheight); - - // Finally, clear clip regions of new_pict and the screen, and put - // the whole thing on screen - x_set_picture_clip_region(ps->c, new_pict, 0, 0, &ps->screen_reg); - x_set_picture_clip_region(ps->c, ps->tgt_picture, 0, 0, &ps->screen_reg); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, new_pict, - XCB_NONE, ps->tgt_picture, 0, 0, 0, 0, 0, 0, - rwidth, rheight); - xcb_render_free_picture(ps->c, new_pict); - } else - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, - ps->tgt_buffer.pict, XCB_NONE, ps->tgt_picture, - 0, 0, 0, 0, 0, 0, rwidth, rheight); - break; -#ifdef CONFIG_OPENGL - case BKEND_XR_GLX_HYBRID: - x_sync(ps->c); - if (ps->o.vsync_use_glfinish) - glFinish(); - else - glFlush(); - glXWaitX(); - assert(ps->tgt_buffer.pixmap); - paint_bind_tex(ps, &ps->tgt_buffer, ps->root_width, ps->root_height, - false, ps->depth, ps->vis, !ps->o.glx_no_rebind_pixmap); - if (ps->o.vsync_use_glfinish) - glFinish(); - else - glFlush(); - glXWaitX(); - glx_render(ps, ps->tgt_buffer.ptex, 0, 0, 0, 0, ps->root_width, - ps->root_height, 0, 1.0, false, false, ®ion, NULL); - fallthrough(); - case BKEND_GLX: glXSwapBuffers(ps->dpy, get_tgt_window(ps)); break; -#endif - default: assert(0); - } - - x_sync(ps->c); - -#ifdef CONFIG_OPENGL - if (glx_has_context(ps)) { - glFlush(); - glXWaitX(); - } -#endif - -#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 - - // Free the paint region - pixman_region32_fini(®ion); -} - -/** - * Query needed X Render / OpenGL filters to check for their existence. - */ -static bool xr_init_blur(session_t *ps) { - // Query filters - xcb_render_query_filters_reply_t *pf = xcb_render_query_filters_reply( - ps->c, xcb_render_query_filters(ps->c, get_tgt_window(ps)), NULL); - if (pf) { - xcb_str_iterator_t iter = xcb_render_query_filters_filters_iterator(pf); - for (; iter.rem; xcb_str_next(&iter)) { - int len = xcb_str_name_length(iter.data); - char *name = xcb_str_name(iter.data); - // Check for the convolution filter - if (strlen(XRFILTER_CONVOLUTION) == len && - !memcmp(XRFILTER_CONVOLUTION, name, strlen(XRFILTER_CONVOLUTION))) - ps->xrfilter_convolution_exists = true; - } - free(pf); - } - - // Turn features off if any required filter is not present - if (!ps->xrfilter_convolution_exists) { - log_error("Xrender convolution filter " - "unsupported by your X server. " - "Background blur is not possible."); - return false; - } - - return true; -} - -/** - * Pregenerate alpha pictures. - */ -static bool init_alpha_picts(session_t *ps) { - ps->alpha_picts = ccalloc(MAX_ALPHA + 1, xcb_render_picture_t); - - for (int i = 0; i <= MAX_ALPHA; ++i) { - double o = (double)i / MAX_ALPHA; - ps->alpha_picts[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0); - if (ps->alpha_picts[i] == XCB_NONE) - return false; - } - return true; -} - -bool init_render(session_t *ps) { - if (ps->o.backend == BKEND_DUMMY) { - return false; - } - - // Initialize OpenGL as early as possible -#ifdef CONFIG_OPENGL - glxext_init(ps->dpy, ps->scr); -#endif - if (bkend_use_glx(ps)) { -#ifdef CONFIG_OPENGL - if (!glx_init(ps, true)) - return false; -#else - log_error("GLX backend support not compiled in."); - return false; -#endif - } - - // Initialize VSync - if (!vsync_init(ps)) { - return false; - } - - // Initialize window GL shader - if (BKEND_GLX == ps->o.backend && ps->o.glx_fshader_win_str) { -#ifdef CONFIG_OPENGL - if (!glx_load_prog_main(NULL, ps->o.glx_fshader_win_str, &ps->glx_prog_win)) - return false; -#else - log_error("GLSL supported not compiled in, can't load " - "shader."); - return false; -#endif - } - - if (!init_alpha_picts(ps)) { - log_error("Failed to init alpha pictures."); - return false; - } - - // Blur filter - if (ps->o.blur_method && ps->o.blur_method != BLUR_METHOD_KERNEL) { - log_warn("Old backends only support blur method \"kernel\". Your blur " - "setting will not be applied"); - ps->o.blur_method = BLUR_METHOD_NONE; - } - - if (ps->o.blur_method == BLUR_METHOD_KERNEL) { - ps->blur_kerns_cache = - ccalloc(ps->o.blur_kernel_count, struct x_convolution_kernel *); - - bool ret = false; - if (ps->o.backend == BKEND_GLX) { -#ifdef CONFIG_OPENGL - ret = glx_init_blur(ps); -#else - assert(false); -#endif - } else { - ret = xr_init_blur(ps); - } - if (!ret) { - return ret; - } - } - - ps->black_picture = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0); - ps->white_picture = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1); - - if (ps->black_picture == XCB_NONE || ps->white_picture == XCB_NONE) { - log_error("Failed to create solid xrender pictures."); - return false; - } - - // Generates another Picture for shadows if the color is modified by - // user - if (ps->o.shadow_red == 0 && ps->o.shadow_green == 0 && ps->o.shadow_blue == 0) { - ps->cshadow_picture = ps->black_picture; - } else { - ps->cshadow_picture = solid_picture(ps->c, ps->root, true, 1, ps->o.shadow_red, - ps->o.shadow_green, ps->o.shadow_blue); - if (ps->cshadow_picture == XCB_NONE) { - log_error("Failed to create shadow picture."); - return false; - } - } - - // Initialize our rounded corners fragment shader - if (ps->o.corner_radius > 0 && ps->o.backend == BKEND_GLX) { -#ifdef CONFIG_OPENGL - if (!glx_init_rounded_corners(ps)) { - log_error("Failed to init rounded corners shader."); - return false; - } -#else - assert(false); -#endif - } - return true; -} - -/** - * Free root tile related things. - */ -void free_root_tile(session_t *ps) { - free_picture(ps->c, &ps->root_tile_paint.pict); -#ifdef CONFIG_OPENGL - free_texture(ps, &ps->root_tile_paint.ptex); -#else - assert(!ps->root_tile_paint.ptex); -#endif - if (ps->root_tile_fill) { - xcb_free_pixmap(ps->c, ps->root_tile_paint.pixmap); - ps->root_tile_paint.pixmap = XCB_NONE; - } - ps->root_tile_paint.pixmap = XCB_NONE; - ps->root_tile_fill = false; -} - -void deinit_render(session_t *ps) { - // Free alpha_picts - for (int i = 0; i <= MAX_ALPHA; ++i) - free_picture(ps->c, &ps->alpha_picts[i]); - free(ps->alpha_picts); - ps->alpha_picts = NULL; - - // Free cshadow_picture and black_picture - if (ps->cshadow_picture == ps->black_picture) - ps->cshadow_picture = XCB_NONE; - else - free_picture(ps->c, &ps->cshadow_picture); - - free_picture(ps->c, &ps->black_picture); - free_picture(ps->c, &ps->white_picture); - - // Free other X resources - free_root_tile(ps); - -#ifdef CONFIG_OPENGL - free(ps->root_tile_paint.fbcfg); - if (bkend_use_glx(ps)) { - glx_destroy(ps); - } -#endif - - if (ps->o.blur_method != BLUR_METHOD_NONE) { - for (int i = 0; i < ps->o.blur_kernel_count; i++) { - free(ps->blur_kerns_cache[i]); - } - free(ps->blur_kerns_cache); - } -} - -// vim: set ts=8 sw=8 noet : diff --git a/src/render.h b/src/render.h deleted file mode 100644 index 95a46db..0000000 --- a/src/render.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#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 *); diff --git a/src/string_utils.c b/src/string_utils.c deleted file mode 100644 index 65af0f2..0000000 --- a/src/string_utils.c +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> - -#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'); -} diff --git a/src/string_utils.h b/src/string_utils.h deleted file mode 100644 index 38febde..0000000 --- a/src/string_utils.h +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#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) diff --git a/src/types.h b/src/types.h deleted file mode 100644 index c8d747b..0000000 --- a/src/types.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018 Yuxuan Shui <[email protected]> - -#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 } diff --git a/src/uthash_extra.h b/src/uthash_extra.h deleted file mode 100644 index cbc1056..0000000 --- a/src/uthash_extra.h +++ /dev/null @@ -1,7 +0,0 @@ -#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) diff --git a/src/utils.c b/src/utils.c deleted file mode 100644 index 8a27f39..0000000 --- a/src/utils.c +++ /dev/null @@ -1,51 +0,0 @@ -#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 : diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index 6bb8643..0000000 --- a/src/utils.h +++ /dev/null @@ -1,294 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018 Yuxuan Shui <[email protected]> -#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 "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)) - -/// 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); - - -// vim: set noet sw=8 ts=8 : diff --git a/src/vsync.c b/src/vsync.c deleted file mode 100644 index 5980155..0000000 --- a/src/vsync.c +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> - -/// 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; -} diff --git a/src/vsync.h b/src/vsync.h deleted file mode 100644 index 076bc26..0000000 --- a/src/vsync.h +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#include <stdbool.h> - -typedef struct session session_t; - -bool vsync_init(session_t *ps); diff --git a/src/win.c b/src/win.c deleted file mode 100644 index 971bdc9..0000000 --- a/src/win.c +++ /dev/null @@ -1,3116 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2011-2013, Christopher Jeffrey -// Copyright (c) 2013 Richard Grenville <[email protected]> - -#include <X11/Xlib.h> -#include <X11/Xutil.h> -#include <inttypes.h> -#include <math.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <xcb/composite.h> -#include <xcb/damage.h> -#include <xcb/render.h> -#include <xcb/xcb.h> -#include <xcb/xcb_renderutil.h> -#include <xcb/xinerama.h> - -#include "atom.h" -#include "backend/backend.h" -#include "backend/backend_common.h" -#include "c2.h" -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "list.h" -#include "log.h" -#include "picom.h" -#include "region.h" -#include "render.h" -#include "string_utils.h" -#include "types.h" -#include "uthash_extra.h" -#include "utils.h" -#include "x.h" - -#ifdef CONFIG_DBUS -#include "dbus.h" -#endif - -#ifdef CONFIG_OPENGL -// TODO(yshui) Get rid of this include -#include "opengl.h" -#endif - -#include "win.h" - -// TODO(yshui) Make more window states internal -struct managed_win_internal { - struct managed_win base; -}; - -#define OPAQUE (0xffffffff) -static const int WIN_GET_LEADER_MAX_RECURSION = 20; -static const int ROUNDED_PIXELS = 1; -static const double ROUNDED_PERCENT = 0.05; - -/** - * Retrieve the <code>WM_CLASS</code> of a window and update its - * <code>win</code> structure. - */ -static bool win_update_class(session_t *ps, struct managed_win *w); -static int win_update_role(session_t *ps, struct managed_win *w); -static void win_update_wintype(session_t *ps, struct managed_win *w); -static int win_update_name(session_t *ps, struct managed_win *w); -/** - * Reread opacity property of a window. - */ -static void win_update_opacity_prop(session_t *ps, struct managed_win *w); -static void win_update_opacity_target(session_t *ps, struct managed_win *w); -/** - * Retrieve frame extents from a window. - */ -static void -win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client); -static void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w); -static void win_update_prop_shadow(session_t *ps, struct managed_win *w); -/** - * Update leader of a window. - */ -static void win_update_leader(session_t *ps, struct managed_win *w); - -/// Generate a "no corners" region function, from a function that returns the -/// region via a region_t pointer argument. Corners of the window will be removed from -/// the returned region. -/// Function signature has to be (win *, region_t *) -#define gen_without_corners(fun) \ - void fun##_without_corners(const struct managed_win *w, region_t *res) { \ - fun(w, res); \ - win_region_remove_corners(w, res); \ - } - -/// Generate a "return by value" function, from a function that returns the -/// region via a region_t pointer argument. -/// Function signature has to be (win *) -#define gen_by_val(fun) \ - region_t fun##_by_val(const struct managed_win *w) { \ - region_t ret; \ - pixman_region32_init(&ret); \ - fun(w, &ret); \ - return ret; \ - } - -/** - * Clear leader cache of all windows. - */ -static inline void clear_cache_win_leaders(session_t *ps) { - win_stack_foreach_managed(w, &ps->window_stack) { - w->cache_leader = XCB_NONE; - } -} - -static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int recursions); - -/** - * Get the leader of a window. - * - * This function updates w->cache_leader if necessary. - */ -static inline xcb_window_t win_get_leader(session_t *ps, struct managed_win *w) { - return win_get_leader_raw(ps, w, 0); -} - -/** - * Whether the real content of the window is visible. - * - * A window is not considered "real" visible if it's fading out. Because in that case a - * cached version of the window is displayed. - */ -static inline bool attr_pure win_is_real_visible(const struct managed_win *w) { - return w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && - w->state != WSTATE_UNMAPPING; -} - -/** - * Update focused state of a window. - */ -static void win_update_focused(session_t *ps, struct managed_win *w) { - if (UNSET != w->focused_force) { - w->focused = w->focused_force; - } else { - w->focused = win_is_focused_raw(ps, w); - - // Use wintype_focus, and treat WM windows and override-redirected - // windows specially - if (ps->o.wintype_option[w->window_type].focus || - (ps->o.mark_wmwin_focused && w->wmwin) || - (ps->o.mark_ovredir_focused && w->base.id == w->client_win && !w->wmwin) || - (w->a.map_state == XCB_MAP_STATE_VIEWABLE && - c2_match(ps, w, ps->o.focus_blacklist, NULL))) { - w->focused = true; - } - - // If window grouping detection is enabled, mark the window active if - // its group is - if (ps->o.track_leader && ps->active_leader && - win_get_leader(ps, w) == ps->active_leader) { - w->focused = true; - } - } -} - -/** - * Run win_on_factor_change() on all windows with the same leader window. - * - * @param leader leader window ID - */ -static inline void group_on_factor_change(session_t *ps, xcb_window_t leader) { - if (!leader) { - return; - } - - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - if (!w->managed) { - continue; - } - auto mw = (struct managed_win *)w; - if (win_get_leader(ps, mw) == leader) { - win_on_factor_change(ps, mw); - } - } -} - -static inline const char *win_get_name_if_managed(const struct win *w) { - if (!w->managed) { - return "(unmanaged)"; - } - auto mw = (struct managed_win *)w; - return mw->name; -} - -/** - * Return whether a window group is really focused. - * - * @param leader leader window ID - * @return true if the window group is focused, false otherwise - */ -static inline bool group_is_focused(session_t *ps, xcb_window_t leader) { - if (!leader) { - return false; - } - - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - if (!w->managed) { - continue; - } - auto mw = (struct managed_win *)w; - if (win_get_leader(ps, mw) == leader && win_is_focused_raw(ps, mw)) { - return true; - } - } - - return false; -} - -/** - * Get a rectangular region a window occupies, excluding shadow. - */ -static void win_get_region_local(const struct managed_win *w, region_t *res) { - assert(w->widthb >= 0 && w->heightb >= 0); - pixman_region32_fini(res); - pixman_region32_init_rect(res, 0, 0, (uint)w->widthb, (uint)w->heightb); -} - -/** - * Get a rectangular region a window occupies, excluding frame and shadow. - */ -void win_get_region_noframe_local(const struct managed_win *w, region_t *res) { - const margin_t extents = win_calc_frame_extents(w); - - int x = extents.left; - int y = extents.top; - int width = max2(w->widthb - (extents.left + extents.right), 0); - int height = max2(w->heightb - (extents.top + extents.bottom), 0); - - pixman_region32_fini(res); - if (width > 0 && height > 0) { - pixman_region32_init_rect(res, x, y, (uint)width, (uint)height); - } else { - pixman_region32_init(res); - } -} - -gen_without_corners(win_get_region_noframe_local); - -void win_get_region_frame_local(const struct managed_win *w, region_t *res) { - const margin_t extents = win_calc_frame_extents(w); - auto outer_width = w->widthb; - auto outer_height = w->heightb; - - pixman_region32_fini(res); - pixman_region32_init_rects( - res, - (rect_t[]){ - // top - {.x1 = 0, .y1 = 0, .x2 = outer_width, .y2 = extents.top}, - // bottom - {.x1 = 0, .y1 = outer_height - extents.bottom, .x2 = outer_width, .y2 = outer_height}, - // left - {.x1 = 0, .y1 = 0, .x2 = extents.left, .y2 = outer_height}, - // right - {.x1 = outer_width - extents.right, .y1 = 0, .x2 = outer_width, .y2 = outer_height}, - }, - 4); - - // limit the frame region to inside the window - region_t reg_win; - pixman_region32_init_rects(®_win, (rect_t[]){{0, 0, outer_width, outer_height}}, 1); - pixman_region32_intersect(res, ®_win, res); - pixman_region32_fini(®_win); -} - -gen_by_val(win_get_region_frame_local); - -/** - * 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) { - // XXX there was a cached extents region, investigate - // if that's better - - // TODO(yshui) use the bounding shape when the window is shaped, otherwise the - // damage would be excessive - region_t extents; - pixman_region32_init(&extents); - win_extents(w, &extents); - add_damage(ps, &extents); - pixman_region32_fini(&extents); -} - -/// Release the images attached to this window -static inline void win_release_pixmap(backend_t *base, struct managed_win *w) { - log_debug("Releasing pixmap of window %#010x (%s)", w->base.id, w->name); - assert(w->win_image); - if (w->win_image) { - base->ops->release_image(base, w->win_image); - w->win_image = NULL; - // Bypassing win_set_flags, because `w` might have been destroyed - w->flags |= WIN_FLAGS_PIXMAP_NONE; - } -} -static inline void win_release_oldpixmap(backend_t *base, struct managed_win *w) { - log_debug("Releasing old_pixmap of window %#010x (%s)", w->base.id, w->name); - if (w->old_win_image) { - base->ops->release_image(base, w->old_win_image); - w->old_win_image = NULL; - } -} -static inline void win_release_shadow(backend_t *base, struct managed_win *w) { - log_debug("Releasing shadow of window %#010x (%s)", w->base.id, w->name); - assert(w->shadow_image); - if (w->shadow_image) { - base->ops->release_image(base, w->shadow_image); - w->shadow_image = NULL; - // Bypassing win_set_flags, because `w` might have been destroyed - w->flags |= WIN_FLAGS_SHADOW_NONE; - } -} - -static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w) { - assert(!w->win_image); - auto pixmap = x_new_id(b->c); - auto e = xcb_request_check( - b->c, xcb_composite_name_window_pixmap_checked(b->c, w->base.id, pixmap)); - if (e) { - log_error("Failed to get named pixmap for window %#010x(%s)", w->base.id, - w->name); - free(e); - return false; - } - log_debug("New named pixmap for %#010x (%s) : %#010x", w->base.id, w->name, pixmap); - w->win_image = - b->ops->bind_pixmap(b, pixmap, x_get_visual_info(b->c, w->a.visual), true); - if (!w->win_image) { - log_error("Failed to bind pixmap"); - win_set_flags(w, WIN_FLAGS_IMAGE_ERROR); - return false; - } - - win_clear_flags(w, WIN_FLAGS_PIXMAP_NONE); - return true; -} - -bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c, - struct conv *kernel) { - assert(!w->shadow_image); - assert(w->shadow); - w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, kernel, c.red, - c.green, c.blue, c.alpha); - if (!w->shadow_image) { - log_error("Failed to bind shadow image, shadow will be disabled for " - "%#010x (%s)", - w->base.id, w->name); - win_set_flags(w, WIN_FLAGS_SHADOW_NONE); - w->shadow = false; - return false; - } - - log_debug("New shadow for %#010x (%s)", w->base.id, w->name); - win_clear_flags(w, WIN_FLAGS_SHADOW_NONE); - return true; -} - -void win_release_images(struct backend_base *backend, struct managed_win *w) { - // We don't want to decide what we should do if the image we want to release is - // stale (do we clear the stale flags or not?) - // But if we are not releasing any images anyway, we don't care about the stale - // flags. - - if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { - assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)); - win_release_pixmap(backend, w); - win_release_oldpixmap(backend, w); - } - - if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) { - assert(!win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE)); - win_release_shadow(backend, w); - } -} - -/// Returns true if the `prop` property is stale, as well as clears the stale flag. -static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop); -/// Returns true if any of the properties are stale, as well as clear all the stale flags. -static void win_clear_all_properties_stale(struct managed_win *w); - -/// Fetch new window properties from the X server, and run appropriate updates. Might set -/// WIN_FLAGS_FACTOR_CHANGED -static void win_update_properties(session_t *ps, struct managed_win *w) { - if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_TYPE)) { - win_update_wintype(ps, w); - } - - if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_OPACITY)) { - win_update_opacity_prop(ps, w); - // we cannot receive OPACITY change when window has been destroyed - assert(w->state != WSTATE_DESTROYING); - win_update_opacity_target(ps, w); - } - - if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_FRAME_EXTENTS)) { - win_update_frame_extents(ps, w, w->client_win); - add_damage_from_win(ps, w); - } - - if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_NAME) || - win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_NAME)) { - if (win_update_name(ps, w) == 1) { - win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED); - } - } - - if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_CLASS)) { - if (win_update_class(ps, w)) { - win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED); - } - } - - if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_WINDOW_ROLE)) { - if (win_update_role(ps, w) == 1) { - win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED); - } - } - - if (win_fetch_and_unset_property_stale(w, ps->atoms->a_COMPTON_SHADOW)) { - win_update_prop_shadow(ps, w); - } - - if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_CLIENT_LEADER) || - win_fetch_and_unset_property_stale(w, ps->atoms->aWM_TRANSIENT_FOR)) { - win_update_leader(ps, w); - } - - win_clear_all_properties_stale(w); -} - -static void init_animation(session_t *ps, struct managed_win *w) { - enum open_window_animation animation = ps->o.animation_for_open_window; - - w->animation_transient = wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR); - - if (w->window_type != WINTYPE_TOOLTIP && w->animation_transient) - animation = ps->o.animation_for_transient_window; - - if (ps->o.wintype_option[w->window_type].animation < OPEN_WINDOW_ANIMATION_INVALID) - animation = ps->o.wintype_option[w->window_type].animation; - - if (ps->root_desktop_switch_direction != 0) { - if (ps->o.animation_for_workspace_switch_in == OPEN_WINDOW_ANIMATION_AUTO) - animation = OPEN_WINDOW_ANIMATION_SLIDE_IN; - else - animation = ps->o.animation_for_workspace_switch_in; - } - - switch (animation) { - case OPEN_WINDOW_ANIMATION_AUTO: - case OPEN_WINDOW_ANIMATION_NONE: { // No animation - w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5; - w->animation_w = w->pending_g.width; - w->animation_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_FLYIN: { // Fly-in from a random point outside the screen - // Compute random point off screen - double angle = 2 * M_PI * ((double)rand() / RAND_MAX); - const double radius = - sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height); - - // Set animation - w->animation_center_x = ps->root_width * 0.5 + radius * cos(angle); - w->animation_center_y = ps->root_height * 0.5 + radius * sin(angle); - w->animation_w = 0; - w->animation_h = 0; - break; - } - case OPEN_WINDOW_ANIMATION_ZOOM: { // Zoom-in the image, without changing its location - w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5; - w->animation_w = 0; - w->animation_h = 0; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_UP: { // Slide up the image, without changing its location - w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_center_y = w->pending_g.y + w->pending_g.height; - w->animation_w = w->pending_g.width; - w->animation_h = 0; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_DOWN: { // Slide down the image, without changing its location - w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_center_y = w->pending_g.y; - w->animation_w = w->pending_g.width; - w->animation_h = 0; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_LEFT: { // Slide left the image, without changing its location - w->animation_center_x = w->pending_g.x + w->pending_g.width; - w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5; - w->animation_w = 0; - w->animation_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_RIGHT: { // Slide right the image, without changing its location - w->animation_center_x = w->pending_g.x; - w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5; - w->animation_w = 0; - w->animation_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_IN: { - w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5 - - ps->root_height * - ((ps->root_desktop_switch_direction < 0 && - ps->root_desktop_switch_direction >= -1) || - ps->root_desktop_switch_direction > 1?1:-1); - w->animation_w = w->pending_g.width; - w->animation_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_OUT: { - w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5 - - ps->root_height * - ((ps->root_desktop_switch_direction < 0 && - ps->root_desktop_switch_direction >= -1) || - ps->root_desktop_switch_direction > 1?-1:1); - w->animation_dest_w = w->pending_g.width; - w->animation_dest_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_INVALID: assert(false); break; - } -} - -static void init_animation_unmap(session_t *ps, struct managed_win *w) { - enum open_window_animation animation; - - if (ps->o.animation_for_unmap_window == OPEN_WINDOW_ANIMATION_AUTO) { - animation = ps->o.animation_for_open_window; - - if (w->window_type != WINTYPE_TOOLTIP && w->animation_transient) - animation = ps->o.animation_for_transient_window; - - if (ps->o.wintype_option[w->window_type].animation < OPEN_WINDOW_ANIMATION_INVALID) - animation = ps->o.wintype_option[w->window_type].animation; - - if (animation == OPEN_WINDOW_ANIMATION_SLIDE_UP) - animation = OPEN_WINDOW_ANIMATION_SLIDE_DOWN; - else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_DOWN) - animation = OPEN_WINDOW_ANIMATION_SLIDE_UP; - else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_LEFT) - animation = OPEN_WINDOW_ANIMATION_SLIDE_RIGHT; - else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_RIGHT) - animation = OPEN_WINDOW_ANIMATION_SLIDE_LEFT; - else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_IN) - animation = OPEN_WINDOW_ANIMATION_SLIDE_OUT; - else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_OUT) - animation = OPEN_WINDOW_ANIMATION_SLIDE_IN; - - } else { - animation = ps->o.animation_for_unmap_window; - - if (ps->o.wintype_option[w->window_type].animation_unmap < OPEN_WINDOW_ANIMATION_INVALID) - animation = ps->o.wintype_option[w->window_type].animation_unmap; - } - - if (ps->root_desktop_switch_direction != 0) { - if (ps->o.animation_for_workspace_switch_out == OPEN_WINDOW_ANIMATION_AUTO) - animation = OPEN_WINDOW_ANIMATION_SLIDE_OUT; - else - animation = ps->o.animation_for_workspace_switch_out; - } - - switch (animation) { - case OPEN_WINDOW_ANIMATION_AUTO: - case OPEN_WINDOW_ANIMATION_NONE: { // No animation - w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; - w->animation_dest_w = w->pending_g.width; - w->animation_dest_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_FLYIN: { // Fly-out from a random point outside the screen - // Compute random point off screen - double angle = 2 * M_PI * ((double)rand() / RAND_MAX); - const double radius = - sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height); - - // Set animation - w->animation_dest_center_x = ps->root_width * 0.5 + radius * cos(angle); - w->animation_dest_center_y = ps->root_height * 0.5 + radius * sin(angle); - w->animation_dest_w = 0; - w->animation_dest_h = 0; - break; - } - case OPEN_WINDOW_ANIMATION_ZOOM: { // Zoom-out the image, without changing its location - w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; - w->animation_dest_w = 0; - w->animation_dest_h = 0; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_UP: { // Slide up the image, without changing its location - w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_dest_center_y = w->pending_g.y; - w->animation_dest_w = w->pending_g.width; - w->animation_dest_h = 0; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_DOWN: { // Slide down the image, without changing its location - w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_dest_center_y = w->pending_g.y + w->pending_g.height; - w->animation_dest_w = w->pending_g.width; - w->animation_dest_h = 0; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_LEFT: { // Slide left the image, without changing its location - w->animation_dest_center_x = w->pending_g.x; - w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; - w->animation_dest_w = 0; - w->animation_dest_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_RIGHT: { // Slide right the image, without changing its location - w->animation_dest_center_x = w->pending_g.x + w->pending_g.width; - w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; - w->animation_dest_w = 0; - w->animation_dest_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_IN: { - w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5 - - ps->root_height * - ((ps->root_desktop_switch_direction < 0 && - ps->root_desktop_switch_direction >= -1) || - ps->root_desktop_switch_direction > 1?1:-1); - w->animation_w = w->pending_g.width; - w->animation_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_OUT: { - w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5 - - ps->root_height * - ((ps->root_desktop_switch_direction < 0 && - ps->root_desktop_switch_direction >= -1) || - ps->root_desktop_switch_direction > 1?-1:1); - w->animation_dest_w = w->pending_g.width; - w->animation_dest_h = w->pending_g.height; - break; - } - case OPEN_WINDOW_ANIMATION_INVALID: assert(false); break; - } -} - -/// Handle non-image flags. This phase might set IMAGES_STALE flags -void win_process_update_flags(session_t *ps, struct managed_win *w) { - // Whether the window was visible before we process the mapped flag. i.e. is the - // window just mapped. - bool was_visible = win_is_real_visible(w); - log_trace("Processing flags for window %#010x (%s), was visible: %d", w->base.id, - w->name, was_visible); - - if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) { - map_win_start(ps, w); - win_clear_flags(w, WIN_FLAGS_MAPPED); - } - - if (!win_is_real_visible(w)) { - // Flags of invisible windows are processed when they are mapped - return; - } - - // Check client first, because later property updates need accurate client window - // information - if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) { - win_recheck_client(ps, w); - win_clear_flags(w, WIN_FLAGS_CLIENT_STALE); - } - - bool damaged = false; - if (win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) { - if (was_visible) { - // Mark the old extents of this window as damaged. The new extents - // will be marked damaged below, after the window extents are - // updated. - // - // If the window is just mapped, we don't need to mark the old - // extent as damaged. (It's possible that the window was in fading - // and is interrupted by being mapped. In that case, the fading - // window will be added to damage by map_win_start, so we don't - // need to do it here) - add_damage_from_win(ps, w); - } - - // Ignore animations all together if set to none on window type basis - if (ps->o.wintype_option[w->window_type].animation == 0) { - w->g = w->pending_g; - - // Update window geometry - } else if (ps->o.animations) { - if (!was_visible) { - // Set window-open animation - init_animation(ps, w); - - w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; - w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; - w->animation_dest_w = w->pending_g.width; - w->animation_dest_h = w->pending_g.height; - - w->g.x = (int16_t)round(w->animation_center_x - - w->animation_w * 0.5); - w->g.y = (int16_t)round(w->animation_center_y - - w->animation_h * 0.5); - w->g.width = (uint16_t)round(w->animation_w); - w->g.height = (uint16_t)round(w->animation_h); - - } else { - w->animation_dest_center_x = - w->pending_g.x + w->pending_g.width * 0.5; - w->animation_dest_center_y = - w->pending_g.y + w->pending_g.height * 0.5; - w->animation_dest_w = w->pending_g.width; - w->animation_dest_h = w->pending_g.height; - } - - w->g.border_width = w->pending_g.border_width; - - double x_dist = w->animation_dest_center_x - w->animation_center_x; - double y_dist = w->animation_dest_center_y - w->animation_center_y; - double w_dist = w->animation_dest_w - w->animation_w; - double h_dist = w->animation_dest_h - w->animation_h; - w->animation_inv_og_distance = - 1.0 / sqrt(x_dist * x_dist + y_dist * y_dist + - w_dist * w_dist + h_dist * h_dist); - - if (isinf(w->animation_inv_og_distance)) - w->animation_inv_og_distance = 0; - - // We only grab images if w->reg_ignore_valid is true as - // there's an ev_shape_notify() event fired quickly on new windows - // for e.g. in case of Firefox main menu and ev_shape_notify() - // sets the win_set_flags(w, WIN_FLAGS_SIZE_STALE); which - // brakes the new image captured and because this same event - // also sets w->reg_ignore_valid = false; too we check for it - if (w->reg_ignore_valid) { - if (w->old_win_image) { - ps->backend_data->ops->release_image(ps->backend_data, - w->old_win_image); - w->old_win_image = NULL; - } - - // We only grab - if (w->win_image) { - w->old_win_image = ps->backend_data->ops->clone_image( - ps->backend_data, w->win_image, &w->bounding_shape); - } - } - - w->animation_progress = 0.0; - - } else { - w->g = w->pending_g; - } - - if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { - win_on_win_size_change(ps, w); - win_update_bounding_shape(ps, w); - damaged = true; - win_clear_flags(w, WIN_FLAGS_SIZE_STALE); - } - - if (win_check_flags_all(w, WIN_FLAGS_POSITION_STALE)) { - damaged = true; - win_clear_flags(w, WIN_FLAGS_POSITION_STALE); - } - - win_update_screen(ps->xinerama_nscrs, ps->xinerama_scr_regs, w); - } - - if (win_check_flags_all(w, WIN_FLAGS_PROPERTY_STALE)) { - win_update_properties(ps, w); - win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE); - } - - // Factor change flags could be set by previous stages, so must be handled last - if (win_check_flags_all(w, WIN_FLAGS_FACTOR_CHANGED)) { - win_on_factor_change(ps, w); - win_clear_flags(w, WIN_FLAGS_FACTOR_CHANGED); - } - - // Add damage, has to be done last so the window has the latest geometry - // information. - if (damaged) { - add_damage_from_win(ps, w); - } -} - -void win_process_image_flags(session_t *ps, struct managed_win *w) { - assert(!win_check_flags_all(w, WIN_FLAGS_MAPPED)); - - if (w->state == WSTATE_UNMAPPED || w->state == WSTATE_DESTROYING || - w->state == WSTATE_UNMAPPING) { - // Flags of invisible windows are processed when they are mapped - return; - } - - // Not a loop - while (win_check_flags_any(w, WIN_FLAGS_IMAGES_STALE) && - !win_check_flags_all(w, WIN_FLAGS_IMAGE_ERROR)) { - // Image needs to be updated, update it. - if (!ps->backend_data) { - // We are using legacy backend, nothing to do here. - break; - } - - if (win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)) { - // Check to make sure the window is still mapped, otherwise we - // won't be able to rebind pixmap after releasing it, yet we might - // still need the pixmap for rendering. - assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING); - if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { - // Must release images first, otherwise breaks - // NVIDIA driver - win_release_pixmap(ps->backend_data, w); - } - win_bind_pixmap(ps->backend_data, w); - } - - if (win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE)) { - if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) { - win_release_shadow(ps->backend_data, w); - } - if (w->shadow) { - win_bind_shadow(ps->backend_data, w, - (struct color){.red = ps->o.shadow_red, - .green = ps->o.shadow_green, - .blue = ps->o.shadow_blue, - .alpha = ps->o.shadow_opacity}, - ps->gaussian_map); - } - } - - // break here, loop always run only once - break; - } - - // Clear stale image flags - if (win_check_flags_any(w, WIN_FLAGS_IMAGES_STALE)) { - win_clear_flags(w, WIN_FLAGS_IMAGES_STALE); - } -} - -/** - * Check if a window has rounded corners. - * XXX This is really dumb - */ -static bool attr_pure win_has_rounded_corners(const struct managed_win *w) { - if (!w->bounding_shaped) { - return false; - } - - // Quit if border_size() returns XCB_NONE - if (!pixman_region32_not_empty((region_t *)&w->bounding_shape)) { - return false; - } - - // Determine the minimum width/height of a rectangle that could mark - // a window as having rounded corners - auto minwidth = - (uint16_t)max2(w->widthb * (1 - ROUNDED_PERCENT), w->widthb - ROUNDED_PIXELS); - auto minheight = - (uint16_t)max2(w->heightb * (1 - ROUNDED_PERCENT), w->heightb - ROUNDED_PIXELS); - - // Get the rectangles in the bounding region - int nrects = 0; - const rect_t *rects = - pixman_region32_rectangles((region_t *)&w->bounding_shape, &nrects); - - // Look for a rectangle large enough for this window be considered - // having rounded corners - for (int i = 0; i < nrects; ++i) { - if (rects[i].x2 - rects[i].x1 >= minwidth && - rects[i].y2 - rects[i].y1 >= minheight) { - return true; - } - } - return false; -} - -int win_update_name(session_t *ps, struct managed_win *w) { - char **strlst = NULL; - int nstr = 0; - - if (!w->client_win) { - return 0; - } - - if (!(wid_get_text_prop(ps, w->client_win, ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) { - log_debug("(%#010x): _NET_WM_NAME unset, falling back to WM_NAME.", - w->client_win); - - if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_NAME, &strlst, &nstr)) { - log_debug("Unsetting window name for %#010x", w->client_win); - free(w->name); - w->name = NULL; - return -1; - } - } - - int ret = 0; - if (!w->name || strcmp(w->name, strlst[0]) != 0) { - ret = 1; - free(w->name); - w->name = strdup(strlst[0]); - } - - free(strlst); - - log_debug("(%#010x): client = %#010x, name = \"%s\", " - "ret = %d", - w->base.id, w->client_win, w->name, ret); - return ret; -} - -static int win_update_role(session_t *ps, struct managed_win *w) { - char **strlst = NULL; - int nstr = 0; - - if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) { - return -1; - } - - int ret = 0; - if (!w->role || strcmp(w->role, strlst[0]) != 0) { - ret = 1; - free(w->role); - w->role = strdup(strlst[0]); - } - - free(strlst); - - log_trace("(%#010x): client = %#010x, role = \"%s\", " - "ret = %d", - w->base.id, w->client_win, w->role, ret); - return ret; -} - -/** - * Check if a window is bounding-shaped. - */ -static inline bool win_bounding_shaped(const session_t *ps, xcb_window_t wid) { - if (ps->shape_exists) { - xcb_shape_query_extents_reply_t *reply; - Bool bounding_shaped; - - reply = xcb_shape_query_extents_reply( - ps->c, xcb_shape_query_extents(ps->c, wid), NULL); - bounding_shaped = reply && reply->bounding_shaped; - free(reply); - - return bounding_shaped; - } - - return false; -} - -static wintype_t wid_get_prop_wintype(session_t *ps, xcb_window_t wid) { - winprop_t prop = - x_get_prop(ps->c, wid, ps->atoms->a_NET_WM_WINDOW_TYPE, 32L, XCB_ATOM_ATOM, 32); - - for (unsigned i = 0; i < prop.nitems; ++i) { - for (wintype_t j = 1; j < NUM_WINTYPES; ++j) { - if (ps->atoms_wintypes[j] == (xcb_atom_t)prop.p32[i]) { - free_winprop(&prop); - return j; - } - } - } - - free_winprop(&prop); - - return WINTYPE_UNKNOWN; -} - -static bool -wid_get_opacity_prop(session_t *ps, xcb_window_t wid, opacity_t def, opacity_t *out) { - bool ret = false; - *out = def; - - winprop_t prop = x_get_prop(ps->c, wid, ps->atoms->a_NET_WM_WINDOW_OPACITY, 1L, - XCB_ATOM_CARDINAL, 32); - - if (prop.nitems) { - *out = *prop.c32; - ret = true; - } - - free_winprop(&prop); - - return ret; -} - -// XXX should distinguish between frame has alpha and window body has alpha -bool win_has_alpha(const struct managed_win *w) { - return w->pictfmt && w->pictfmt->type == XCB_RENDER_PICT_TYPE_DIRECT && - w->pictfmt->direct.alpha_mask; -} - -bool win_client_has_alpha(const struct managed_win *w) { - return w->client_pictfmt && w->client_pictfmt->type == XCB_RENDER_PICT_TYPE_DIRECT && - w->client_pictfmt->direct.alpha_mask; -} - -winmode_t win_calc_mode(const struct managed_win *w) { - if (w->opacity < 1.0) { - return WMODE_TRANS; - } - - if (win_has_alpha(w)) { - if (w->client_win == XCB_NONE) { - // This is a window not managed by the WM, and it has alpha, - // so it's transparent. No need to check WM frame. - return WMODE_TRANS; - } - // The WM window has alpha - if (win_client_has_alpha(w)) { - // The client window also has alpha, the entire window is - // transparent - return WMODE_TRANS; - } - if (win_has_frame(w)) { - // The client window doesn't have alpha, but we have a WM frame - // window, which has alpha. - return WMODE_FRAME_TRANS; - } - // Although the WM window has alpha, the frame window has 0 size, so - // consider the window solid - } - - if (w->frame_opacity != 1.0 && win_has_frame(w)) { - return WMODE_FRAME_TRANS; - } - - // log_trace("Window %#010x(%s) is solid", w->client_win, w->name); - return WMODE_SOLID; -} - -/** - * 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 win_calc_opacity_target(session_t *ps, const struct managed_win *w) { - double opacity = 1; - - if (w->state == WSTATE_UNMAPPED) { - // be consistent - return 0; - } - if (w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) { - if (ps->root_desktop_switch_direction) - return w->opacity; - - return 0; - } - // Try obeying opacity property and window type opacity firstly - if (w->has_opacity_prop) { - opacity = ((double)w->opacity_prop) / OPAQUE; - } else if (w->opacity_is_set) { - opacity = w->opacity_set; - } else if (!safe_isnan(ps->o.wintype_option[w->window_type].opacity)) { - opacity = ps->o.wintype_option[w->window_type].opacity; - } else { - // Respect active_opacity only when the window is physically focused - if (win_is_focused_raw(ps, w)) - opacity = ps->o.active_opacity; - else if (!w->focused) - // Respect inactive_opacity in some cases - opacity = ps->o.inactive_opacity; - } - - // respect inactive override - if (ps->o.inactive_opacity_override && !w->focused) { - opacity = ps->o.inactive_opacity; - } - - return opacity; -} - -/** - * Determine whether a window is to be dimmed. - */ -bool win_should_dim(session_t *ps, const struct managed_win *w) { - // Make sure we do nothing if the window is unmapped / being destroyed - if (w->state == WSTATE_UNMAPPED) { - return false; - } - - if (ps->o.inactive_dim > 0 && !(w->focused)) { - return true; - } else { - return false; - } -} - -/** - * Determine if a window should fade on opacity change. - */ -bool win_should_fade(session_t *ps, const struct managed_win *w) { - // To prevent it from being overwritten by last-paint value if the window is - if (w->fade_force != UNSET) { - return w->fade_force; - } - if (ps->o.no_fading_openclose && w->in_openclose) { - return false; - } - if (ps->o.no_fading_destroyed_argb && w->state == WSTATE_DESTROYING && - win_has_alpha(w) && w->client_win && w->client_win != w->base.id) { - // deprecated - return false; - } - if (w->fade_excluded) { - return false; - } - return ps->o.wintype_option[w->window_type].fade; -} - -/** - * Reread _COMPTON_SHADOW property from a window. - * - * The property must be set on the outermost window, usually the WM frame. - */ -void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w) { - winprop_t prop = x_get_prop(ps->c, w->base.id, ps->atoms->a_COMPTON_SHADOW, 1, - XCB_ATOM_CARDINAL, 32); - - if (!prop.nitems) { - w->prop_shadow = -1; - } else { - w->prop_shadow = *prop.c32; - } - - free_winprop(&prop); -} - -static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new) { - if (w->shadow == shadow_new) { - return; - } - - log_debug("Updating shadow property of window %#010x (%s) to %d", w->base.id, - w->name, shadow_new); - - // We don't handle property updates of non-visible windows until they are mapped. - assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && - w->state != WSTATE_UNMAPPING); - - // Keep a copy of window extent before the shadow change. Will be used for - // calculation of damaged region - region_t extents; - pixman_region32_init(&extents); - win_extents(w, &extents); - - // Apply the shadow change - w->shadow = shadow_new; - - if (ps->redirected) { - // Add damage for shadow change - - // Window extents need update on shadow state change - // Shadow geometry currently doesn't change on shadow state change - // calc_shadow_geometry(ps, w); - - // Note: because the release and creation of the shadow images are - // delayed. When multiple shadow changes happen in a row, without - // rendering phase between them, there could be a stale shadow image - // attached to the window even if w->shadow was previously false. And vice - // versa. So we check the STALE flag before asserting the existence of the - // shadow image. - if (w->shadow) { - // Mark the new extents as damaged if the shadow is added - assert(!w->shadow_image || - win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) || - !ps->o.experimental_backends); - pixman_region32_clear(&extents); - win_extents(w, &extents); - add_damage_from_win(ps, w); - } else { - // Mark the old extents as damaged if the shadow is removed - assert(w->shadow_image || - win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) || - !ps->o.experimental_backends); - add_damage(ps, &extents); - } - - // Delayed update of shadow image - // By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to - // re-create or release the shaodw in based on whether w->shadow is set. - win_set_flags(w, WIN_FLAGS_SHADOW_STALE); - - // Only set pending_updates if we are redirected. Otherwise change of a - // shadow won't have influence on whether we should redirect. - ps->pending_updates = true; - } - - pixman_region32_fini(&extents); -} - -/** - * Determine if a window should have shadow, and update things depending - * on shadow state. - */ -static void win_determine_shadow(session_t *ps, struct managed_win *w) { - log_debug("Determining shadow of window %#010x (%s)", w->base.id, w->name); - bool shadow_new = w->shadow; - - if (w->shadow_force != UNSET) { - shadow_new = w->shadow_force; - } else if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { - shadow_new = true; - if (!ps->o.wintype_option[w->window_type].shadow) { - log_debug("Shadow disabled by wintypes"); - shadow_new = false; - } else if (c2_match(ps, w, ps->o.shadow_blacklist, NULL)) { - log_debug("Shadow disabled by shadow-exclude"); - shadow_new = false; - } else if (ps->o.shadow_ignore_shaped && w->bounding_shaped && - !w->rounded_corners) { - log_debug("Shadow disabled by shadow-ignore-shaped"); - shadow_new = false; - } else if (w->prop_shadow == 0) { - log_debug("Shadow disabled by shadow property"); - shadow_new = false; - } - } - - win_set_shadow(ps, w, shadow_new); -} - -/** - * Reread _COMPTON_SHADOW property from a window and update related - * things. - */ -void win_update_prop_shadow(session_t *ps, struct managed_win *w) { - long attr_shadow_old = w->prop_shadow; - - win_update_prop_shadow_raw(ps, w); - - if (w->prop_shadow != attr_shadow_old) { - win_determine_shadow(ps, w); - } -} - -static void win_determine_clip_shadow_above(session_t *ps, struct managed_win *w) { - bool should_crop = (ps->o.wintype_option[w->window_type].clip_shadow_above || - c2_match(ps, w, ps->o.shadow_clip_list, NULL)); - w->clip_shadow_above = should_crop; -} - -static void win_set_invert_color(session_t *ps, struct managed_win *w, bool invert_color_new) { - if (w->invert_color == invert_color_new) { - return; - } - - w->invert_color = invert_color_new; - - add_damage_from_win(ps, w); -} - -/** - * Determine if a window should have color inverted. - */ -static void win_determine_invert_color(session_t *ps, struct managed_win *w) { - bool invert_color_new = w->invert_color; - - if (UNSET != w->invert_color_force) { - invert_color_new = w->invert_color_force; - } else if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { - invert_color_new = c2_match(ps, w, ps->o.invert_color_list, NULL); - } - - win_set_invert_color(ps, w, invert_color_new); -} - -/** - * Set w->invert_color_force of a window. - */ -void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t val) { - if (val != w->invert_color_force) { - w->invert_color_force = val; - win_determine_invert_color(ps, w); - queue_redraw(ps); - } -} - -/** - * Set w->fade_force of a window. - * - * Doesn't affect fading already in progress - */ -void win_set_fade_force(struct managed_win *w, switch_t val) { - w->fade_force = val; -} - -/** - * Set w->focused_force of a window. - */ -void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val) { - if (val != w->focused_force) { - w->focused_force = val; - win_on_factor_change(ps, w); - queue_redraw(ps); - } -} - -/** - * Set w->shadow_force of a window. - */ -void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val) { - if (val != w->shadow_force) { - w->shadow_force = val; - win_determine_shadow(ps, w); - queue_redraw(ps); - } -} - -static void -win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_background_new) { - if (w->blur_background == blur_background_new) - return; - - w->blur_background = blur_background_new; - - // This damage might not be absolutely necessary (e.g. when the window is opaque), - // but blur_background changes should be rare, so this should be fine. - add_damage_from_win(ps, w); -} - -/** - * Determine if a window should have background blurred. - */ -static void win_determine_blur_background(session_t *ps, struct managed_win *w) { - log_debug("Determining blur-background of window %#010x (%s)", w->base.id, w->name); - if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { - return; - } - - bool blur_background_new = ps->o.blur_method != BLUR_METHOD_NONE; - if (blur_background_new) { - if (!ps->o.wintype_option[w->window_type].blur_background) { - log_debug("Blur background disabled by wintypes"); - blur_background_new = false; - } else if (c2_match(ps, w, ps->o.blur_background_blacklist, NULL)) { - log_debug("Blur background disabled by blur-background-exclude"); - blur_background_new = false; - } - } - - win_set_blur_background(ps, w, blur_background_new); -} - -/** - * Determine if a window should have rounded corners. - */ -static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) { - if (ps->o.corner_radius == 0) { - w->corner_radius = 0; - return; - } - - // Don't round full screen windows & excluded windows - if ((w && win_is_fullscreen(ps, w)) || - c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL)) { - w->corner_radius = 0; - log_debug("Not rounding corners for window %#010x", w->base.id); - } else { - w->corner_radius = ps->o.corner_radius; - log_debug("Rounding corners for window %#010x", w->base.id); - // Initialize the border color to an invalid value - w->border_col[0] = w->border_col[1] = w->border_col[2] = - w->border_col[3] = -1.0F; - } -} - -/** - * Update window opacity according to opacity rules. - */ -void win_update_opacity_rule(session_t *ps, struct managed_win *w) { - if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { - return; - } - - double opacity = 1.0; - bool is_set = false; - void *val = NULL; - if (c2_match(ps, w, ps->o.opacity_rules, &val)) { - opacity = ((double)(long)val) / 100.0; - is_set = true; - } - - w->opacity_set = opacity; - w->opacity_is_set = is_set; -} - -/** - * Function to be called on window data changes. - * - * TODO(yshui) need better name - */ -void win_on_factor_change(session_t *ps, struct managed_win *w) { - log_debug("Window %#010x (%s) factor change", w->base.id, w->name); - // Focus needs to be updated first, as other rules might depend on the focused - // state of the window - win_update_focused(ps, w); - - win_determine_shadow(ps, w); - win_determine_clip_shadow_above(ps, w); - win_determine_invert_color(ps, w); - win_determine_blur_background(ps, w); - win_determine_rounded_corners(ps, w); - w->mode = win_calc_mode(w); - log_debug("Window mode changed to %d", w->mode); - win_update_opacity_rule(ps, w); - if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { - w->paint_excluded = c2_match(ps, w, ps->o.paint_blacklist, NULL); - } - if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { - w->unredir_if_possible_excluded = - c2_match(ps, w, ps->o.unredir_if_possible_blacklist, NULL); - } - - w->fade_excluded = c2_match(ps, w, ps->o.fade_blacklist, NULL); - - win_update_opacity_target(ps, w); - - w->reg_ignore_valid = false; -} - -/** - * Update cache data in struct _win that depends on window size. - */ -void win_on_win_size_change(session_t *ps, struct managed_win *w) { - w->widthb = w->g.width + w->g.border_width * 2; - w->heightb = w->g.height + w->g.border_width * 2; - w->shadow_dx = ps->o.shadow_offset_x; - w->shadow_dy = ps->o.shadow_offset_y; - w->shadow_width = w->widthb + ps->o.shadow_radius * 2; - w->shadow_height = w->heightb + ps->o.shadow_radius * 2; - - // We don't handle property updates of non-visible windows until they are mapped. - assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && - w->state != WSTATE_UNMAPPING); - - // Invalidate the shadow we built - if (w->state != WSTATE_DESTROYING) - win_set_flags(w, WIN_FLAGS_IMAGES_STALE); - - ps->pending_updates = true; - free_paint(ps, &w->shadow_paint); -} - -/** - * Update window type. - */ -void win_update_wintype(session_t *ps, struct managed_win *w) { - const wintype_t wtype_old = w->window_type; - - // Detect window type here - w->window_type = wid_get_prop_wintype(ps, w->client_win); - - // Conform to EWMH standard, if _NET_WM_WINDOW_TYPE is not present, take - // override-redirect windows or windows without WM_TRANSIENT_FOR as - // _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG. - if (WINTYPE_UNKNOWN == w->window_type) { - if (w->a.override_redirect || - !wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) - w->window_type = WINTYPE_NORMAL; - else - w->window_type = WINTYPE_DIALOG; - } - - if (w->window_type != wtype_old) { - win_on_factor_change(ps, w); - } -} - -/** - * Mark a window as the client window of another. - * - * @param ps current session - * @param w struct _win of the parent window - * @param client window ID of the client window - */ -void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) { - w->client_win = client; - - // If the window isn't mapped yet, stop here, as the function will be - // called in map_win() - if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { - return; - } - - auto e = xcb_request_check( - ps->c, xcb_change_window_attributes( - ps->c, client, XCB_CW_EVENT_MASK, - (const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_CLIENT)})); - if (e) { - log_error("Failed to change event mask of window %#010x", client); - free(e); - } - - win_update_wintype(ps, w); - - // Get frame widths. The window is in damaged area already. - win_update_frame_extents(ps, w, client); - - // Get window group - if (ps->o.track_leader) { - win_update_leader(ps, w); - } - - // Get window name and class if we are tracking them - win_update_name(ps, w); - win_update_class(ps, w); - win_update_role(ps, w); - - // Update everything related to conditions - win_on_factor_change(ps, w); - - auto r = xcb_get_window_attributes_reply( - ps->c, xcb_get_window_attributes(ps->c, w->client_win), &e); - if (!r) { - log_error_x_error(e, "Failed to get client window attributes"); - return; - } - - w->client_pictfmt = x_get_pictform_for_visual(ps->c, r->visual); - free(r); -} - -/** - * Unmark current client window of a window. - * - * @param ps current session - * @param w struct _win of the parent window - */ -void win_unmark_client(session_t *ps, struct managed_win *w) { - xcb_window_t client = w->client_win; - log_debug("Detaching client window %#010x from frame %#010x (%s)", client, - w->base.id, w->name); - - w->client_win = XCB_NONE; - - // Recheck event mask - xcb_change_window_attributes( - ps->c, client, XCB_CW_EVENT_MASK, - (const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_UNKNOWN)}); -} - -/** - * Look for the client window of a particular window. - */ -static xcb_window_t find_client_win(session_t *ps, xcb_window_t w) { - if (wid_has_prop(ps, w, ps->atoms->aWM_STATE)) { - return w; - } - - xcb_query_tree_reply_t *reply = - xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, w), NULL); - if (!reply) { - return 0; - } - - xcb_window_t *children = xcb_query_tree_children(reply); - int nchildren = xcb_query_tree_children_length(reply); - int i; - xcb_window_t ret = 0; - - for (i = 0; i < nchildren; ++i) { - if ((ret = find_client_win(ps, children[i]))) { - break; - } - } - - free(reply); - - return ret; -} - -/** - * Recheck client window of a window. - * - * @param ps current session - * @param w struct _win of the parent window - */ -void win_recheck_client(session_t *ps, struct managed_win *w) { - assert(ps->server_grabbed); - // Initialize wmwin to false - w->wmwin = false; - - // Look for the client window - - // Always recursively look for a window with WM_STATE, as Fluxbox - // sets override-redirect flags on all frame windows. - xcb_window_t cw = find_client_win(ps, w->base.id); - if (cw) { - log_debug("(%#010x): client %#010x", w->base.id, cw); - } - // Set a window's client window to itself if we couldn't find a - // client window - if (!cw) { - cw = w->base.id; - w->wmwin = !w->a.override_redirect; - log_debug("(%#010x): client self (%s)", w->base.id, - (w->wmwin ? "wmwin" : "override-redirected")); - } - - // Unmark the old one - if (w->client_win && w->client_win != cw) { - win_unmark_client(ps, w); - } - - // Mark the new one - win_mark_client(ps, w, cw); -} - -/** - * Free all resources in a <code>struct _win</code>. - */ -void free_win_res(session_t *ps, struct managed_win *w) { - // No need to call backend release_image here because - // finish_unmap_win should've done that for us. - // XXX unless we are called by session_destroy - // assert(w->win_data == NULL); - free_win_res_glx(ps, w); - free_paint(ps, &w->paint); - free_paint(ps, &w->shadow_paint); - // Above should be done during unmapping - // Except when we are called by session_destroy - - pixman_region32_fini(&w->bounding_shape); - // BadDamage may be thrown if the window is destroyed - set_ignore_cookie(ps, xcb_damage_destroy(ps->c, w->damage)); - rc_region_unref(&w->reg_ignore); - free(w->name); - free(w->class_instance); - free(w->class_general); - free(w->role); - - free(w->stale_props); - w->stale_props = NULL; - w->stale_props_capacity = 0; -} - -/// Insert a new window after list_node `prev` -/// New window will be in unmapped state -static struct win *add_win(session_t *ps, xcb_window_t id, struct list_node *prev) { - log_debug("Adding window %#010x", id); - struct win *old_w = NULL; - HASH_FIND_INT(ps->windows, &id, old_w); - assert(old_w == NULL); - - auto new_w = cmalloc(struct win); - list_insert_after(prev, &new_w->stack_neighbour); - new_w->id = id; - new_w->managed = false; - new_w->is_new = true; - new_w->destroyed = false; - - HASH_ADD_INT(ps->windows, id, new_w); - ps->pending_updates = true; - return new_w; -} - -/// Insert a new win entry at the top of the stack -struct win *add_win_top(session_t *ps, xcb_window_t id) { - return add_win(ps, id, &ps->window_stack); -} - -/// Insert a new window above window with id `below`, if there is no window, add to top -/// New window will be in unmapped state -struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) { - struct win *w = NULL; - HASH_FIND_INT(ps->windows, &below, w); - if (!w) { - if (!list_is_empty(&ps->window_stack)) { - // `below` window is not found even if the window stack is not - // empty - return NULL; - } - return add_win_top(ps, id); - } else { - // we found something from the hash table, so if the stack is empty, - // we are in an inconsistent state. - assert(!list_is_empty(&ps->window_stack)); - return add_win(ps, id, w->stack_neighbour.prev); - } -} - -/// Query the Xorg for information about window `win` -/// `win` pointer might become invalid after this function returns -/// Returns the pointer to the window, might be different from `w` -struct win *fill_win(session_t *ps, struct win *w) { - static const struct managed_win win_def = { - // No need to initialize. (or, you can think that - // they are initialized right here). - // The following ones are updated during paint or paint preprocess - .shadow_opacity = 0.0, - .to_paint = false, - .frame_opacity = 1.0, - .dim = false, - .invert_color = false, - .blur_background = false, - .reg_ignore = NULL, - // The following ones are updated for other reasons - .pixmap_damaged = false, // updated by damage events - .state = WSTATE_UNMAPPED, // updated by window state changes - .in_openclose = true, // set to false after first map is done, - // true here because window is just created - .animation_velocity_x = 0.0, // updated by window geometry changes - .animation_velocity_y = 0.0, // updated by window geometry changes - .animation_velocity_w = 0.0, // updated by window geometry changes - .animation_velocity_h = 0.0, // updated by window geometry changes - .animation_progress = 1.0, // updated by window geometry changes - .animation_inv_og_distance = NAN, // updated by window geometry changes - .reg_ignore_valid = false, // set to true when damaged - .flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc - // change - .stale_props = NULL, - .stale_props_capacity = 0, - - // Runtime variables, updated by dbus - .fade_force = UNSET, - .shadow_force = UNSET, - .focused_force = UNSET, - .invert_color_force = UNSET, - - // Initialized in this function - .a = {0}, - .pictfmt = NULL, - .client_pictfmt = NULL, - .widthb = 0, - .heightb = 0, - .shadow_dx = 0, - .shadow_dy = 0, - .shadow_width = 0, - .shadow_height = 0, - .damage = XCB_NONE, - - // Not initialized until mapped, this variables - // have no meaning or have no use until the window - // is mapped - .win_image = NULL, - .old_win_image = NULL, - .shadow_image = NULL, - .prev_trans = NULL, - .shadow = false, - .clip_shadow_above = false, - .xinerama_scr = -1, - .mode = WMODE_TRANS, - .ever_damaged = false, - .client_win = XCB_NONE, - .leader = XCB_NONE, - .cache_leader = XCB_NONE, - .window_type = WINTYPE_UNKNOWN, - .wmwin = false, - .focused = false, - .opacity = 0, - .opacity_target = 0, - .has_opacity_prop = false, - .opacity_prop = OPAQUE, - .opacity_is_set = false, - .opacity_set = 1, - .frame_extents = MARGIN_INIT, // in win_mark_client - .bounding_shaped = false, - .bounding_shape = {0}, - .rounded_corners = false, - .paint_excluded = false, - .fade_excluded = false, - .unredir_if_possible_excluded = false, - .prop_shadow = -1, - // following 4 are set in win_mark_client - .name = NULL, - .class_instance = NULL, - .class_general = NULL, - .role = NULL, - - // Initialized during paint - .paint = PAINT_INIT, - .shadow_paint = PAINT_INIT, - - .corner_radius = 0, - }; - - assert(!w->destroyed); - assert(w->is_new); - - w->is_new = false; - - // Reject overlay window and already added windows - if (w->id == ps->overlay) { - return w; - } - - auto duplicated_win = find_managed_win(ps, w->id); - if (duplicated_win) { - log_debug("Window %#010x (recorded name: %s) added multiple times", w->id, - duplicated_win->name); - return &duplicated_win->base; - } - - log_debug("Managing window %#010x", w->id); - xcb_get_window_attributes_cookie_t acookie = xcb_get_window_attributes(ps->c, w->id); - xcb_get_window_attributes_reply_t *a = - xcb_get_window_attributes_reply(ps->c, acookie, NULL); - if (!a || a->map_state == XCB_MAP_STATE_UNVIEWABLE) { - // Failed to get window attributes or geometry probably means - // the window is gone already. Unviewable means the window is - // already reparented elsewhere. - // BTW, we don't care about Input Only windows, except for stacking - // proposes, so we need to keep track of them still. - free(a); - return w; - } - - if (a->_class == XCB_WINDOW_CLASS_INPUT_ONLY) { - // No need to manage this window, but we still keep it on the window stack - w->managed = false; - free(a); - return w; - } - - // Allocate and initialize the new win structure - auto new_internal = cmalloc(struct managed_win_internal); - auto new = (struct managed_win *)new_internal; - - // Fill structure - // We only need to initialize the part that are not initialized - // by map_win - *new = win_def; - new->base = *w; - new->base.managed = true; - new->a = *a; - pixman_region32_init(&new->bounding_shape); - - free(a); - - xcb_generic_error_t *e; - auto g = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, w->id), &e); - if (!g) { - log_error_x_error(e, "Failed to get geometry of window %#010x", w->id); - free(e); - free(new); - return w; - } - new->pending_g = (struct win_geometry){ - .x = g->x, - .y = g->y, - .width = g->width, - .height = g->height, - .border_width = g->border_width, - }; - - free(g); - - // Create Damage for window (if not Input Only) - new->damage = x_new_id(ps->c); - e = xcb_request_check( - ps->c, xcb_damage_create_checked(ps->c, new->damage, w->id, - XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY)); - if (e) { - log_error_x_error(e, "Failed to create damage"); - free(e); - free(new); - return w; - } - - // Set window event mask - xcb_change_window_attributes( - ps->c, new->base.id, XCB_CW_EVENT_MASK, - (const uint32_t[]){determine_evmask(ps, new->base.id, WIN_EVMODE_FRAME)}); - - // Get notification when the shape of a window changes - if (ps->shape_exists) { - xcb_shape_select_input(ps->c, new->base.id, 1); - } - - new->pictfmt = x_get_pictform_for_visual(ps->c, new->a.visual); - new->client_pictfmt = NULL; - - list_replace(&w->stack_neighbour, &new->base.stack_neighbour); - struct win *replaced = NULL; - HASH_REPLACE_INT(ps->windows, id, &new->base, replaced); - assert(replaced == w); - free(w); - - // Set all the stale flags on this new window, so it's properties will get updated - // when it's mapped - win_set_flags(new, WIN_FLAGS_CLIENT_STALE | WIN_FLAGS_SIZE_STALE | - WIN_FLAGS_POSITION_STALE | WIN_FLAGS_PROPERTY_STALE | - WIN_FLAGS_FACTOR_CHANGED); - xcb_atom_t init_stale_props[] = { - ps->atoms->a_NET_WM_WINDOW_TYPE, ps->atoms->a_NET_WM_WINDOW_OPACITY, - ps->atoms->a_NET_FRAME_EXTENTS, ps->atoms->aWM_NAME, - ps->atoms->a_NET_WM_NAME, ps->atoms->aWM_CLASS, - ps->atoms->aWM_WINDOW_ROLE, ps->atoms->a_COMPTON_SHADOW, - ps->atoms->aWM_CLIENT_LEADER, ps->atoms->aWM_TRANSIENT_FOR, - }; - win_set_properties_stale(new, init_stale_props, ARR_SIZE(init_stale_props)); - -#ifdef CONFIG_DBUS - // Send D-Bus signal - if (ps->o.dbus) { - cdbus_ev_win_added(ps, &new->base); - } -#endif - return &new->base; -} - -/** - * Set leader of a window. - */ -static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_window_t nleader) { - // If the leader changes - if (w->leader != nleader) { - xcb_window_t cache_leader_old = win_get_leader(ps, w); - - w->leader = nleader; - - // Forcefully do this to deal with the case when a child window - // gets mapped before parent, or when the window is a waypoint - clear_cache_win_leaders(ps); - - // Update the old and new window group and active_leader if the window - // could affect their state. - xcb_window_t cache_leader = win_get_leader(ps, w); - if (win_is_focused_raw(ps, w) && cache_leader_old != cache_leader) { - ps->active_leader = cache_leader; - - group_on_factor_change(ps, cache_leader_old); - group_on_factor_change(ps, cache_leader); - } - - // Update everything related to conditions - win_on_factor_change(ps, w); - } -} - -/** - * Update leader of a window. - */ -void win_update_leader(session_t *ps, struct managed_win *w) { - xcb_window_t leader = XCB_NONE; - - // Read the leader properties - if (ps->o.detect_transient && !leader) { - leader = - wid_get_prop_window(ps->c, w->client_win, ps->atoms->aWM_TRANSIENT_FOR); - } - - if (ps->o.detect_client_leader && !leader) { - leader = - wid_get_prop_window(ps->c, w->client_win, ps->atoms->aWM_CLIENT_LEADER); - } - - win_set_leader(ps, w, leader); - - log_trace("(%#010x): client %#010x, leader %#010x, cache %#010x", w->base.id, - w->client_win, w->leader, win_get_leader(ps, w)); -} - -/** - * Internal function of win_get_leader(). - */ -static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int recursions) { - // Rebuild the cache if needed - if (!w->cache_leader && (w->client_win || w->leader)) { - // Leader defaults to client window - if (!(w->cache_leader = w->leader)) - w->cache_leader = w->client_win; - - // If the leader of this window isn't itself, look for its ancestors - if (w->cache_leader && w->cache_leader != w->client_win) { - auto wp = find_toplevel(ps, w->cache_leader); - if (wp) { - // Dead loop? - if (recursions > WIN_GET_LEADER_MAX_RECURSION) - return XCB_NONE; - - w->cache_leader = win_get_leader_raw(ps, wp, recursions + 1); - } - } - } - - return w->cache_leader; -} - -/** - * Retrieve the <code>WM_CLASS</code> of a window and update its - * <code>win</code> structure. - */ -bool win_update_class(session_t *ps, struct managed_win *w) { - char **strlst = NULL; - int nstr = 0; - - // Can't do anything if there's no client window - if (!w->client_win) - return false; - - // Free and reset old strings - free(w->class_instance); - free(w->class_general); - w->class_instance = NULL; - w->class_general = NULL; - - // Retrieve the property string list - if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_CLASS, &strlst, &nstr)) { - return false; - } - - // Copy the strings if successful - w->class_instance = strdup(strlst[0]); - - if (nstr > 1) { - w->class_general = strdup(strlst[1]); - } - - free(strlst); - - log_trace("(%#010x): client = %#010x, " - "instance = \"%s\", general = \"%s\"", - w->base.id, w->client_win, w->class_instance, w->class_general); - - return true; -} - -/** - * Handle window focus change. - */ -static void win_on_focus_change(session_t *ps, struct managed_win *w) { - // If window grouping detection is enabled - if (ps->o.track_leader) { - xcb_window_t leader = win_get_leader(ps, w); - - // If the window gets focused, replace the old active_leader - if (win_is_focused_raw(ps, w) && leader != ps->active_leader) { - xcb_window_t active_leader_old = ps->active_leader; - - ps->active_leader = leader; - - group_on_factor_change(ps, active_leader_old); - group_on_factor_change(ps, leader); - } - // If the group get unfocused, remove it from active_leader - else if (!win_is_focused_raw(ps, w) && leader && - leader == ps->active_leader && !group_is_focused(ps, leader)) { - ps->active_leader = XCB_NONE; - group_on_factor_change(ps, leader); - } - } - - // Update everything related to conditions - win_on_factor_change(ps, w); - -#ifdef CONFIG_DBUS - // Send D-Bus signal - if (ps->o.dbus) { - if (win_is_focused_raw(ps, w)) { - cdbus_ev_win_focusin(ps, &w->base); - } else { - cdbus_ev_win_focusout(ps, &w->base); - } - } -#endif -} - -/** - * Set real focused state of a window. - */ -void win_set_focused(session_t *ps, struct managed_win *w) { - // Unmapped windows will have their focused state reset on map - if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { - return; - } - - if (win_is_focused_raw(ps, w)) { - return; - } - - auto old_active_win = ps->active_win; - ps->active_win = w; - assert(win_is_focused_raw(ps, w)); - - if (old_active_win) { - win_on_focus_change(ps, old_active_win); - } - win_on_focus_change(ps, w); -} - -/** - * Get a rectangular region 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) { - pixman_region32_clear(res); - pixman_region32_union_rect(res, res, w->g.x, w->g.y, (uint)w->widthb, (uint)w->heightb); - - if (w->shadow) { - assert(w->shadow_width >= 0 && w->shadow_height >= 0); - pixman_region32_union_rect(res, res, w->g.x + w->shadow_dx, - w->g.y + w->shadow_dy, (uint)w->shadow_width, - (uint)w->shadow_height); - } -} - -gen_by_val(win_extents); - -/** - * Update the out-dated bounding shape of a window. - * - * Mark the window shape as updated - */ -void win_update_bounding_shape(session_t *ps, struct managed_win *w) { - if (ps->shape_exists) { - w->bounding_shaped = win_bounding_shaped(ps, w->base.id); - } - - // We don't handle property updates of non-visible windows until they are mapped. - assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && - w->state != WSTATE_UNMAPPING); - - pixman_region32_clear(&w->bounding_shape); - // Start with the window rectangular region - win_get_region_local(w, &w->bounding_shape); - - // Only request for a bounding region if the window is shaped - // (while loop is used to avoid goto, not an actual loop) - while (w->bounding_shaped) { - /* - * if window doesn't exist anymore, this will generate an error - * as well as not generate a region. - */ - - xcb_shape_get_rectangles_reply_t *r = xcb_shape_get_rectangles_reply( - ps->c, - xcb_shape_get_rectangles(ps->c, w->base.id, XCB_SHAPE_SK_BOUNDING), NULL); - - if (!r) { - break; - } - - xcb_rectangle_t *xrects = xcb_shape_get_rectangles_rectangles(r); - int nrects = xcb_shape_get_rectangles_rectangles_length(r); - rect_t *rects = from_x_rects(nrects, xrects); - free(r); - - region_t br; - pixman_region32_init_rects(&br, rects, nrects); - free(rects); - - // Add border width because we are using a different origin. - // X thinks the top left of the inner window is the origin - // (for the bounding shape, althought xcb_get_geometry thinks - // the outer top left (outer means outside of the window - // border) is the origin), - // We think the top left of the border is the origin - pixman_region32_translate(&br, w->g.border_width, w->g.border_width); - - // Intersect the bounding region we got with the window rectangle, to - // make sure the bounding region is not bigger than the window - // rectangle - pixman_region32_intersect(&w->bounding_shape, &w->bounding_shape, &br); - pixman_region32_fini(&br); - break; - } - - if (w->bounding_shaped && ps->o.detect_rounded_corners) { - w->rounded_corners = win_has_rounded_corners(w); - } - - // Window shape changed, we should free old wpaint and shadow pict - // log_trace("free out dated pict"); - win_set_flags(w, WIN_FLAGS_IMAGES_STALE); - ps->pending_updates = true; - - free_paint(ps, &w->paint); - free_paint(ps, &w->shadow_paint); - - win_on_factor_change(ps, w); -} - -/** - * Reread opacity property of a window. - */ -void win_update_opacity_prop(session_t *ps, struct managed_win *w) { - // get frame opacity first - w->has_opacity_prop = wid_get_opacity_prop(ps, w->base.id, OPAQUE, &w->opacity_prop); - - if (w->has_opacity_prop) { - // opacity found - return; - } - - if (ps->o.detect_client_opacity && w->client_win && w->base.id == w->client_win) { - // checking client opacity not allowed - return; - } - - // get client opacity - w->has_opacity_prop = - wid_get_opacity_prop(ps, w->client_win, OPAQUE, &w->opacity_prop); -} - -/** - * Retrieve frame extents from a window. - */ -void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client) { - winprop_t prop = x_get_prop(ps->c, client, ps->atoms->a_NET_FRAME_EXTENTS, 4L, - XCB_ATOM_CARDINAL, 32); - - if (prop.nitems == 4) { - int extents[4]; - for (int i = 0; i < 4; i++) { - if (prop.c32[i] > (uint32_t)INT_MAX) { - log_warn("Your window manager sets a absurd " - "_NET_FRAME_EXTENTS value (%u), ignoring it.", - prop.c32[i]); - memset(extents, 0, sizeof(extents)); - break; - } - extents[i] = (int)prop.c32[i]; - } - - const bool changed = w->frame_extents.left != extents[0] || - w->frame_extents.right != extents[1] || - w->frame_extents.top != extents[2] || - w->frame_extents.bottom != extents[3]; - w->frame_extents.left = extents[0]; - w->frame_extents.right = extents[1]; - w->frame_extents.top = extents[2]; - w->frame_extents.bottom = extents[3]; - - // If frame_opacity != 1, then frame of this window - // is not included in reg_ignore of underneath windows - if (ps->o.frame_opacity == 1 && changed) { - w->reg_ignore_valid = false; - } - } - - log_trace("(%#010x): %d, %d, %d, %d", w->base.id, w->frame_extents.left, - w->frame_extents.right, w->frame_extents.top, w->frame_extents.bottom); - - free_winprop(&prop); -} - -bool win_is_region_ignore_valid(session_t *ps, const struct managed_win *w) { - win_stack_foreach_managed(i, &ps->window_stack) { - if (i == w) { - break; - } - if (!i->reg_ignore_valid) { - return false; - } - } - return true; -} - -/** - * Stop listening for events on a particular window. - */ -void win_ev_stop(session_t *ps, const struct win *w) { - xcb_change_window_attributes(ps->c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); - - if (!w->managed) { - return; - } - - auto mw = (struct managed_win *)w; - if (mw->client_win) { - xcb_change_window_attributes(ps->c, mw->client_win, XCB_CW_EVENT_MASK, - (const uint32_t[]){0}); - } - - if (ps->shape_exists) { - xcb_shape_select_input(ps->c, w->id, 0); - } -} - -/// Finish the unmapping of a window (e.g. after fading has finished). -/// Doesn't free `w` -static void unmap_win_finish(session_t *ps, struct managed_win *w) { - w->reg_ignore_valid = false; - w->state = WSTATE_UNMAPPED; - - // We are in unmap_win, this window definitely was viewable - if (ps->backend_data) { - // Only the pixmap needs to be freed and reacquired when mapping. - // Shadow image can be preserved. - if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { - win_release_pixmap(ps->backend_data, w); - win_release_oldpixmap(ps->backend_data, w); - } - } else { - assert(!w->win_image); - assert(!w->old_win_image); - assert(!w->shadow_image); - } - - // Force animation to completed position - w->animation_velocity_x = 0; - w->animation_velocity_y = 0; - w->animation_velocity_w = 0; - w->animation_velocity_h = 0; - w->animation_progress = 1.0; - - free_paint(ps, &w->paint); - free_paint(ps, &w->shadow_paint); - - // Try again at binding images when the window is mapped next time - win_clear_flags(w, WIN_FLAGS_IMAGE_ERROR); - - // Flag window so that it gets animated when it reapears - // in case it wasn't destroyed - win_set_flags(w, WIN_FLAGS_POSITION_STALE); - win_set_flags(w, WIN_FLAGS_SIZE_STALE); -} - -/// Finish the destruction of a window (e.g. after fading has finished). -/// Frees `w` -static void destroy_win_finish(session_t *ps, struct win *w) { - log_trace("Trying to finish destroying (%#010x)", w->id); - - auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour); - list_remove(&w->stack_neighbour); - - if (w->managed) { - auto mw = (struct managed_win *)w; - - if (mw->state != WSTATE_UNMAPPED) { - // Only UNMAPPED state has window resources freed, otherwise - // we need to call unmap_win_finish to free them. - // XXX actually we unmap_win_finish only frees the rendering - // resources, we still need to call free_win_res. will fix - // later. - unmap_win_finish(ps, mw); - } - - // Unmapping preserves the shadow image, so free it here - if (!win_check_flags_all(mw, WIN_FLAGS_SHADOW_NONE)) { - assert(mw->shadow_image != NULL); - win_release_shadow(ps->backend_data, mw); - } - - // Invalidate reg_ignore of windows below this one - // TODO(yshui) what if next_w is not mapped?? - /* TODO(yshui) seriously figure out how reg_ignore behaves. - * I think if `w` is unmapped, and destroyed after - * paint happened at least once, w->reg_ignore_valid would - * be true, and there is no need to invalid w->next->reg_ignore - * when w is destroyed. */ - if (next_w) { - rc_region_unref(&next_w->reg_ignore); - next_w->reg_ignore_valid = false; - } - - if (mw == ps->active_win) { - // Usually, the window cannot be the focused at destruction. - // FocusOut should be generated before the window is destroyed. We - // do this check just to be completely sure we don't have dangling - // references. - log_debug("window %#010x (%s) is destroyed while being focused", - w->id, mw->name); - ps->active_win = NULL; - } - - free_win_res(ps, mw); - - // Drop w from all prev_trans to avoid accessing freed memory in - // repair_win() - // TODO(yshui) there can only be one prev_trans pointing to w - win_stack_foreach_managed(w2, &ps->window_stack) { - if (mw == w2->prev_trans) { - w2->prev_trans = NULL; - } - } - } - - free(w); -} - -static void map_win_finish(struct managed_win *w) { - w->in_openclose = false; - w->state = WSTATE_MAPPED; -} - -/// Move window `w` so it's before `next` in the list -static inline void restack_win(session_t *ps, struct win *w, struct list_node *next) { - struct managed_win *mw = NULL; - if (w->managed) { - mw = (struct managed_win *)w; - } - - if (mw) { - // This invalidates all reg_ignore below the new stack position of `w` - mw->reg_ignore_valid = false; - rc_region_unref(&mw->reg_ignore); - - // This invalidates all reg_ignore below the old stack position of `w` - auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour); - if (next_w) { - next_w->reg_ignore_valid = false; - rc_region_unref(&next_w->reg_ignore); - } - } - - list_move_before(&w->stack_neighbour, next); - - // add damage for this window - if (mw) { - add_damage_from_win(ps, mw); - } - -#ifdef DEBUG_RESTACK - log_trace("Window stack modified. Current stack:"); - for (auto c = ps->list; c; c = c->next) { - const char *desc = ""; - if (c->state == WSTATE_DESTROYING) { - desc = "(D) "; - } - log_trace("%#010x \"%s\" %s", c->id, c->name, desc); - } -#endif -} - -/// Move window `w` so it's right above `below` -void restack_above(session_t *ps, struct win *w, xcb_window_t below) { - xcb_window_t old_below; - - if (!list_node_is_last(&ps->window_stack, &w->stack_neighbour)) { - old_below = list_next_entry(w, stack_neighbour)->id; - } else { - old_below = XCB_NONE; - } - log_debug("Restack %#010x (%s), old_below: %#010x, new_below: %#010x", w->id, - win_get_name_if_managed(w), old_below, below); - - if (old_below != below) { - struct list_node *new_next; - if (!below) { - new_next = &ps->window_stack; - } else { - struct win *tmp_w = NULL; - HASH_FIND_INT(ps->windows, &below, tmp_w); - - if (!tmp_w) { - log_error("Failed to found new below window %#010x.", below); - return; - } - - new_next = &tmp_w->stack_neighbour; - } - restack_win(ps, w, new_next); - } -} - -void restack_bottom(session_t *ps, struct win *w) { - restack_above(ps, w, 0); -} - -void restack_top(session_t *ps, struct win *w) { - log_debug("Restack %#010x (%s) to top", w->id, win_get_name_if_managed(w)); - if (&w->stack_neighbour == ps->window_stack.next) { - // already at top - return; - } - restack_win(ps, w, ps->window_stack.next); -} - -/// Start destroying a window. Windows cannot always be destroyed immediately -/// because of fading and such. -/// -/// @return whether the window has finished destroying and is freed -bool destroy_win_start(session_t *ps, struct win *w) { - auto mw = (struct managed_win *)w; - assert(w); - - log_debug("Destroying %#010x \"%s\", managed = %d", w->id, - (w->managed ? mw->name : NULL), w->managed); - - // Delete destroyed window from the hash table, even though the window might still - // be rendered for a while. We need to make sure future window with the same - // window id won't confuse us. Keep the window in the window stack if it's managed - // and mapped, since we might still need to render it (e.g. fading out). Window - // will be removed from the stack when it finishes destroying. - HASH_DEL(ps->windows, w); - - if (!w->managed || mw->state == WSTATE_UNMAPPED) { - // Window is already unmapped, or is an unmanged window, just destroy it - destroy_win_finish(ps, w); - return true; - } - - if (w->managed) { - // Clear IMAGES_STALE flags since the window is destroyed: Clear - // PIXMAP_STALE as there is no pixmap available anymore, so STALE doesn't - // make sense. - // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed window - // doesn't work leading to an inconsistent state where the shadow is - // refreshed but the flags are stuck in STALE. - // Do this before changing the window state to destroying - win_clear_flags(mw, WIN_FLAGS_IMAGES_STALE); - - // If size/shape/position information is stale, win_process_update_flags - // will update them and add the new window extents to damage. Since the - // window has been destroyed, we cannot get the complete information at - // this point, so we just add what we currently have to the damage. - if (win_check_flags_any(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) { - add_damage_from_win(ps, mw); - } - - // Clear some flags about stale window information. Because now the window - // is destroyed, we can't update them anyway. - win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE | - WIN_FLAGS_PROPERTY_STALE | - WIN_FLAGS_FACTOR_CHANGED | WIN_FLAGS_CLIENT_STALE); - - // Update state flags of a managed window - mw->state = WSTATE_DESTROYING; - mw->a.map_state = XCB_MAP_STATE_UNMAPPED; - mw->in_openclose = true; - } - - // don't need win_ev_stop because the window is gone anyway -#ifdef CONFIG_DBUS - // Send D-Bus signal - if (ps->o.dbus) { - cdbus_ev_win_destroyed(ps, w); - } -#endif - - if (!ps->redirected) { - // Skip transition if we are not rendering - return win_skip_fading(ps, mw); - } - - return false; -} - -void unmap_win_start(session_t *ps, struct managed_win *w) { - assert(w); - assert(w->base.managed); - assert(w->a._class != XCB_WINDOW_CLASS_INPUT_ONLY); - - log_debug("Unmapping %#010x \"%s\"", w->base.id, w->name); - - if (unlikely(w->state == WSTATE_DESTROYING)) { - log_warn("Trying to undestroy a window?"); - assert(false); - } - - bool was_damaged = w->ever_damaged; - w->ever_damaged = false; - - if (unlikely(w->state == WSTATE_UNMAPPING || w->state == WSTATE_UNMAPPED)) { - if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) { - // Clear the pending map as this window is now unmapped - win_clear_flags(w, WIN_FLAGS_MAPPED); - } else { - log_warn("Trying to unmapping an already unmapped window %#010x " - "\"%s\"", - w->base.id, w->name); - assert(false); - } - return; - } - - // Note we don't update focused window here. This will either be - // triggered by subsequence Focus{In, Out} event, or by recheck_focus - - w->a.map_state = XCB_MAP_STATE_UNMAPPED; - w->state = WSTATE_UNMAPPING; - w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old); - w->opacity_target = win_calc_opacity_target(ps, w); - - if (ps->o.animations && - ps->o.animation_for_unmap_window != OPEN_WINDOW_ANIMATION_NONE && - ps->o.wintype_option[w->window_type].animation != 0) - { - init_animation_unmap(ps, w); - - double x_dist = w->animation_dest_center_x - w->animation_center_x; - double y_dist = w->animation_dest_center_y - w->animation_center_y; - double w_dist = w->animation_dest_w - w->animation_w; - double h_dist = w->animation_dest_h - w->animation_h; - w->animation_inv_og_distance = - 1.0 / sqrt(x_dist * x_dist + y_dist * y_dist + - w_dist * w_dist + h_dist * h_dist); - - if (isinf(w->animation_inv_og_distance)) - w->animation_inv_og_distance = 0; - - w->animation_progress = 0.0; - - if (w->old_win_image) { - ps->backend_data->ops->release_image(ps->backend_data, - w->old_win_image); - w->old_win_image = NULL; - } - } - -#ifdef CONFIG_DBUS - // Send D-Bus signal - if (ps->o.dbus) { - cdbus_ev_win_unmapped(ps, &w->base); - } -#endif - - if (!ps->redirected || !was_damaged) { - // If we are not redirected, we skip fading because we aren't rendering - // anything anyway. - // If the window wasn't ever damaged, it shouldn't be painted either. But - // a fading out window is always painted, so we have to skip fading here. - CHECK(!win_skip_fading(ps, w)); - } -} - -/** - * Execute fade callback of a window if fading finished. - * - * @return whether the window is destroyed and freed - */ -bool win_check_fade_finished(session_t *ps, struct managed_win *w) { - if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) { - // No fading in progress - assert(w->opacity_target == w->opacity); - return false; - } - - if (w->opacity == w->opacity_target) { - switch (w->state) { - case WSTATE_UNMAPPING: unmap_win_finish(ps, w); return false; - case WSTATE_DESTROYING: destroy_win_finish(ps, &w->base); return true; - case WSTATE_MAPPING: map_win_finish(w); return false; - case WSTATE_FADING: w->state = WSTATE_MAPPED; break; - default: unreachable; - } - } - - return false; -} - -/// 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 win_skip_fading(session_t *ps, struct managed_win *w) { - if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) { - assert(w->opacity_target == w->opacity); - return false; - } - log_debug("Skipping fading process of window %#010x (%s)", w->base.id, w->name); - w->opacity = w->opacity_target; - - if (w->animation_progress < 1) { - w->animation_progress = 1; - w->g.x = w->pending_g.x; - w->g.y = w->pending_g.y; - w->g.width = w->pending_g.width; - w->g.height = w->pending_g.height; - } - - return win_check_fade_finished(ps, w); -} - -/** - * Get the Xinerama screen a window is on. - * - * Return an index >= 0, or -1 if not found. - * - * TODO(yshui) move to x.c - * TODO(yshui) use xrandr - */ -void win_update_screen(int nscreens, region_t *screens, struct managed_win *w) { - w->xinerama_scr = -1; - - for (int i = 0; i < nscreens; i++) { - auto e = pixman_region32_extents(&screens[i]); - if (e->x1 <= w->g.x && e->y1 <= w->g.y && e->x2 >= w->g.x + w->widthb && - e->y2 >= w->g.y + w->heightb) { - w->xinerama_scr = i; - log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen %d " - "(%dx%d+%dx%d)", - w->base.id, w->name, w->g.x, w->g.y, w->widthb, w->heightb, - i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1); - return; - } - } - log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any screen", - w->base.id, w->name, w->g.x, w->g.y, w->g.width, w->g.height); -} - -/// Map an already registered window -void map_win_start(session_t *ps, struct managed_win *w) { - assert(ps->server_grabbed); - assert(w); - - // Don't care about window mapping if it's an InputOnly window - // Also, try avoiding mapping a window twice - if (w->a._class == XCB_WINDOW_CLASS_INPUT_ONLY) { - return; - } - - log_debug("Mapping (%#010x \"%s\")", w->base.id, w->name); - - assert(w->state != WSTATE_DESTROYING); - if (w->state != WSTATE_UNMAPPED && w->state != WSTATE_UNMAPPING) { - log_warn("Mapping an already mapped window"); - return; - } - - if (w->state == WSTATE_UNMAPPING) { - CHECK(!win_skip_fading(ps, w)); - // We skipped the unmapping process, the window was rendered, now it is - // not anymore. So we need to mark the then unmapping window as damaged. - // - // Solves problem when, for example, a window is unmapped then mapped in a - // different location - add_damage_from_win(ps, w); - assert(w); - } - - assert(w->state == WSTATE_UNMAPPED); - - // Rant: window size could change after we queried its geometry here and before - // we get its pixmap. Later, when we get back to the event processing loop, we - // will get the notification about size change from Xserver and try to refresh the - // pixmap, while the pixmap is actually already up-to-date (i.e. the notification - // is stale). There is basically no real way to prevent this, aside from grabbing - // the server. - - // XXX Can we assume map_state is always viewable? - w->a.map_state = XCB_MAP_STATE_VIEWABLE; - - // Update window mode here to check for ARGB windows - w->mode = win_calc_mode(w); - - log_debug("Window (%#010x) has type %s", w->base.id, WINTYPES[w->window_type]); - - // XXX We need to make sure that win_data is available - // iff `state` is MAPPED - w->state = WSTATE_MAPPING; - w->opacity_target_old = 0; - w->opacity_target = win_calc_opacity_target(ps, w); - - log_debug("Window %#010x has opacity %f, opacity target is %f", w->base.id, - w->opacity, w->opacity_target); - - // Cannot set w->ever_damaged = false here, since window mapping could be - // delayed, so a damage event might have already arrived before this function - // is called. But this should be unnecessary in the first place, since - // ever_damaged is set to false in unmap_win_finish anyway. - - // Sets the WIN_FLAGS_IMAGES_STALE flag so later in the critical section - // the window's image will be bound - - win_set_flags(w, WIN_FLAGS_PIXMAP_STALE); - -#ifdef CONFIG_DBUS - // Send D-Bus signal - if (ps->o.dbus) { - cdbus_ev_win_mapped(ps, &w->base); - } -#endif - - if (!ps->redirected) { - CHECK(!win_skip_fading(ps, w)); - } -} - -/** - * Update target window opacity depending on the current state. - */ -void win_update_opacity_target(session_t *ps, struct managed_win *w) { - auto opacity_target_old = w->opacity_target; - w->opacity_target = win_calc_opacity_target(ps, w); - - if (opacity_target_old == w->opacity_target) { - return; - } - - if (w->state == WSTATE_MAPPED) { - // Opacity target changed while MAPPED. Transition to FADING. - assert(w->opacity == opacity_target_old); - w->opacity_target_old = opacity_target_old; - w->state = WSTATE_FADING; - log_debug("Window %#010x (%s) opacity %f, opacity target %f, set " - "old target %f", - w->base.id, w->name, w->opacity, w->opacity_target, - w->opacity_target_old); - } else if (w->state == WSTATE_MAPPING) { - // Opacity target changed while fading in. - if (w->opacity >= w->opacity_target) { - // Already reached new target opacity. Transition to - // FADING. - map_win_finish(w); - w->opacity_target_old = fmax(opacity_target_old, w->opacity); - w->state = WSTATE_FADING; - log_debug("Window %#010x (%s) opacity %f already reached " - "new opacity target %f while mapping, set old " - "target %f", - w->base.id, w->name, w->opacity, w->opacity_target, - w->opacity_target_old); - } - } else if (w->state == WSTATE_FADING) { - // Opacity target changed while FADING. - if ((w->opacity < opacity_target_old && w->opacity > w->opacity_target) || - (w->opacity > opacity_target_old && w->opacity < w->opacity_target)) { - // Changed while fading in and will fade out or while - // fading out and will fade in. - w->opacity_target_old = opacity_target_old; - log_debug("Window %#010x (%s) opacity %f already reached " - "new opacity target %f while fading, set " - "old target %f", - w->base.id, w->name, w->opacity, w->opacity_target, - w->opacity_target_old); - } - } - - if (!ps->redirected) { - CHECK(!win_skip_fading(ps, w)); - } -} - -/** - * Find a managed window from window id in window linked list of the session. - */ -struct win *find_win(session_t *ps, xcb_window_t id) { - if (!id) { - return NULL; - } - - struct win *w = NULL; - HASH_FIND_INT(ps->windows, &id, w); - assert(w == NULL || !w->destroyed); - return 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 *w = find_win(ps, id); - if (!w || !w->managed) { - return NULL; - } - - auto mw = (struct managed_win *)w; - assert(mw->state != WSTATE_DESTROYING); - return mw; -} - -/** - * Find out the WM frame of a client window using existing data. - * - * @param id window ID - * @return struct win object of the found window, NULL if not found - */ -struct managed_win *find_toplevel(session_t *ps, xcb_window_t id) { - if (!id) { - return NULL; - } - - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - if (!w->managed) { - continue; - } - - auto mw = (struct managed_win *)w; - if (mw->client_win == id) { - return mw; - } - } - - return NULL; -} - -/** - * 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) { - // TODO(yshui) this should probably be an "update tree", then find_toplevel. - // current approach is a bit more "racy", as the server state might be ahead of - // our state - struct win *w = NULL; - - // We traverse through its ancestors to find out the frame - // Using find_win here because if we found a unmanaged window we know about, we - // can stop early. - while (wid && wid != ps->root && !(w = find_win(ps, wid))) { - // xcb_query_tree probably fails if you run picom when X is somehow - // initializing (like add it in .xinitrc). In this case - // just leave it alone. - auto reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL); - if (reply == NULL) { - break; - } - - wid = reply->parent; - free(reply); - } - - if (w == NULL || !w->managed) { - return NULL; - } - - return (struct managed_win *)w; -} - -/** - * Check if a rectangle includes the whole screen. - */ -static inline bool rect_is_fullscreen(const session_t *ps, int x, int y, int wid, int hei) { - return (x <= 0 && y <= 0 && (x + wid) >= ps->root_width && (y + hei) >= ps->root_height); -} - -/** - * Check if a window is fulscreen using EWMH - * - * TODO(yshui) cache this property - */ -static inline bool -win_is_fullscreen_xcb(xcb_connection_t *c, const struct atom *a, const xcb_window_t w) { - xcb_get_property_cookie_t prop = - xcb_get_property(c, 0, w, a->a_NET_WM_STATE, XCB_ATOM_ATOM, 0, 12); - xcb_get_property_reply_t *reply = xcb_get_property_reply(c, prop, NULL); - if (!reply) { - return false; - } - - if (reply->length) { - xcb_atom_t *val = xcb_get_property_value(reply); - for (uint32_t i = 0; i < reply->length; i++) { - if (val[i] != a->a_NET_WM_STATE_FULLSCREEN) { - continue; - } - free(reply); - return true; - } - } - free(reply); - return false; -} - -/// Set flags on a window. Some sanity checks are performed -void win_set_flags(struct managed_win *w, uint64_t flags) { - log_debug("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name); - if (unlikely(w->state == WSTATE_DESTROYING)) { - log_error("Flags set on a destroyed window %#010x (%s)", w->base.id, w->name); - return; - } - - w->flags |= flags; -} - -/// Clear flags on a window. Some sanity checks are performed -void win_clear_flags(struct managed_win *w, uint64_t flags) { - log_debug("Clear flags %" PRIu64 " from window %#010x (%s)", flags, w->base.id, - w->name); - if (unlikely(w->state == WSTATE_DESTROYING)) { - log_warn("Flags cleared on a destroyed window %#010x (%s)", w->base.id, - w->name); - return; - } - - w->flags = w->flags & (~flags); -} - -void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *props, int nprops) { - const auto bits_per_element = sizeof(*w->stale_props) * 8; - size_t new_capacity = w->stale_props_capacity; - - // Calculate the new capacity of the properties array - for (int i = 0; i < nprops; i++) { - if (props[i] >= new_capacity * bits_per_element) { - new_capacity = props[i] / bits_per_element + 1; - } - } - - // Reallocate if necessary - if (new_capacity > w->stale_props_capacity) { - w->stale_props = - realloc(w->stale_props, new_capacity * sizeof(*w->stale_props)); - - // Clear the content of the newly allocated bytes - memset(w->stale_props + w->stale_props_capacity, 0, - (new_capacity - w->stale_props_capacity) * sizeof(*w->stale_props)); - w->stale_props_capacity = new_capacity; - } - - // Set the property bits - for (int i = 0; i < nprops; i++) { - w->stale_props[props[i] / bits_per_element] |= - 1UL << (props[i] % bits_per_element); - } - win_set_flags(w, WIN_FLAGS_PROPERTY_STALE); -} - -static void win_clear_all_properties_stale(struct managed_win *w) { - memset(w->stale_props, 0, w->stale_props_capacity * sizeof(*w->stale_props)); - win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE); -} - -static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop) { - const auto bits_per_element = sizeof(*w->stale_props) * 8; - if (prop >= w->stale_props_capacity * bits_per_element) { - return false; - } - - const auto mask = 1UL << (prop % bits_per_element); - bool ret = w->stale_props[prop / bits_per_element] & mask; - w->stale_props[prop / bits_per_element] &= ~mask; - return ret; -} - -bool win_check_flags_any(struct managed_win *w, uint64_t flags) { - return (w->flags & flags) != 0; -} - -bool win_check_flags_all(struct managed_win *w, uint64_t flags) { - return (w->flags & flags) == flags; -} - -/** - * Check if a window is a fullscreen window. - * - * It's not using w->border_size for performance measures. - */ -bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) { - if (!ps->o.no_ewmh_fullscreen && - win_is_fullscreen_xcb(ps->c, ps->atoms, w->client_win)) { - return true; - } - return rect_is_fullscreen(ps, w->g.x, w->g.y, w->widthb, w->heightb) && - (!w->bounding_shaped || w->rounded_corners); -} - -/** - * Check if a window has BYPASS_COMPOSITOR property set - * - * TODO(yshui) cache this property - */ -bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *w) { - bool ret = false; - - auto prop = x_get_prop(ps->c, w->client_win, ps->atoms->a_NET_WM_BYPASS_COMPOSITOR, - 1L, XCB_ATOM_CARDINAL, 32); - - if (prop.nitems && *prop.c32 == 1) { - ret = true; - } - - free_winprop(&prop); - return ret; -} - -/** - * Check if a window is focused, without using any focus rules or forced focus settings - */ -bool win_is_focused_raw(const session_t *ps, const struct managed_win *w) { - return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w; -} - -// Find the managed window immediately below `i` in the window stack -struct managed_win * -win_stack_find_next_managed(const session_t *ps, const struct list_node *i) { - while (!list_node_is_last(&ps->window_stack, i)) { - auto next = list_entry(i->next, struct win, stack_neighbour); - if (next->managed) { - return (struct managed_win *)next; - } - i = &next->stack_neighbour; - } - return NULL; -} - -/// Return whether this window is mapped on the X server side -bool win_is_mapped_in_x(const struct managed_win *w) { - return w->state == WSTATE_MAPPING || w->state == WSTATE_FADING || - w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPING || - w->state == WSTATE_DESTROYING || (w->flags & WIN_FLAGS_MAPPED); -} diff --git a/src/win.h b/src/win.h deleted file mode 100644 index d3a74f9..0000000 --- a/src/win.h +++ /dev/null @@ -1,531 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2011-2013, Christopher Jeffrey -// Copyright (c) 2013 Richard Grenville <[email protected]> -#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 - uint64_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 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_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 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; -} diff --git a/src/win_defs.h b/src/win_defs.h deleted file mode 100644 index e032bc7..0000000 --- a/src/win_defs.h +++ /dev/null @@ -1,102 +0,0 @@ -#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) diff --git a/src/x.c b/src/x.c deleted file mode 100644 index c146f48..0000000 --- a/src/x.c +++ /dev/null @@ -1,748 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018 Yuxuan Shui <[email protected]> -#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; -} - -// xcb-render specific macros -#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536) -#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) - -/** - * Convert a struct conv to a X picture convolution filter, normalizing the kernel - * in the process. Allow the caller to specify the element at the center of the kernel, - * 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; -} diff --git a/src/x.h b/src/x.h deleted file mode 100644 index e01aa0a..0000000 --- a/src/x.h +++ /dev/null @@ -1,293 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018 Yuxuan Shui <[email protected]> -#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)) - -/// 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); diff --git a/src/xrescheck.c b/src/xrescheck.c deleted file mode 100644 index 1785fc8..0000000 --- a/src/xrescheck.c +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2014 Richard Grenville <[email protected]> - -#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); - } -} diff --git a/src/xrescheck.h b/src/xrescheck.h deleted file mode 100644 index 5ad5c46..0000000 --- a/src/xrescheck.h +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2014 Richard Grenville <[email protected]> -#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); |