diff --git a/README.md b/README.md index 6f8fbe43..6536a65c 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, meso * libGL (optional, disable with the `-Dopengl=false` meson configure flag) * libpcre (optional, disable with the `-Dregex=false` meson configure flag) * libev +* uthash To build the documents, you need `asciidoc` diff --git a/meson.build b/meson.build index 6fe5e1b0..ff25fab9 100644 --- a/meson.build +++ b/meson.build @@ -31,6 +31,9 @@ if get_option('sanitize') endif add_global_arguments('-fsanitize='+','.join(sanitizers), language: 'c') add_global_link_arguments('-fsanitize='+','.join(sanitizers), language: 'c') + if cc.has_argument('-fno-sanitize=unsigned-integer-overflow') + add_global_arguments('-fno-sanitize=unsigned-integer-overflow', language: 'c') + endif endif if get_option('modularize') diff --git a/src/common.h b/src/common.h index 407d0bb9..f889633f 100644 --- a/src/common.h +++ b/src/common.h @@ -49,6 +49,7 @@ #include #include +#include "uthash_extra.h" #ifdef CONFIG_OPENGL // libGL #include "backend/gl/glx.h" @@ -389,8 +390,10 @@ typedef struct session { int n_expose; // === Window related === - /// Linked list of all windows. - win *list; + /// A hash table of all windows. + win *windows; + /// Windows in their stacking order + win *window_stack; /// Pointer to win of current active window. Used by /// EWMH _NET_ACTIVE_WINDOW focus detection. In theory, /// it's more reliable to store the window ID directly here, just in @@ -732,43 +735,6 @@ static inline xcb_window_t get_tgt_window(session_t *ps) { return ps->overlay != XCB_NONE ? ps->overlay : ps->root; } -/** - * Find a window from window id in window linked list of the session. - */ -static inline win *find_win(session_t *ps, xcb_window_t id) { - if (!id) - return NULL; - - win *w; - - for (w = ps->list; w; w = w->next) { - if (w->id == id && w->state != WSTATE_DESTROYING) { - return w; - } - } - - return 0; -} - -/** - * Find out the WM frame of a client window using existing data. - * - * @param id window ID - * @return struct win object of the found window, NULL if not found - */ -static inline win *find_toplevel(session_t *ps, xcb_window_t id) { - if (!id) - return NULL; - - for (win *w = ps->list; w; w = w->next) { - if (w->client_win == id && w->state != WSTATE_DESTROYING) { - return w; - } - } - - return NULL; -} - /** * Check if current backend uses GLX. */ diff --git a/src/compton.c b/src/compton.c index a4e3ce3a..5a26a793 100644 --- a/src/compton.c +++ b/src/compton.c @@ -51,6 +51,7 @@ #endif #include "event.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) \ @@ -419,7 +420,7 @@ static void handle_root_flags(session_t *ps) { static win *paint_preprocess(session_t *ps, bool *fade_running) { // XXX need better, more general name for `fade_running`. It really // means if fade is still ongoing after the current frame is rendered - win *t = NULL, *next = NULL; + win *t = NULL; *fade_running = false; // Fading step calculation @@ -436,8 +437,7 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { ps->fade_time += steps * ps->o.fade_delta; // First, let's process fading - for (win *w = ps->list; w; w = next) { - next = w->next; + WIN_STACK_ITER(ps, w) { const winmode_t mode_old = w->mode; const bool was_painted = w->to_paint; const double opacity_old = w->opacity; @@ -490,15 +490,12 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { // Track whether it's the highest window to paint bool is_highest = true; bool reg_ignore_valid = true; - for (win *w = ps->list; w; w = next) { + WIN_STACK_ITER(ps, w) { __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; - // In case calling the fade callback function destroys this window - next = w->next; - // Destroy reg_ignore if some window above us invalidated it if (!reg_ignore_valid) { rc_region_unref(&w->reg_ignore); @@ -672,7 +669,7 @@ static void restack_win(session_t *ps, win *w, xcb_window_t new_above) { win **prev = NULL, **prev_old = NULL; bool found = false; - for (prev = &ps->list; *prev; prev = &(*prev)->next) { + for (prev = &ps->window_stack; *prev; prev = &(*prev)->next) { if ((*prev)->id == new_above && (*prev)->state != WSTATE_DESTROYING) { found = true; break; @@ -685,7 +682,7 @@ static void restack_win(session_t *ps, win *w, xcb_window_t new_above) { return; } - for (prev_old = &ps->list; *prev_old; prev_old = &(*prev_old)->next) { + for (prev_old = &ps->window_stack; *prev_old; prev_old = &(*prev_old)->next) { if ((*prev_old) == w) { break; } @@ -713,8 +710,7 @@ static void restack_win(session_t *ps, win *w, xcb_window_t new_above) { /// Free up all the images and deinit the backend static void destroy_backend(session_t *ps) { - for (win *w = ps->list, *next; w; w = next) { - next = w->next; + WIN_STACK_ITER(ps, w) { // Wrapping up fading in progress win_skip_fading(ps, &w); @@ -760,7 +756,9 @@ static bool initialize_backend(session_t *ps) { return false; } - for (win *w = ps->list; w; w = w->next) { + // 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->a.map_state == XCB_MAP_STATE_VIEWABLE) { if (!win_bind_image(ps, w)) { w->flags |= WIN_FLAGS_IMAGE_ERROR; @@ -800,8 +798,8 @@ void configure_root(session_t *ps, int width, int height) { ps->damage = ps->damage_ring + ps->ndamage - 1; // Invalidate reg_ignore from the top - rc_region_unref(&ps->list->reg_ignore); - ps->list->reg_ignore_valid = false; + rc_region_unref(&ps->window_stack->reg_ignore); + ps->window_stack->reg_ignore_valid = false; #ifdef CONFIG_OPENGL // GLX root change callback @@ -846,7 +844,6 @@ void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { restack_win(ps, w, ce->above_sibling); } else { restack_win(ps, w, ce->above_sibling); - bool factor_change = false; win_extents(w, &damage); @@ -897,7 +894,7 @@ void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce) { return; if (ce->place == PlaceOnTop) { - new_above = ps->list->id; + new_above = ps->window_stack->id; } else { new_above = XCB_NONE; } @@ -1017,12 +1014,14 @@ void opts_init_track_focus(session_t *ps) { if (!ps->o.use_ewmh_active_win) { // Start listening to FocusChange events - for (win *w = ps->list; w; w = w->next) - if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) + HASH_ITER2(ps->windows, w) { + if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { xcb_change_window_attributes( ps->c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){ determine_evmask(ps, w->id, WIN_EVMODE_FRAME)}); + } + } } // Recheck focus @@ -1687,7 +1686,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .size_expose = 0, .n_expose = 0, - .list = NULL, + .windows = NULL, .active_win = NULL, .active_leader = XCB_NONE, @@ -2125,15 +2124,15 @@ static session_t *session_init(int argc, char **argv, Display *dpy, add_win(ps, children[i], i ? children[i - 1] : XCB_NONE); } - for (win *i = ps->list; i; i = i->next) { - if (i->a.map_state == XCB_MAP_STATE_VIEWABLE) { - map_win(ps, i); + HASH_ITER2(ps->windows, w) { + if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { + map_win(ps, w); } } free(reply); log_trace("Initial stack:"); - for (win *c = ps->list; c; c = c->next) { + for (win *c = ps->window_stack; c; c = c->next) { log_trace("%#010x \"%s\"", c->id, c->name); } } @@ -2183,22 +2182,17 @@ static void session_destroy(session_t *ps) { #endif // Free window linked list - { - win *next = NULL; - win *list = ps->list; - ps->list = NULL; - for (win *w = list; w; w = next) { - next = w->next; - - if (w->state != WSTATE_DESTROYING) { - win_ev_stop(ps, w); - } - - free_win_res(ps, w); - free(w); + WIN_STACK_ITER(ps, w) { + if (w->state != WSTATE_DESTROYING) { + win_ev_stop(ps, w); + HASH_DEL(ps->windows, w); } + + free_win_res(ps, w); + free(w); } + ps->window_stack = NULL; // Free blacklists free_wincondlst(&ps->o.shadow_blacklist); diff --git a/src/dbus.c b/src/dbus.c index 01f1d45e..18f74cb7 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -26,6 +26,7 @@ #include "types.h" #include "utils.h" #include "win.h" +#include "uthash_extra.h" #include "dbus.h" @@ -465,10 +466,9 @@ static bool cdbus_apdarg_string(session_t *ps, DBusMessage *msg, const void *dat static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data) { // Get the number of wids we are to include unsigned count = 0; - for (win *w = ps->list; w; w = w->next) { - if (w->state != WSTATE_DESTROYING) { - ++count; - } + HASH_ITER2(ps->windows, w) { + assert(w->state != WSTATE_DESTROYING); + ++count; } if (!count) { @@ -480,17 +480,13 @@ static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data) auto arr = ccalloc(count, cdbus_window_t); // Build the array - { - cdbus_window_t *pcur = arr; - for (win *w = ps->list; w; w = w->next) { - if (w->state != WSTATE_DESTROYING) { - *pcur = w->id; - ++pcur; - assert(pcur <= arr + count); - } - } - assert(pcur == arr + count); + cdbus_window_t *pcur = arr; + HASH_ITER2(ps->windows, w) { + assert(w->state != WSTATE_DESTROYING); + *pcur = w->id; + ++pcur; } + assert(pcur == arr + count); // Append arguments if (!dbus_message_append_args(msg, DBUS_TYPE_ARRAY, CDBUS_TYPE_WINDOW, &arr, diff --git a/src/meson.build b/src/meson.build index 44b34173..d22f1da0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -23,6 +23,10 @@ foreach i : required_package base_deps += [dependency(i, required: true)] endforeach +if not cc.has_header('uthash.h') + error('Dependency uthash not found') +endif + deps = [] if get_option('config_file') diff --git a/src/opengl.c b/src/opengl.c index 65f339d0..08563a3d 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -26,6 +26,7 @@ #include "string_utils.h" #include "utils.h" #include "win.h" +#include "uthash_extra.h" #include "opengl.h" @@ -220,8 +221,9 @@ void glx_destroy(session_t *ps) { return; // Free all GLX resources of windows - for (win *w = ps->list; w; w = w->next) + WIN_STACK_ITER(ps, w) { free_win_res_glx(ps, w); + } // Free GLSL shaders/programs for (int i = 0; i < MAX_BLUR_PASS; ++i) { diff --git a/src/uthash_extra.h b/src/uthash_extra.h new file mode 100644 index 00000000..cbc10561 --- /dev/null +++ b/src/uthash_extra.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#define HASH_ITER2(head, el) \ + for (__typeof__(head) el = (head), __tmp = el != NULL ? el->hh.next : NULL; \ + el != NULL; el = __tmp, __tmp = el != NULL ? el->hh.next : NULL) diff --git a/src/win.c b/src/win.c index 3a094b2b..8970d344 100644 --- a/src/win.c +++ b/src/win.c @@ -25,6 +25,7 @@ #include "render.h" #include "string_utils.h" #include "types.h" +#include "uthash_extra.h" #include "utils.h" #include "x.h" @@ -56,8 +57,9 @@ * Clear leader cache of all windows. */ static inline void clear_cache_win_leaders(session_t *ps) { - for (win *w = ps->list; w; w = w->next) + for (win *w = ps->window_stack; w; w = w->next) { w->cache_leader = XCB_NONE; + } } static inline void wid_set_opacity_prop(session_t *ps, xcb_window_t wid, opacity_t val) { @@ -79,9 +81,11 @@ static inline void group_update_focused(session_t *ps, xcb_window_t leader) { if (!leader) return; - for (win *w = ps->list; w; w = w->next) { - if (win_get_leader(ps, w) == leader && w->state != WSTATE_DESTROYING) + HASH_ITER2(ps->windows, w) { + assert(w->state != WSTATE_DESTROYING); + if (win_get_leader(ps, w) == leader) { win_update_focused(ps, w); + } } return; @@ -97,10 +101,11 @@ static inline bool group_is_focused(session_t *ps, xcb_window_t leader) { if (!leader) return false; - for (win *w = ps->list; w; w = w->next) { - if (win_get_leader(ps, w) == leader && w->state != WSTATE_DESTROYING && - win_is_focused_real(ps, w)) + HASH_ITER2(ps->windows, w) { + assert(w->state != WSTATE_DESTROYING); + if (win_get_leader(ps, w) == leader && win_is_focused_real(ps, w)) { return true; + } } return false; @@ -1022,15 +1027,16 @@ void add_win(session_t *ps, xcb_window_t id, xcb_window_t prev) { // Find window insertion point win **p = NULL; if (prev) { - for (p = &ps->list; *p; p = &(*p)->next) { + for (p = &ps->window_stack; *p; p = &(*p)->next) { if ((*p)->id == prev && (*p)->state != WSTATE_DESTROYING) break; } } else { - p = &ps->list; + p = &ps->window_stack; } new->next = *p; *p = new; + HASH_ADD_INT(ps->windows, id, new); #ifdef CONFIG_DBUS // Send D-Bus signal @@ -1414,7 +1420,7 @@ void win_update_frame_extents(session_t *ps, win *w, xcb_window_t client) { } bool win_is_region_ignore_valid(session_t *ps, const win *w) { - for (win *i = ps->list; w; w = w->next) { + WIN_STACK_ITER(ps, i) { if (i == w) break; if (!i->reg_ignore_valid) @@ -1481,7 +1487,7 @@ static void finish_destroy_win(session_t *ps, win **_w) { } log_trace("Trying to destroy (%#010x)", w->id); - for (prev = &ps->list; *prev; prev = &(*prev)->next) { + for (prev = &ps->window_stack; *prev; prev = &(*prev)->next) { if (w == *prev) { log_trace("Found (%#010x \"%s\")", w->id, w->name); *prev = w->next; @@ -1495,7 +1501,7 @@ static void finish_destroy_win(session_t *ps, win **_w) { // Drop w from all prev_trans to avoid accessing freed memory in // repair_win() // TODO there can only be one prev_trans pointing to w - for (win *w2 = ps->list; w2; w2 = w2->next) { + for (win *w2 = ps->window_stack; w2; w2 = w2->next) { if (w == w2->prev_trans) { w2->prev_trans = NULL; } @@ -1545,6 +1551,15 @@ void unmap_win(session_t *ps, win **_w, bool destroy) { return; } + if (destroy) { + // Delete destroyed window from the hash table, so future window with the + // same window id won't confuse us. + // Keep the window in the window stack, since we might still need to + // render it (fading out). Window will be removed from the stack when + // fading finishes. + HASH_DEL(ps->windows, w); + } + if (unlikely(w->state == WSTATE_UNMAPPED) || w->a._class == XCB_WINDOW_CLASS_INPUT_ONLY) { if (unlikely(!destroy)) { log_warn("Unmapping an already unmapped window %#010x %s twice", @@ -1811,3 +1826,38 @@ void map_win_by_id(session_t *ps, xcb_window_t id) { map_win(ps, w); } + +/** + * Find a window from window id in window linked list of the session. + */ +win *find_win(session_t *ps, xcb_window_t id) { + if (!id) { + return NULL; + } + + win *w = NULL; + HASH_FIND_INT(ps->windows, &id, w); + assert(w == NULL || w->state != WSTATE_DESTROYING); + return w; +} + +/** + * Find out the WM frame of a client window using existing data. + * + * @param id window ID + * @return struct win object of the found window, NULL if not found + */ +win *find_toplevel(session_t *ps, xcb_window_t id) { + if (!id) { + return NULL; + } + + HASH_ITER2(ps->windows, w) { + assert(w->state != WSTATE_DESTROYING); + if (w->client_win == id) { + return w; + } + } + + return NULL; +} diff --git a/src/win.h b/src/win.h index e0e253d9..c1376540 100644 --- a/src/win.h +++ b/src/win.h @@ -7,20 +7,25 @@ #include #include +#include "uthash_extra.h" + // FIXME shouldn't need this #ifdef CONFIG_OPENGL #include #endif +#include "backend/backend.h" #include "c2.h" #include "compiler.h" #include "region.h" #include "render.h" #include "types.h" #include "utils.h" -#include "backend/backend.h" #include "x.h" +#define WIN_STACK_ITER(ps, w) \ + for (win *w = ps->window_stack, *next; w ? (next = w->next, true) : false; w = next) + typedef struct session session_t; typedef struct _glx_texture glx_texture_t; @@ -285,6 +290,7 @@ struct win { /// Textures and FBO background blur use. glx_blur_cache_t glx_blur_cache; #endif + UT_hash_handle hh; }; void win_release_image(backend_t *base, win *w); @@ -392,6 +398,8 @@ void win_ev_stop(session_t *ps, const win *w); /// Skip the current in progress fading of window, /// transition the window straight to its end state void win_skip_fading(session_t *ps, win **_w); +win *find_win(session_t *ps, xcb_window_t id); +win *find_toplevel(session_t *ps, xcb_window_t id); /** * Get the leader of a window.