aboutsummaryrefslogtreecommitdiff
path: root/src/compfy.c
diff options
context:
space:
mode:
authorallusive-dev <[email protected]>2023-11-15 15:13:38 +1100
committerallusive-dev <[email protected]>2023-11-15 15:13:38 +1100
commitf481ea09f9f69c96575662d7b67d290f380aee83 (patch)
tree9155652619c26d1a028ddc63eb7a0c29094dd0d3 /src/compfy.c
parentUpdate README.md (diff)
downloadcompfy-f481ea09f9f69c96575662d7b67d290f380aee83.tar.xz
compfy-f481ea09f9f69c96575662d7b67d290f380aee83.zip
merge with compfy again lol1.6.0
Diffstat (limited to 'src/compfy.c')
-rw-r--r--src/compfy.c2823
1 files changed, 2823 insertions, 0 deletions
diff --git a/src/compfy.c b/src/compfy.c
new file mode 100644
index 0000000..87dc603
--- /dev/null
+++ b/src/compfy.c
@@ -0,0 +1,2823 @@
+// SPDX-License-Identifier: MIT
+
+#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 "compfy.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 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",
+ [BKEND_EGL] = "egl",
+ 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);
+ }
+
+ HASH_ITER2(ps->shaders, shader) {
+ if (shader->backend_shader != NULL) {
+ ps->backend_data->ops->destroy_shader(ps->backend_data,
+ shader->backend_shader);
+ shader->backend_shader = NULL;
+ }
+ }
+
+ if (ps->backend_data && ps->root_image) {
+ ps->backend_data->ops->release_image(ps->backend_data, ps->root_image);
+ ps->root_image = NULL;
+ }
+
+ 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;
+ }
+ if (ps->shadow_context) {
+ ps->backend_data->ops->destroy_shadow_context(ps->backend_data,
+ ps->shadow_context);
+ ps->shadow_context = NULL;
+ }
+ ps->backend_data->ops->deinit(ps->backend_data);
+ ps->backend_data = NULL;
+ }
+}
+
+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.legacy_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];
+ ps->shadow_context = ps->backend_data->ops->create_shadow_context(
+ ps->backend_data, ps->o.shadow_radius);
+ if (!ps->shadow_context) {
+ log_fatal("Failed to initialize shadow context, aborting...");
+ goto err;
+ }
+
+ if (!initialize_blur(ps)) {
+ log_fatal("Failed to prepare for background blur, aborting...");
+ goto err;
+ }
+
+ // Create shaders
+ HASH_ITER2(ps->shaders, shader) {
+ assert(shader->backend_shader == NULL);
+ shader->backend_shader = ps->backend_data->ops->create_shader(
+ ps->backend_data, shader->source);
+ if (shader->backend_shader == NULL) {
+ log_warn("Failed to create shader for shader file %s, "
+ "this shader will not be used",
+ shader->key);
+ } else {
+ if (ps->backend_data->ops->get_shader_attributes) {
+ shader->attributes =
+ ps->backend_data->ops->get_shader_attributes(
+ ps->backend_data, shader->backend_shader);
+ } else {
+ shader->attributes = 0;
+ }
+ log_debug("Shader %s has attributes %" PRIu64,
+ shader->key, shader->attributes);
+ }
+ }
+
+ // window_stack shouldn't include window that's
+ // 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;
+err:
+ if (ps->shadow_context) {
+ ps->backend_data->ops->destroy_shadow_context(ps->backend_data,
+ ps->shadow_context);
+ ps->shadow_context = NULL;
+ }
+ ps->backend_data->ops->deinit(ps->backend_data);
+ ps->backend_data = NULL;
+ quit(ps);
+ return false;
+}
+
+/// Handle configure event of the root window
+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.legacy_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.legacy_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);
+ }
+ 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
+ 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, and animated shaders
+ // TODO(yshui) check if a window is fully obscured, and if we don't need to
+ // process fading or animation for it.
+ win_stack_foreach_managed_safe(w, &ps->window_stack) {
+ const winmode_t mode_old = w->mode;
+ const bool was_painted = w->to_paint;
+ 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);
+
+ // if (ps->o.support_for_wm == WM_SUPPORT_AWESOME) {
+ // win_update_bounding_shape(ps, w);
+ // } else if (ps->o.support_for_wm == WM_SUPPORT_HERB) {
+ // win_update_bounding_shape(ps, w);
+ // } else {
+ // 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 (ps->o.support_for_wm == WM_SUPPORT_LEGACY) {
+ 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);
+ } else {
+ win_update_bounding_shape(ps, w);
+ }
+
+ 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 (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->fg_shader && (w->fg_shader->attributes & SHADER_ATTRIBUTE_ANIMATED)) {
+ add_damage_from_win(ps, w);
+ *animation_running = true;
+ }
+
+ 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->transparent_clipping_excluded)) {
+ // 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);
+ if (ps->root_image) {
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE,
+ ps->root_image, (int[]){ps->root_width, ps->root_height});
+ } else {
+ log_error("Failed to bind root back pixmap");
+ }
+ }
+ }
+
+ // 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("compfy"), "compfy"));
+ if (e) {
+ log_error_x_error(e, "Failed to set window property %d",
+ prop_atoms[i]);
+ free(e);
+ }
+ }
+
+ const char compfy_class[] = "compfy\0compfy";
+ 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(compfy_class), compfy_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(COMPFY_VERSION), COMPFY_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;
+}
+
+/**
+ * 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.legacy_backends);
+ return XCB_COMPOSITE_REDIRECT_AUTOMATIC;
+ }
+ if (!ps->o.legacy_backends && !backend_list[ps->o.backend]->present) {
+ // if the backend doesn't render anything, we don't need to take over the
+ // screen.
+ return XCB_COMPOSITE_REDIRECT_AUTOMATIC;
+ }
+ 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.legacy_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.legacy_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) {
+ 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, or if
+ // draw_callback_impl thinks we should continue painting.
+ if (!ps->o.benchmark && !ps->redraw_needed) {
+ ev_idle_stop(EV_A_ & ps->draw_idle);
+ }
+}
+
+static void 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("Compfy 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("Compfy is quitting...");
+ quit(ps);
+}
+
+static void config_file_change_cb(void *_ps) {
+ auto ps = (struct session *)_ps;
+ reset_enable(ps->loop, NULL, 0);
+}
+
+static bool load_shader_source(session_t *ps, const char *path) {
+ if (!path) {
+ // Using the default shader.
+ return false;
+ }
+
+ log_info("Loading shader source from %s", path);
+
+ struct shader_info *shader = NULL;
+ HASH_FIND_STR(ps->shaders, path, shader);
+ if (shader) {
+ log_debug("Shader already loaded, reusing");
+ return false;
+ }
+
+ shader = ccalloc(1, struct shader_info);
+ shader->key = strdup(path);
+ HASH_ADD_KEYPTR(hh, ps->shaders, shader->key, strlen(shader->key), shader);
+
+ FILE *f = fopen(path, "r");
+ if (!f) {
+ log_error("Failed to open custom shader file: %s", path);
+ goto err;
+ }
+ struct stat statbuf;
+ if (fstat(fileno(f), &statbuf) < 0) {
+ log_error("Failed to access custom shader file: %s", path);
+ goto err;
+ }
+
+ auto num_bytes = (size_t)statbuf.st_size;
+ shader->source = ccalloc(num_bytes + 1, char);
+ auto read_bytes = fread(shader->source, sizeof(char), num_bytes, f);
+ if (read_bytes < num_bytes || ferror(f)) {
+ // This is a difficult to hit error case, review thoroughly.
+ log_error("Failed to read custom shader at %s. (read %lu bytes, expected "
+ "%lu bytes)",
+ path, read_bytes, num_bytes);
+ goto err;
+ }
+ return false;
+err:
+ HASH_DEL(ps->shaders, shader);
+ if (f) {
+ fclose(f);
+ }
+ free(shader->source);
+ free(shader->key);
+ free(shader);
+ return true;
+}
+
+static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data) {
+ return load_shader_source(data, c2_list_get_data(cond));
+}
+
+/**
+ * Initialize a session.
+ *
+ * @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,
+ .shadow_context = NULL,
+
+#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.window_shader_fg) {
+ log_debug("Default window shader: \"%s\"", ps->o.window_shader_fg);
+ }
+
+ if (ps->o.logpath) {
+ auto l = file_logger_new(ps->o.logpath);
+ if (l) {
+ 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 \"compfy\", the \"picom\" "
+ "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.window_shader_fg_rules) &&
+ c2_list_postprocess(ps, ps->o.opacity_rules) &&
+ c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) &&
+ c2_list_postprocess(ps, ps->o.corner_rules) &&
+ c2_list_postprocess(ps, ps->o.blur_rules) &&
+ c2_list_postprocess(ps, ps->o.animation_open_blacklist) &&
+ c2_list_postprocess(ps, ps->o.animation_unmap_blacklist) &&
+ c2_list_postprocess(ps, ps->o.active_opacity_blacklist) &&
+ c2_list_postprocess(ps, ps->o.inactive_opacity_blacklist) &&
+ c2_list_postprocess(ps, ps->o.focus_blacklist))) {
+ log_error("Post-processing of conditionals failed, some of your rules "
+ "might not work");
+ }
+
+ // Load shader source file specified in the shader rules
+ if (c2_list_foreach(ps->o.window_shader_fg_rules, load_shader_source_for_condition, ps)) {
+ log_error("Failed to load shader source file for some of the window "
+ "shader rules");
+ }
+ if (load_shader_source(ps, ps->o.window_shader_fg)) {
+ log_error("Failed to load window shader source file");
+ }
+
+ if (log_get_level_tls() <= LOG_LEVEL_DEBUG) {
+ HASH_ITER2(ps->shaders, shader) {
+ log_debug("Shader %s:", shader->key);
+ log_debug("%s", shader->source);
+ }
+ }
+
+ if (ps->o.legacy_backends) {
+ ps->shadow_context =
+ (void *)gaussian_kernel_autodetect_deviation(ps->o.shadow_radius);
+ sum_kernel_preprocess((conv *)ps->shadow_context);
+ }
+
+ rebuild_shadow_exclude_reg(ps);
+
+ // 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.xinerama_shadow_crop) {
+ if (!ps->randr_exists) {
+ log_fatal("No XRandR extension. 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.legacy_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.legacy_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.legacy_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.legacy_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;
+ }
+ }
+
+ // 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.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);
+ ev_idle_init(&ps->draw_idle, draw_callback);
+
+ ev_init(&ps->fade_timer, fade_timer_callback);
+ ev_init(&ps->animation_timer, animation_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
+ c2_list_free(&ps->o.shadow_blacklist, NULL);
+ c2_list_free(&ps->o.shadow_clip_list, NULL);
+ c2_list_free(&ps->o.fade_blacklist, NULL);
+ c2_list_free(&ps->o.focus_blacklist, NULL);
+ c2_list_free(&ps->o.invert_color_list, NULL);
+ c2_list_free(&ps->o.blur_background_blacklist, NULL);
+ c2_list_free(&ps->o.opacity_rules, NULL);
+ c2_list_free(&ps->o.paint_blacklist, NULL);
+ c2_list_free(&ps->o.unredir_if_possible_blacklist, NULL);
+ c2_list_free(&ps->o.rounded_corners_blacklist, NULL);
+ c2_list_free(&ps->o.corner_rules, NULL);
+ c2_list_free(&ps->o.blur_rules, NULL);
+ c2_list_free(&ps->o.animation_open_blacklist, NULL);
+ c2_list_free(&ps->o.animation_unmap_blacklist, NULL);
+ c2_list_free(&ps->o.active_opacity_blacklist, NULL);
+ c2_list_free(&ps->o.inactive_opacity_blacklist, NULL);
+ c2_list_free(&ps->o.window_shader_fg_rules, free);
+
+ // 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);
+
+ // Release custom window shaders
+ free(ps->o.window_shader_fg);
+ struct shader_info *shader, *tmp;
+ HASH_ITER(hh, ps->shaders, shader, tmp) {
+ HASH_DEL(ps->shaders, shader);
+ assert(shader->backend_shader == NULL);
+ free(shader->source);
+ free(shader->key);
+ free(shader);
+ }
+
+#ifdef CONFIG_VSYNC_DRM
+ // Close file opened for DRM VSync
+ if (ps->drm_fd >= 0) {
+ 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.legacy_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);
+ if (ps->o.legacy_backends) {
+ free_conv((conv *)ps->shadow_context);
+ }
+ 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) {
+ // 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;
+}