* xcb.c: Communicating with X
*
*/
+#include "common.h"
+
#include <xcb/xcb.h>
#include <xcb/xkb.h>
#include <xcb/xproto.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_cursor.h>
-#ifdef XCB_COMPAT
-#include "xcb_compat.h"
-#endif
-
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
-#include "common.h"
+#ifdef I3_ASAN_ENABLED
+#include <sanitizer/lsan_interface.h>
+#endif
+
#include "libi3.h"
/** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a
/* These are only relevant for XKB, which we only need for grabbing modifiers */
int xkb_base;
-int mod_pressed = 0;
+bool mod_pressed = 0;
/* Event watchers, to interact with the user */
ev_prepare *xcb_prep;
-ev_check *xcb_chk;
ev_io *xcb_io;
ev_io *xkb_io;
uint32_t center_x = x - sep_offset;
if (config.separator_symbol == NULL) {
/* Draw a classic one pixel, vertical separator. */
- draw_util_rectangle(xcb_connection, &output->statusline_buffer, sep_fg,
+ draw_util_rectangle(&output->statusline_buffer, sep_fg,
center_x,
logical_px(sep_voff_px),
logical_px(1),
struct status_block *block;
color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
- draw_util_clear_surface(xcb_connection, &output->statusline_buffer, bar_color);
+ draw_util_clear_surface(&output->statusline_buffer, bar_color);
/* Use unsigned integer wraparound to clip off the left side.
* For example, if clip_left is 75, then x will start at the very large
}
/* Draw the border. */
- draw_util_rectangle(xcb_connection, &output->statusline_buffer, border_color,
+ draw_util_rectangle(&output->statusline_buffer, border_color,
x, logical_px(1),
full_render_width,
bar_height - logical_px(2));
/* Draw the background. */
- draw_util_rectangle(xcb_connection, &output->statusline_buffer, bg_color,
+ draw_util_rectangle(&output->statusline_buffer, bg_color,
x + border_width,
logical_px(1) + border_width,
full_render_width - 2 * border_width,
xcb_flush(xcb_connection);
}
+static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) {
+ binding_t *binding;
+ TAILQ_FOREACH(binding, &(config.bindings), bindings) {
+ if ((binding->input_code != input_code) || (binding->release != event_is_release))
+ continue;
+
+ i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
+ return true;
+ }
+ return false;
+}
+
/*
* 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
*
*/
return;
}
- int32_t x = event->event_x >= 0 ? event->event_x : 0;
-
DLOG("Got button %d\n", event->detail);
+ /* During button release events, only check for custom commands. */
+ const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE;
+ if (event_is_release) {
+ execute_custom_command(event->detail, event_is_release);
+ return;
+ }
+
+ int32_t x = event->event_x >= 0 ? event->event_x : 0;
int workspace_width = 0;
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder;
if (statusline_x <= block_x && statusline_x >= last_block_x) {
- send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
+ send_block_clicked(event->detail, block->name, block->instance,
+ event->root_x, event->root_y, statusline_x - last_block_x, event->event_y, block_x - last_block_x, bar_height);
return;
}
/* If a custom command was specified for this mouse button, it overrides
* the default behavior. */
- binding_t *binding;
- TAILQ_FOREACH(binding, &(config.bindings), bindings) {
- if (binding->input_code != event->detail)
- continue;
-
- i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, binding->command);
+ if (execute_custom_command(event->detail, event_is_release)) {
return;
}
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
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
buffer[outpos] = utf8_name[inpos];
}
buffer[outpos] = '"';
- i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
+ i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
free(buffer);
}
*
*/
static void handle_client_message(xcb_client_message_event_t *event) {
- if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
- event->format == 32) {
+ if (event->type == atoms[I3_SYNC]) {
+ xcb_window_t window = event->data.data32[0];
+ uint32_t rnd = event->data.data32[1];
+ /* Forward the request to i3 via the IPC interface so that all pending
+ * IPC messages are guaranteed to be handled. */
+ char *payload = NULL;
+ sasprintf(&payload, "{\"rnd\":%d, \"window\":%d}", rnd, window);
+ i3_send_msg(I3_IPC_MESSAGE_TYPE_SYNC, payload);
+ free(payload);
+ } else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
+ event->format == 32) {
DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
/* event->data.data32[0] is the timestamp */
uint32_t op = event->data.data32[1];
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
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;
break;
}
}
+
if (output == NULL) {
ELOG("No output found\n");
return;
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,
* client to finish the protocol. After this event is received, there is no
* further interaction with the tray client.
*
- * See: http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
+ * See: https://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
*
*/
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();
}
/*
- * 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) {
continue;
clients++;
- if (trayclient->win != event->window)
+ if (trayclient->win != window)
continue;
xcb_rectangle_t rect;
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;
}
}
DLOG("WARNING: Could not find corresponding tray window.\n");
}
-/*
- * This function is called immediately before the main loop locks. We flush xcb
- * then (and only then)
- *
- */
-void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
- xcb_flush(xcb_connection);
+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 after the main loop locks, so when one
- * of the watchers registered an event.
- * We check whether an X-Event arrived and handle it.
+ * This function is called immediately before the main loop locks. We check for
+ * events from X11, handle them, then flush our outgoing queue.
*
*/
-void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
+void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
xcb_generic_event_t *event;
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);
}
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) {
- int modstate = state->mods & config.modifier;
-
-#define DLOGMOD(modmask, status) \
- do { \
- switch (modmask) { \
- case ShiftMask: \
- DLOG("ShiftMask got " #status "!\n"); \
- break; \
- case ControlMask: \
- DLOG("ControlMask got " #status "!\n"); \
- break; \
- case Mod1Mask: \
- DLOG("Mod1Mask got " #status "!\n"); \
- break; \
- case Mod2Mask: \
- DLOG("Mod2Mask got " #status "!\n"); \
- break; \
- case Mod3Mask: \
- DLOG("Mod3Mask got " #status "!\n"); \
- break; \
- case Mod4Mask: \
- DLOG("Mod4Mask got " #status "!\n"); \
- break; \
- case Mod5Mask: \
- DLOG("Mod5Mask got " #status "!\n"); \
- break; \
- } \
- } while (0)
-
- if (modstate != mod_pressed) {
- if (modstate == 0) {
- DLOGMOD(config.modifier, released);
- if (!activated_mode)
- hide_bars();
- } else {
- DLOGMOD(config.modifier, pressed);
- activated_mode = false;
- unhide_bars();
- }
- mod_pressed = modstate;
+ const uint32_t mod = (config.modifier & 0xFFFF);
+ mod_pressed = (mod != 0 && (state->mods & mod) == mod);
+ if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) {
+ if (mod_pressed) {
+ activated_mode = false;
+ unhide_bars();
+ } else if (!activated_mode) {
+ hide_bars();
}
-#undef DLOGMOD
}
free(event);
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_RELEASE:
case XCB_BUTTON_PRESS:
/* Button press events are mouse buttons clicked on one of our bars */
handle_button((xcb_button_press_event_t *)event);
/* 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);
}
+
+ xcb_flush(xcb_connection);
}
/*
/* The various watchers to communicate with xcb */
xcb_io = smalloc(sizeof(ev_io));
xcb_prep = smalloc(sizeof(ev_prepare));
- xcb_chk = smalloc(sizeof(ev_check));
ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
ev_prepare_init(xcb_prep, &xcb_prep_cb);
- ev_check_init(xcb_chk, &xcb_chk_cb);
ev_io_start(main_loop, xcb_io);
ev_prepare_start(main_loop, xcb_prep);
- ev_check_start(main_loop, xcb_chk);
/* Now we get the atoms and save them in a nice data structure */
get_atoms();
return;
}
+ free(selreply);
+
send_tray_clientmessage();
}
*
*/
void clean_xcb(void) {
- i3_output *o_walk;
- free_workspaces();
- SLIST_FOREACH(o_walk, outputs, slist) {
- destroy_window(o_walk);
- FREE(o_walk->trayclients);
- FREE(o_walk->workspaces);
- FREE(o_walk->name);
- }
- FREE_SLIST(outputs, i3_output);
- FREE(outputs);
+ free_outputs();
+
+ free_font();
xcb_free_cursor(xcb_connection, cursor);
xcb_flush(xcb_connection);
xcb_aux_sync(xcb_connection);
xcb_disconnect(xcb_connection);
- ev_check_stop(main_loop, xcb_chk);
ev_prepare_stop(main_loop, xcb_prep);
ev_io_stop(main_loop, xcb_io);
- FREE(xcb_chk);
FREE(xcb_prep);
FREE(xcb_io);
}
/* 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.
* */
values[3] = XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
- XCB_EVENT_MASK_BUTTON_PRESS;
+ XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE;
if (config.hide_on_modifier == M_DOCK) {
/* If the bar is normally visible, catch visibility change events to suspend
* the status process when the bar is obscured by full-screened windows. */
}
/* 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. */
bool use_focus_colors = output_has_focus(outputs_walk);
/* First things first: clear the backbuffer */
- draw_util_clear_surface(xcb_connection, &(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;
}
/* Draw the border of the button. */
- draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), border_color,
+ draw_util_rectangle(&(outputs_walk->buffer), border_color,
workspace_width,
logical_px(1),
ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
/* Draw the inside of the button. */
- draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color,
+ draw_util_rectangle(&(outputs_walk->buffer), bg_color,
workspace_width + logical_px(1),
2 * logical_px(1),
ws_walk->name_width + 2 * logical_px(ws_hoff_px),
color_t fg_color = colors.binding_mode_fg;
color_t bg_color = colors.binding_mode_bg;
- draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), colors.binding_mode_border,
+ draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border,
workspace_width,
logical_px(1),
binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
- draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color,
+ draw_util_rectangle(&(outputs_walk->buffer), bg_color,
workspace_width + logical_px(1),
2 * logical_px(1),
binding.width + 2 * logical_px(ws_hoff_px),
int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width;
draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
- draw_util_copy_surface(xcb_connection, &outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
+ draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
x_dest, 0, visible_statusline_width, (int16_t)bar_height);
outputs_walk->statusline_width = statusline_width;
continue;
}
- draw_util_copy_surface(xcb_connection, &(outputs_walk->buffer), &(outputs_walk->bar), 0, 0,
+ draw_util_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0,
0, 0, outputs_walk->rect.w, outputs_walk->rect.h);
xcb_flush(xcb_connection);
}