X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=i3bar%2Fsrc%2Fxcb.c;h=2ba446b1176be8596d34d2b3b7c6e19287dcd19a;hb=8106ae7923a4a94949435b65aca0aad303215432;hp=92b0d1abddc470af476d5139c84940a3bfa1a0be;hpb=bb7a36e0c7c8534a008bc569af4e1708c4865fe1;p=i3%2Fi3 diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 92b0d1ab..2ba446b1 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -7,14 +7,13 @@ * xcb.c: Communicating with X * */ +#include "common.h" + #include #include #include #include - -#ifdef XCB_COMPAT -#include "xcb_compat.h" -#endif +#include #include #include @@ -31,9 +30,16 @@ #include #include -#include "common.h" +#ifdef I3_ASAN_ENABLED +#include +#endif + #include "libi3.h" +/** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a + * constant for that. */ +#define XCB_CURSOR_LEFT_PTR 68 + /* We save the atoms in an easy to access array, indexed by an enum */ enum { #define ATOM_DO(name) name, @@ -49,6 +55,7 @@ xcb_connection_t *xcb_connection; int screen; xcb_screen_t *root_screen; xcb_window_t xcb_root; +static xcb_cursor_t cursor; /* selection window for tray support */ static xcb_window_t selwin = XCB_NONE; @@ -435,7 +442,7 @@ void init_colors(const struct xcb_color_strings_t *new_colors) { /* * Handle a button press event (i.e. a mouse click on one of our bars). - * We determine, whether the click occured on a workspace button or if the scroll- + * We determine, whether the click occurred on a workspace button or if the scroll- * wheel was used and change the workspace appropriately * */ @@ -524,7 +531,8 @@ void handle_button(xcb_button_press_event_t *event) { return; } switch (event->detail) { - case 4: + case XCB_BUTTON_SCROLL_UP: + case XCB_BUTTON_SCROLL_LEFT: /* Mouse wheel up. We select the previous ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end @@ -534,7 +542,8 @@ void handle_button(xcb_button_press_event_t *event) { cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq); break; - case 5: + case XCB_BUTTON_SCROLL_DOWN: + case XCB_BUTTON_SCROLL_RIGHT: /* Mouse wheel down. We select the next ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end @@ -679,15 +688,17 @@ static void handle_client_message(xcb_client_message_event_t *event) { if (op == SYSTEM_TRAY_REQUEST_DOCK) { xcb_window_t client = event->data.data32[2]; - /* Listen for PropertyNotify events to get the most recent value of - * the XEMBED_MAPPED atom, also listen for UnmapNotify events */ mask = XCB_CW_EVENT_MASK; - values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | - XCB_EVENT_MASK_STRUCTURE_NOTIFY; - xcb_change_window_attributes(xcb_connection, - client, - mask, - values); + + /* Needed to get the most recent value of XEMBED_MAPPED. */ + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; + /* Needed for UnmapNotify events. */ + values[0] |= XCB_EVENT_MASK_STRUCTURE_NOTIFY; + /* Needed because some tray applications (e.g., VLC) use + * override_redirect which causes no ConfigureRequest to be sent. */ + values[0] |= XCB_EVENT_MASK_RESIZE_REDIRECT; + + xcb_change_window_attributes(xcb_connection, client, mask, values); /* Request the _XEMBED_INFO property. The XEMBED specification * (which is referred by the tray specification) says this *has* to @@ -759,19 +770,9 @@ static void handle_client_message(xcb_client_message_event_t *event) { break; } - /* Check whether any "tray_output primary" was defined for this bar. */ - bool contains_primary = false; - TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) { - if (strcasecmp("primary", tray_output->output) == 0) { - contains_primary = true; - break; - } - } - - /* In case of tray_output == primary and there is no primary output - * configured, we fall back to the first available output. We do the - * same if no tray_output was specified. */ - if (output == NULL && (contains_primary || TAILQ_EMPTY(&(config.tray_outputs)))) { + /* If no tray_output has been specified, we fall back to the first + * available output. */ + if (output == NULL && TAILQ_EMPTY(&(config.tray_outputs))) { SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; @@ -780,6 +781,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { break; } } + if (output == NULL) { ELOG("No output found\n"); return; @@ -814,7 +816,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { ev->type = atoms[_XEMBED]; ev->format = 32; ev->data.data32[0] = XCB_CURRENT_TIME; - ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY]; + ev->data.data32[1] = XEMBED_EMBEDDED_NOTIFY; ev->data.data32[2] = output->bar.id; ev->data.data32[3] = xe_version; xcb_send_event(xcb_connection, @@ -870,11 +872,13 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) { DLOG("checking output %s\n", walk->name); trayclient *trayclient; TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { - if (trayclient->win != event->window) + if (trayclient->win != event->window) { continue; + } DLOG("Removing tray client with window ID %08x\n", event->window); TAILQ_REMOVE(walk->trayclients, trayclient, tailq); + FREE(trayclient); /* Trigger an update, we now have more space for the statusline */ configure_trayclients(); @@ -1007,13 +1011,11 @@ static void handle_property_notify(xcb_property_notify_event_t *event) { } /* - * Handle ConfigureRequests by denying them and sending the client a - * ConfigureNotify with its actual size. + * If a tray client attempts to change its size we deny the request and respond + * by telling it its actual size. * */ -static void handle_configure_request(xcb_configure_request_event_t *event) { - DLOG("ConfigureRequest for window = %08x\n", event->window); - +static void handle_configuration_change(xcb_window_t window) { trayclient *trayclient; i3_output *output; SLIST_FOREACH(output, outputs, slist) { @@ -1026,7 +1028,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { continue; clients++; - if (trayclient->win != event->window) + if (trayclient->win != window) continue; xcb_rectangle_t rect; @@ -1036,7 +1038,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { rect.height = icon_size; DLOG("This is a tray window. x = %d\n", rect.x); - fake_configure_notify(xcb_connection, rect, event->window, 0); + fake_configure_notify(xcb_connection, rect, window, 0); return; } } @@ -1044,6 +1046,16 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { DLOG("WARNING: Could not find corresponding tray window.\n"); } +static void handle_configure_request(xcb_configure_request_event_t *event) { + DLOG("ConfigureRequest for window = %08x\n", event->window); + handle_configuration_change(event->window); +} + +static void handle_resize_request(xcb_resize_request_event_t *event) { + DLOG("ResizeRequest for window = %08x\n", event->window); + handle_configuration_change(event->window); +} + /* * This function is called immediately before the main loop locks. We flush xcb * then (and only then) @@ -1064,6 +1076,9 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { if (xcb_connection_has_error(xcb_connection)) { ELOG("X11 connection was closed unexpectedly - maybe your X server terminated / crashed?\n"); +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif exit(1); } @@ -1081,7 +1096,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { DLOG("received an xkb event\n"); xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event; - if (state->xkbType == XCB_XKB_STATE_NOTIFY) { + if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) { int modstate = state->mods & config.modifier; #define DLOGMOD(modmask, status) \ @@ -1136,8 +1151,11 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { handle_visibility_notify((xcb_visibility_notify_event_t *)event); break; case XCB_EXPOSE: - /* Expose-events happen, when the window needs to be redrawn */ - redraw_bars(); + if (((xcb_expose_event_t *)event)->count == 0) { + /* Expose-events happen, when the window needs to be redrawn */ + redraw_bars(); + } + break; case XCB_BUTTON_PRESS: /* Button press events are mouse buttons clicked on one of our bars */ @@ -1167,6 +1185,10 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { /* ConfigureRequest, sent by a tray child */ handle_configure_request((xcb_configure_request_event_t *)event); break; + case XCB_RESIZE_REQUEST: + /* ResizeRequest sent by a tray child using override_redirect. */ + handle_resize_request((xcb_resize_request_event_t *)event); + break; } free(event); } @@ -1206,6 +1228,24 @@ char *init_xcb_early() { colormap = root_screen->default_colormap; visual_type = get_visualtype(root_screen); + xcb_cursor_context_t *cursor_ctx; + if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) == 0) { + cursor = xcb_cursor_load_cursor(cursor_ctx, "left_ptr"); + xcb_cursor_context_free(cursor_ctx); + } else { + cursor = xcb_generate_id(xcb_connection); + i3Font cursor_font = load_font("cursor", false); + xcb_create_glyph_cursor( + xcb_connection, + cursor, + cursor_font.specific.xcb.id, + cursor_font.specific.xcb.id, + XCB_CURSOR_LEFT_PTR, + XCB_CURSOR_LEFT_PTR + 1, + 0, 0, 0, + 65535, 65535, 65535); + } + /* The various watchers to communicate with xcb */ xcb_io = smalloc(sizeof(ev_io)); xcb_prep = smalloc(sizeof(ev_prepare)); @@ -1215,6 +1255,12 @@ char *init_xcb_early() { ev_prepare_init(xcb_prep, &xcb_prep_cb); ev_check_init(xcb_chk, &xcb_chk_cb); + /* Within an event loop iteration, run the xcb_chk watcher last: other + * watchers might call xcb_flush(), which, unexpectedly, can also read + * events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s + * queue last, otherwise we risk dead-locking. */ + ev_set_priority(xcb_chk, EV_MINPRI); + ev_io_start(main_loop, xcb_io); ev_prepare_start(main_loop, xcb_prep); ev_check_start(main_loop, xcb_chk); @@ -1401,6 +1447,8 @@ void init_tray(void) { return; } + free(selreply); + send_tray_clientmessage(); } @@ -1462,6 +1510,9 @@ void clean_xcb(void) { FREE_SLIST(outputs, i3_output); FREE(outputs); + free_font(); + + xcb_free_cursor(xcb_connection, cursor); xcb_flush(xcb_connection); xcb_aux_sync(xcb_connection); xcb_disconnect(xcb_connection); @@ -1521,6 +1572,7 @@ void kick_tray_clients(i3_output *output) { /* We remove the trayclient right here. We might receive an UnmapNotify * event afterwards, but better safe than sorry. */ TAILQ_REMOVE(output->trayclients, trayclient, tailq); + FREE(trayclient); } /* Fake a DestroyNotify so that Qt re-adds tray icons. @@ -1605,7 +1657,7 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) { */ void reconfig_windows(bool redraw_bars) { uint32_t mask; - uint32_t values[5]; + uint32_t values[6]; static bool tray_configured = false; i3_output *walk; @@ -1623,7 +1675,7 @@ void reconfig_windows(bool redraw_bars) { xcb_window_t bar_id = xcb_generate_id(xcb_connection); xcb_pixmap_t buffer_id = xcb_generate_id(xcb_connection); xcb_pixmap_t statusline_buffer_id = xcb_generate_id(xcb_connection); - mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; + mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP | XCB_CW_CURSOR; values[0] = colors.bar_bg.colorpixel; values[1] = root_screen->black_pixel; @@ -1645,6 +1697,7 @@ void reconfig_windows(bool redraw_bars) { walk->visible = true; } values[4] = colormap; + values[5] = cursor; xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection, depth, @@ -1709,9 +1762,9 @@ void reconfig_windows(bool redraw_bars) { 1, (unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]); - draw_util_surface_init(&walk->bar, bar_id, walk->rect.w, bar_height); - draw_util_surface_init(&walk->buffer, buffer_id, walk->rect.w, bar_height); - draw_util_surface_init(&walk->statusline_buffer, statusline_buffer_id, walk->rect.w, bar_height); + draw_util_surface_init(xcb_connection, &walk->bar, bar_id, NULL, walk->rect.w, bar_height); + draw_util_surface_init(xcb_connection, &walk->buffer, buffer_id, NULL, walk->rect.w, bar_height); + draw_util_surface_init(xcb_connection, &walk->statusline_buffer, statusline_buffer_id, NULL, walk->rect.w, bar_height); xcb_void_cookie_t strut_cookie = config_strut_partial(walk); @@ -1733,16 +1786,35 @@ void reconfig_windows(bool redraw_bars) { } /* Unless "tray_output none" was specified, we need to initialize the tray. */ - const char *first = (TAILQ_EMPTY(&(config.tray_outputs))) ? SLIST_FIRST(outputs)->name : TAILQ_FIRST(&(config.tray_outputs))->output; - if (!tray_configured && strcasecmp(first, "none") != 0) { - /* We do a sanity check here to ensure that this i3bar instance actually handles - * the output on which the tray should appear. For example, - * consider tray_output == [VGA-1], but output == [HDMI-1]. */ + bool no_tray = false; + if (!(TAILQ_EMPTY(&(config.tray_outputs)))) { + no_tray = strcasecmp(TAILQ_FIRST(&(config.tray_outputs))->output, "none") == 0; + } + /* + * There are three scenarios in which we need to initialize the tray: + * 1. A specific output was listed in tray_outputs which is also + * in the list of outputs managed by this bar. + * 2. No tray_output directive was specified. In this case, we + * use the first available output. + * 3. 'tray_output primary' was specified. In this case we use the + * primary output. + * + * Three scenarios in which we specifically don't want to + * initialize the tray are: + * 1. 'tray_output none' was specified. + * 2. A specific output was listed as a tray_output, but is not + * one of the outputs managed by this bar. For example, consider + * tray_outputs == [VGA-1], but outputs == [HDMI-1]. + * 3. 'tray_output primary' was specified and no output in the list + * is primary. + */ + if (!tray_configured && !no_tray) { /* If no tray_output was specified, we go ahead and initialize the tray as * we will be using the first available output. */ - if (TAILQ_EMPTY(&(config.tray_outputs))) + if (TAILQ_EMPTY(&(config.tray_outputs))) { init_tray(); + } /* If one or more tray_output assignments were specified, we ensure that at least one of * them is actually an output managed by this instance. */ @@ -1820,12 +1892,12 @@ void reconfig_windows(bool redraw_bars) { walk->rect.w, bar_height); - draw_util_surface_free(&(walk->bar)); - draw_util_surface_free(&(walk->buffer)); - draw_util_surface_free(&(walk->statusline_buffer)); - draw_util_surface_init(&(walk->bar), walk->bar.id, walk->rect.w, bar_height); - draw_util_surface_init(&(walk->buffer), walk->buffer.id, walk->rect.w, bar_height); - draw_util_surface_init(&(walk->statusline_buffer), walk->statusline_buffer.id, walk->rect.w, bar_height); + draw_util_surface_free(xcb_connection, &(walk->bar)); + draw_util_surface_free(xcb_connection, &(walk->buffer)); + draw_util_surface_free(xcb_connection, &(walk->statusline_buffer)); + draw_util_surface_init(xcb_connection, &(walk->bar), walk->bar.id, NULL, walk->rect.w, bar_height); + draw_util_surface_init(xcb_connection, &(walk->buffer), walk->buffer.id, NULL, walk->rect.w, bar_height); + draw_util_surface_init(xcb_connection, &(walk->statusline_buffer), walk->statusline_buffer.id, NULL, walk->rect.w, bar_height); xcb_void_cookie_t map_cookie, umap_cookie; if (redraw_bars) { @@ -1886,8 +1958,7 @@ void draw_bars(bool unhide) { bool use_focus_colors = output_has_focus(outputs_walk); /* First things first: clear the backbuffer */ - draw_util_clear_surface(&(outputs_walk->buffer), - (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg)); + draw_util_clear_surface(&(outputs_walk->buffer), (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg)); if (!config.disable_ws) { i3_ws *ws_walk;