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() \