diff --git a/src/compton.c b/src/compton.c
index 2e2e781..49baf38 100644
--- a/src/compton.c
+++ b/src/compton.c
@@ -51,9 +51,7 @@
#include "dbus.h"
#endif
#include "options.h"
-
-#define CASESTRRET(s) \
- case s: return #s
+#include "event.h"
/// Get session_t pointer from a pointer to a member of session_t
#define session_ptr(ptr, member) \
@@ -62,28 +60,11 @@
(session_t *)((char *)__mptr - offsetof(session_t, member)); \
})
-static void update_refresh_rate(session_t *ps);
-
-static bool swopti_init(session_t *ps);
-
-static void cxinerama_upd_scrs(session_t *ps);
-
-static void session_destroy(session_t *ps);
-
-static void cxinerama_upd_scrs(session_t *ps);
static bool must_use redir_start(session_t *ps);
static void redir_stop(session_t *ps);
-static win *recheck_focus(session_t *ps);
-
-static void restack_win(session_t *ps, win *w, xcb_window_t new_above);
-
-static void update_ewmh_active_win(session_t *ps);
-
-static void draw_callback(EV_P_ ev_idle *w, int revents);
-
// === Global constants ===
/// Name strings for window types.
@@ -132,7 +113,7 @@ static inline uint64_t get_time_ms(void) {
}
// XXX Move to x.c
-static void cxinerama_upd_scrs(session_t *ps) {
+void cxinerama_upd_scrs(session_t *ps) {
// XXX Consider deprecating Xinerama, switch to RandR when necessary
free_xinerama_info(ps);
@@ -271,7 +252,7 @@ static bool run_fade(session_t *ps, win **_w, unsigned steps) {
// === Error handling ===
-static void discard_ignore(session_t *ps, unsigned long sequence) {
+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;
@@ -358,7 +339,7 @@ win *find_toplevel2(session_t *ps, xcb_window_t wid) {
* @param ps current session
* @return struct _win of currently focused window, NULL if not found
*/
-static win *recheck_focus(session_t *ps) {
+win *recheck_focus(session_t *ps) {
// Use EWMH _NET_ACTIVE_WINDOW if enabled
if (ps->o.use_ewmh_active_win) {
update_ewmh_active_win(ps);
@@ -661,45 +642,6 @@ static void rebuild_shadow_exclude_reg(session_t *ps) {
exit(1);
}
-static void repair_win(session_t *ps, win *w) {
- if (w->a.map_state != XCB_MAP_STATE_VIEWABLE)
- return;
-
- 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 {
- xcb_xfixes_region_t tmp = xcb_generate_id(ps->c);
- xcb_xfixes_create_region(ps->c, tmp, 0, NULL);
- set_ignore_cookie(ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, tmp));
- xcb_xfixes_translate_region(ps->c, tmp, w->g.x + w->g.border_width,
- w->g.y + w->g.border_width);
- x_fetch_region(ps->c, tmp, &parts);
- xcb_xfixes_destroy_region(ps->c, tmp);
- }
-
- 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 void restack_win(session_t *ps, win *w, xcb_window_t new_above) {
xcb_window_t old_above;
@@ -878,7 +820,7 @@ void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) {
w->a.override_redirect = ce->override_redirect;
}
-static void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce) {
+void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce) {
win *w = find_win(ps, ce->window);
xcb_window_t new_above;
@@ -894,7 +836,7 @@ static void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce) {
restack_win(ps, w, new_above);
}
-static inline void root_damaged(session_t *ps) {
+void root_damaged(session_t *ps) {
if (ps->root_tile_paint.pixmap) {
free_root_tile(ps);
}
@@ -934,11 +876,6 @@ void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) {
x_print_error(err->sequence, err->major_code, err->minor_code, err->error_code);
}
-static 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);
-}
/**
* Force a full-screen repaint.
*/
@@ -1098,37 +1035,6 @@ static inline xcb_window_t attr_unused ev_window(session_t *ps, xcb_generic_even
}
}
-static inline const char *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 *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";
-}
-
-static inline void attr_unused ev_focus_report(xcb_focus_in_event_t *ev) {
- log_trace("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
- ev_focus_detail_name(ev));
-}
-
// === Events ===
/**
@@ -1142,144 +1048,13 @@ ev_focus_accept(XFocusChangeEvent *ev) {
}
*/
-static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) {
-#ifdef DEBUG_EVENTS
- ev_focus_report(ev);
-#endif
-
- recheck_focus(ps);
-}
-
-inline static void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) {
-#ifdef DEBUG_EVENTS
- ev_focus_report(ev);
-#endif
-
- recheck_focus(ps);
-}
-
-inline static void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
- assert(ev->parent == ps->root);
- add_win(ps, ev->window, 0);
-}
-
-inline static void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) {
- log_trace("{ 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) {
- configure_root(ps, ev->width, ev->height);
- } else {
- configure_win(ps, ev);
- }
-}
-
-inline static void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) {
- win *w = find_win(ps, ev->window);
- if (w) {
- unmap_win(ps, &w, true);
- }
-}
-
-inline static void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) {
- map_win_by_id(ps, ev->window);
- // FocusIn/Out may be ignored when the window is unmapped, so we must
- // recheck focus here
- if (ps->o.track_focus) {
- recheck_focus(ps);
- }
-}
-
-inline static void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) {
- win *w = find_win(ps, ev->window);
- if (w) {
- unmap_win(ps, &w, false);
- }
-}
-
-inline static void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) {
- log_trace("{ new_parent: %#010x, override_redirect: %d }", ev->parent,
- ev->override_redirect);
-
- if (ev->parent == ps->root) {
- // new window
- add_win(ps, ev->window, 0);
- } else {
- // otherwise, find and destroy the window first
- win *w = find_win(ps, ev->window);
- if (w) {
- unmap_win(ps, &w, true);
- }
-
- // 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)});
-
- // Check if the window is an undetected client window
- // Firstly, check if it's a known client window
- if (!find_toplevel(ps, ev->window)) {
- // If not, look for its frame window
- win *w_top = find_toplevel2(ps, ev->parent);
- // If found, and the client window has not been determined, or its
- // frame may not have a correct client, continue
- if (w_top && (!w_top->client_win || w_top->client_win == w_top->id)) {
- // If it has WM_STATE, mark it the client window
- if (wid_has_prop(ps, ev->window, ps->atom_client)) {
- w_top->wmwin = false;
- win_unmark_client(ps, w_top);
- win_mark_client(ps, w_top, ev->window);
- }
- // Otherwise, watch for WM_STATE on it
- else {
- 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});
- }
- }
- }
- }
-}
-
-inline static void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) {
- circulate_win(ps, ev);
-}
-
-inline static 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;
- }
- }
-}
-
/**
* 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.
*/
-static void update_ewmh_active_win(session_t *ps) {
+void update_ewmh_active_win(session_t *ps) {
// Search for the window
xcb_window_t wid = wid_get_prop_window(ps, ps->root, ps->atom_ewmh_active_win);
win *w = find_win_all(ps, wid);
@@ -1289,213 +1064,6 @@ static void update_ewmh_active_win(session_t *ps) {
win_set_focused(ps, w, true);
}
-inline static void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) {
-#ifdef DEBUG_EVENTS
- {
- // 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_trace("{ atom = %.*s }", name_len, name);
- free(reply);
- }
-#endif
-
- if (ps->root == ev->window) {
- if (ps->o.track_focus && ps->o.use_ewmh_active_win &&
- ps->atom_ewmh_active_win == ev->atom) {
- update_ewmh_active_win(ps);
- } else {
- // Destroy the root "image" if the wallpaper probably changed
- if (x_is_root_back_pixmap_atom(ps, ev->atom)) {
- root_damaged(ps);
- }
- }
-
- // Unconcerned about any other proprties on root window
- return;
- }
-
- // If WM_STATE changes
- if (ev->atom == ps->atom_client) {
- // 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)});
-
- win *w_top = find_toplevel2(ps, ev->window);
- // Initialize client_win as early as possible
- if (w_top && (!w_top->client_win || w_top->client_win == w_top->id) &&
- wid_has_prop(ps, ev->window, ps->atom_client)) {
- w_top->wmwin = false;
- win_unmark_client(ps, w_top);
- win_mark_client(ps, w_top, ev->window);
- }
- }
- }
-
- // If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
- // there are always some stupid applications. (#144)
- if (ev->atom == ps->atom_win_type) {
- win *w = NULL;
- if ((w = find_toplevel(ps, ev->window)))
- win_update_wintype(ps, w);
- }
-
- // If _NET_WM_OPACITY changes
- if (ev->atom == ps->atom_opacity) {
- win *w = find_win(ps, ev->window) ?: find_toplevel(ps, ev->window);
- if (w) {
- win_update_opacity_prop(ps, w);
- // we cannot receive OPACITY change when window is destroyed
- assert(w->state != WSTATE_DESTROYING);
- if (w->state == WSTATE_MAPPED) {
- // See the winstate_t transition table
- w->state = WSTATE_FADING;
- }
- w->opacity_tgt = win_calc_opacity_target(ps, w);
- }
- }
-
- // If frame extents property changes
- if (ps->o.frame_opacity && ev->atom == ps->atom_frame_extents) {
- win *w = find_toplevel(ps, ev->window);
- if (w) {
- win_update_frame_extents(ps, w, ev->window);
- // If frame extents change, the window needs repaint
- add_damage_from_win(ps, w);
- }
- }
-
- // If name changes
- if (ps->o.track_wdata && (ps->atom_name == ev->atom || ps->atom_name_ewmh == ev->atom)) {
- win *w = find_toplevel(ps, ev->window);
- if (w && 1 == win_get_name(ps, w)) {
- win_on_factor_change(ps, w);
- }
- }
-
- // If class changes
- if (ps->o.track_wdata && ps->atom_class == ev->atom) {
- win *w = find_toplevel(ps, ev->window);
- if (w) {
- win_get_class(ps, w);
- win_on_factor_change(ps, w);
- }
- }
-
- // If role changes
- if (ps->o.track_wdata && ps->atom_role == ev->atom) {
- win *w = find_toplevel(ps, ev->window);
- if (w && 1 == win_get_role(ps, w)) {
- win_on_factor_change(ps, w);
- }
- }
-
- // If _COMPTON_SHADOW changes
- if (ps->o.respect_prop_shadow && ps->atom_compton_shadow == ev->atom) {
- win *w = find_win(ps, ev->window);
- if (w)
- win_update_prop_shadow(ps, w);
- }
-
- // If a leader property changes
- if ((ps->o.detect_transient && ps->atom_transient == ev->atom) ||
- (ps->o.detect_client_leader && ps->atom_client_leader == ev->atom)) {
- win *w = find_toplevel(ps, ev->window);
- if (w) {
- win_update_leader(ps, w);
- }
- }
-
- // Check for other atoms we are tracking
- for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
- if (platom->atom == ev->atom) {
- win *w = find_win(ps, ev->window);
- if (!w)
- w = find_toplevel(ps, ev->window);
- if (w)
- win_on_factor_change(ps, w);
- break;
- }
- }
-}
-
-inline static void ev_damage_notify(session_t *ps, xcb_damage_notify_event_t *de) {
- /*
- if (ps->root == de->drawable) {
- root_damaged();
- return;
- } */
-
- win *w = find_win(ps, de->drawable);
-
- if (!w)
- return;
-
- repair_win(ps, w);
-}
-
-inline static void ev_shape_notify(session_t *ps, xcb_shape_notify_event_t *ev) {
- win *w = find_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 border_size as damaged
- region_t tmp = win_get_bounding_shape_global_by_val(w);
- add_damage(ps, &tmp);
- pixman_region32_fini(&tmp);
-
- win_update_bounding_shape(ps, w);
-
- // Mark the new border_size as damaged
- tmp = win_get_bounding_shape_global_by_val(w);
- add_damage(ps, &tmp);
- pixman_region32_fini(&tmp);
-
- w->reg_ignore_valid = false;
-}
-
-/**
- * Handle ScreenChangeNotify events from X RandR extension.
- */
-static void ev_screen_change_notify(session_t *ps,
- xcb_randr_screen_change_notify_event_t attr_unused *ev) {
- 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");
- }
- }
-}
-
-inline static 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.");
- exit(1);
-}
-
/**
* Get a window's name from window ID.
*/
@@ -1522,93 +1090,6 @@ static inline void attr_unused ev_window_name(session_t *ps, xcb_window_t wid, c
}
}
-static void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
- if ((ev->response_type & 0x7f) != KeymapNotify) {
- discard_ignore(ps, ev->full_sequence);
- }
-
-#ifdef DEBUG_EVENTS
- if (ev->response_type != ps->damage_event + XCB_DAMAGE_NOTIFY) {
- xcb_window_t wid = ev_window(ps, ev);
- char *window_name = NULL;
- ev_window_name(ps, wid, &window_name);
-
- log_trace("event %10.10s serial %#010x window %#010lx \"%s\"",
- ev_name(ps, ev), ev_serial(ev), wid, window_name);
- }
-#endif
-
- // 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)) {
- ev_screen_change_notify(
- ps, (xcb_randr_screen_change_notify_event_t *)ev);
- break;
- }
- if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) {
- ev_damage_notify(ps, (xcb_damage_notify_event_t *)ev);
- break;
- }
- }
-}
-
// === Main ===
/**
@@ -1746,7 +1227,7 @@ static void init_atoms(session_t *ps) {
/**
* Update refresh rate info with X Randr extension.
*/
-static void update_refresh_rate(session_t *ps) {
+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);
diff --git a/src/compton.h b/src/compton.h
index 572d048..182af66 100644
--- a/src/compton.h
+++ b/src/compton.h
@@ -40,6 +40,28 @@ xcb_window_t find_client_win(session_t *ps, xcb_window_t w);
win *find_toplevel2(session_t *ps, xcb_window_t wid);
+win *recheck_focus(session_t *ps);
+
+/// Handle configure event of a root window
+void configure_root(session_t *ps, int width, int height);
+
+/// Handle configure event of a regular window
+void configure_win(session_t *ps, xcb_configure_notify_event_t *ce);
+
+void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce);
+
+void update_ewmh_active_win(session_t *ps);
+
+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);
+
/**
* Set a switch_t array of all unset wintypes to true.
*/
diff --git a/src/event.c b/src/event.c
new file mode 100644
index 0000000..d735b61
--- /dev/null
+++ b/src/event.c
@@ -0,0 +1,538 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2019, Yuxuan Shui
+
+#include
+
+#include "common.h"
+#include "compton.h"
+#include "event.h"
+#include "utils.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 the things described above
+
+static inline const char *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 *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";
+}
+
+static inline void attr_unused ev_focus_report(xcb_focus_in_event_t *ev) {
+ log_trace("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
+ ev_focus_detail_name(ev));
+}
+
+static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) {
+#ifdef DEBUG_EVENTS
+ ev_focus_report(ev);
+#endif
+
+ recheck_focus(ps);
+}
+
+static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) {
+#ifdef DEBUG_EVENTS
+ ev_focus_report(ev);
+#endif
+
+ recheck_focus(ps);
+}
+
+static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
+ assert(ev->parent == ps->root);
+ add_win(ps, ev->window, 0);
+}
+
+static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) {
+ log_trace("{ 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) {
+ configure_root(ps, ev->width, ev->height);
+ } else {
+ configure_win(ps, ev);
+ }
+}
+
+static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) {
+ win *w = find_win(ps, ev->window);
+ if (w) {
+ unmap_win(ps, &w, true);
+ }
+}
+
+static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) {
+ map_win_by_id(ps, ev->window);
+ // FocusIn/Out may be ignored when the window is unmapped, so we must
+ // recheck focus here
+ if (ps->o.track_focus) {
+ recheck_focus(ps);
+ }
+}
+
+static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) {
+ win *w = find_win(ps, ev->window);
+ if (w) {
+ unmap_win(ps, &w, false);
+ }
+}
+
+static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) {
+ log_trace("{ new_parent: %#010x, override_redirect: %d }", ev->parent,
+ ev->override_redirect);
+
+ if (ev->parent == ps->root) {
+ // new window
+ add_win(ps, ev->window, 0);
+ } else {
+ // otherwise, find and destroy the window first
+ win *w = find_win(ps, ev->window);
+ if (w) {
+ unmap_win(ps, &w, true);
+ }
+
+ // 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)});
+
+ // Check if the window is an undetected client window
+ // Firstly, check if it's a known client window
+ if (!find_toplevel(ps, ev->window)) {
+ // If not, look for its frame window
+ win *w_top = find_toplevel2(ps, ev->parent);
+ // If found, and the client window has not been determined, or its
+ // frame may not have a correct client, continue
+ if (w_top && (!w_top->client_win || w_top->client_win == w_top->id)) {
+ // If it has WM_STATE, mark it the client window
+ if (wid_has_prop(ps, ev->window, ps->atom_client)) {
+ w_top->wmwin = false;
+ win_unmark_client(ps, w_top);
+ win_mark_client(ps, w_top, ev->window);
+ }
+ // Otherwise, watch for WM_STATE on it
+ else {
+ 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});
+ }
+ }
+ }
+ }
+}
+
+static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) {
+ circulate_win(ps, ev);
+}
+
+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);
+}
+
+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) {
+#ifdef DEBUG_EVENTS
+ {
+ // 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_trace("{ atom = %.*s }", name_len, name);
+ free(reply);
+ }
+#endif
+
+ if (ps->root == ev->window) {
+ if (ps->o.track_focus && ps->o.use_ewmh_active_win &&
+ ps->atom_ewmh_active_win == ev->atom) {
+ update_ewmh_active_win(ps);
+ } else {
+ // Destroy the root "image" if the wallpaper probably changed
+ if (x_is_root_back_pixmap_atom(ps, ev->atom)) {
+ root_damaged(ps);
+ }
+ }
+
+ // Unconcerned about any other proprties on root window
+ return;
+ }
+
+ // If WM_STATE changes
+ if (ev->atom == ps->atom_client) {
+ // 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)});
+
+ win *w_top = find_toplevel2(ps, ev->window);
+ // Initialize client_win as early as possible
+ if (w_top && (!w_top->client_win || w_top->client_win == w_top->id) &&
+ wid_has_prop(ps, ev->window, ps->atom_client)) {
+ w_top->wmwin = false;
+ win_unmark_client(ps, w_top);
+ win_mark_client(ps, w_top, ev->window);
+ }
+ }
+ }
+
+ // If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
+ // there are always some stupid applications. (#144)
+ if (ev->atom == ps->atom_win_type) {
+ win *w = NULL;
+ if ((w = find_toplevel(ps, ev->window)))
+ win_update_wintype(ps, w);
+ }
+
+ // If _NET_WM_OPACITY changes
+ if (ev->atom == ps->atom_opacity) {
+ win *w = find_win(ps, ev->window) ?: find_toplevel(ps, ev->window);
+ if (w) {
+ win_update_opacity_prop(ps, w);
+ // we cannot receive OPACITY change when window is destroyed
+ assert(w->state != WSTATE_DESTROYING);
+ if (w->state == WSTATE_MAPPED) {
+ // See the winstate_t transition table
+ w->state = WSTATE_FADING;
+ }
+ w->opacity_tgt = win_calc_opacity_target(ps, w);
+ }
+ }
+
+ // If frame extents property changes
+ if (ps->o.frame_opacity && ev->atom == ps->atom_frame_extents) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_update_frame_extents(ps, w, ev->window);
+ // If frame extents change, the window needs repaint
+ add_damage_from_win(ps, w);
+ }
+ }
+
+ // If name changes
+ if (ps->o.track_wdata && (ps->atom_name == ev->atom || ps->atom_name_ewmh == ev->atom)) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w && 1 == win_get_name(ps, w)) {
+ win_on_factor_change(ps, w);
+ }
+ }
+
+ // If class changes
+ if (ps->o.track_wdata && ps->atom_class == ev->atom) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_get_class(ps, w);
+ win_on_factor_change(ps, w);
+ }
+ }
+
+ // If role changes
+ if (ps->o.track_wdata && ps->atom_role == ev->atom) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w && 1 == win_get_role(ps, w)) {
+ win_on_factor_change(ps, w);
+ }
+ }
+
+ // If _COMPTON_SHADOW changes
+ if (ps->o.respect_prop_shadow && ps->atom_compton_shadow == ev->atom) {
+ win *w = find_win(ps, ev->window);
+ if (w)
+ win_update_prop_shadow(ps, w);
+ }
+
+ // If a leader property changes
+ if ((ps->o.detect_transient && ps->atom_transient == ev->atom) ||
+ (ps->o.detect_client_leader && ps->atom_client_leader == ev->atom)) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_update_leader(ps, w);
+ }
+ }
+
+ // Check for other atoms we are tracking
+ for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
+ if (platom->atom == ev->atom) {
+ win *w = find_win(ps, ev->window);
+ if (!w)
+ w = find_toplevel(ps, ev->window);
+ if (w)
+ win_on_factor_change(ps, w);
+ break;
+ }
+ }
+}
+
+static inline void repair_win(session_t *ps, win *w) {
+ if (w->a.map_state != XCB_MAP_STATE_VIEWABLE)
+ return;
+
+ 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 {
+ xcb_xfixes_region_t tmp = xcb_generate_id(ps->c);
+ xcb_xfixes_create_region(ps->c, tmp, 0, NULL);
+ set_ignore_cookie(ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, tmp));
+ xcb_xfixes_translate_region(ps->c, tmp, w->g.x + w->g.border_width,
+ w->g.y + w->g.border_width);
+ x_fetch_region(ps->c, tmp, &parts);
+ xcb_xfixes_destroy_region(ps->c, tmp);
+ }
+
+ 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;
+ } */
+
+ win *w = find_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) {
+ win *w = find_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 border_size as damaged
+ region_t tmp = win_get_bounding_shape_global_by_val(w);
+ add_damage(ps, &tmp);
+ pixman_region32_fini(&tmp);
+
+ win_update_bounding_shape(ps, w);
+
+ // Mark the new border_size as damaged
+ tmp = win_get_bounding_shape_global_by_val(w);
+ add_damage(ps, &tmp);
+ pixman_region32_fini(&tmp);
+
+ w->reg_ignore_valid = false;
+}
+
+/**
+ * Handle ScreenChangeNotify events from X RandR extension.
+ */
+static void ev_screen_change_notify(session_t *ps,
+ xcb_randr_screen_change_notify_event_t attr_unused *ev) {
+ 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");
+ }
+ }
+}
+
+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.");
+ exit(1);
+}
+
+void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
+ if ((ev->response_type & 0x7f) != KeymapNotify) {
+ discard_ignore(ps, ev->full_sequence);
+ }
+
+#ifdef DEBUG_EVENTS
+ if (ev->response_type != ps->damage_event + XCB_DAMAGE_NOTIFY) {
+ xcb_window_t wid = ev_window(ps, ev);
+ char *window_name = NULL;
+ ev_window_name(ps, wid, &window_name);
+
+ log_trace("event %10.10s serial %#010x window %#010lx \"%s\"",
+ ev_name(ps, ev), ev_serial(ev), wid, window_name);
+ }
+#endif
+
+ // 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)) {
+ ev_screen_change_notify(
+ ps, (xcb_randr_screen_change_notify_event_t *)ev);
+ 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
new file mode 100644
index 0000000..629dec0
--- /dev/null
+++ b/src/event.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2019, Yuxuan Shui
+
+#include
+
+#include "common.h"
+
+void ev_handle(session_t *ps, xcb_generic_event_t *ev);
diff --git a/src/meson.build b/src/meson.build
index 9d73bc3..2dec9db 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -6,7 +6,7 @@ base_deps = [
srcs = [ files('compton.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') ]
+ 'options.c', 'event.c') ]
compton_inc = include_directories('.')
cflags = []
diff --git a/src/utils.h b/src/utils.h
index d6f6aa0..45f3911 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -30,6 +30,9 @@ safe_isnan(double a) {
return isnan(a);
}
+#define CASESTRRET(s) \
+ case s: return #s
+
/// 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() \