+/* Custom data structure used to track dragging-related events. */
+struct drag_x11_cb {
+ ev_prepare prepare;
+
+ /* Whether this modal event loop should be exited and with which result. */
+ drag_result_t result;
+
+ /* The container that is being dragged or resized, or NULL if this is a
+ * drag of the resize handle. */
+ Con *con;
+
+ /* The dimensions of con when the loop was started. */
+ Rect old_rect;
+
+ /* The callback to invoke after every pointer movement. */
+ callback_t callback;
+
+ /* User data pointer for callback. */
+ const void *extra;
+};
+
+static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
+ xcb_motion_notify_event_t *last_motion_notify = NULL;
+ xcb_generic_event_t *event;
+
+ while ((event = xcb_poll_for_event(conn)) != NULL) {
+ if (event->response_type == 0) {
+ xcb_generic_error_t *error = (xcb_generic_error_t *)event;
+ DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
+ error->sequence, error->error_code);
+ free(event);
+ continue;
+ }
+
+ /* Strip off the highest bit (set if the event is generated) */
+ int type = (event->response_type & 0x7F);
+
+ switch (type) {
+ case XCB_BUTTON_RELEASE:
+ dragloop->result = DRAG_SUCCESS;
+ break;
+
+ case XCB_KEY_PRESS:
+ DLOG("A key was pressed during drag, reverting changes.\n");
+ dragloop->result = DRAG_REVERT;
+ handle_event(type, event);
+ break;
+
+ case XCB_UNMAP_NOTIFY: {
+ xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t *)event;
+ Con *con = con_by_window_id(unmap_event->window);
+
+ if (con != NULL) {
+ DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con);
+
+ if (con_get_workspace(con) == con_get_workspace(focused)) {
+ DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
+ dragloop->result = DRAG_ABORT;
+ }
+ }
+
+ handle_event(type, event);
+ break;
+ }
+
+ case XCB_MOTION_NOTIFY:
+ /* motion_notify events are saved for later */
+ FREE(last_motion_notify);
+ last_motion_notify = (xcb_motion_notify_event_t *)event;
+ break;
+
+ default:
+ DLOG("Passing to original handler\n");
+ handle_event(type, event);
+ break;
+ }
+
+ if (last_motion_notify != (xcb_motion_notify_event_t *)event)
+ free(event);
+
+ if (dragloop->result != DRAGGING) {
+ ev_break(EV_A_ EVBREAK_ONE);
+ if (dragloop->result == DRAG_SUCCESS) {
+ /* Ensure motion notify events are handled. */
+ break;
+ } else {
+ free(last_motion_notify);
+ return true;
+ }
+ }
+ }
+
+ if (last_motion_notify == NULL) {
+ return true;
+ }
+
+ /* Ensure that we are either dragging the resize handle (con is NULL) or that the
+ * container still exists. The latter might not be true, e.g., if the window closed
+ * for any reason while the user was dragging it. */
+ if (!dragloop->con || con_exists(dragloop->con)) {
+ dragloop->callback(
+ dragloop->con,
+ &(dragloop->old_rect),
+ last_motion_notify->root_x,
+ last_motion_notify->root_y,
+ dragloop->extra);
+ }
+ FREE(last_motion_notify);
+
+ xcb_flush(conn);
+ return dragloop->result != DRAGGING;
+}
+
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+ struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+ while (!drain_drag_events(EV_A, dragloop)) {
+ /* repeatedly drain events: draining might produce additional ones */
+ }
+}
+