#include #include "any_log.h" #include "util.h" #include "layout.h" #include "event.h" #include "block.h" #include "action.h" const char *event_type_to_string(event_type_t type) { const char *types[EVENT_LENGTH] = { "trigger", "left_click", "middle_click", "right_click", "scroll_up", "scroll_down", "hover_start", "hover_move", "hover_stop", }; assert(type >= EVENT_TRIGGER && type < EVENT_LENGTH); return types[type]; } bool event_is_trigger(event_t event) { return event.type == EVENT_TRIGGER; } bool event_is_click(event_t event) { return event.type == EVENT_LEFT_CLICK || event.type == EVENT_MIDDLE_CLICK || event.type == EVENT_RIGHT_CLICK; } bool event_is_scroll(event_t event) { return event.type == EVENT_SCROLL_UP || event.type == EVENT_SCROLL_DOWN; } bool event_is_hover(event_t event) { return event.type == EVENT_HOVER_START || event.type == EVENT_HOVER_MOVE || event.type == EVENT_HOVER_STOP; } static layout_t *event_dispatch_find(block_t *block, layout_t *layout) { if (layout->block == block) return layout; for (size_t i = 0; i < layout->n_children; i++) { layout_t *l = event_dispatch_find(block, &layout->children[i]); if (l != NULL) return l; } return NULL; } static void event_dispatch_callback(event_state_t *state, layout_t *layout, event_t event) { block_event_t event_fn = layout->block->type == BLOCK_SPEC ? ((block_spec_t *)layout->block)->event_fn : NULL; log_value_debug("Block received an event", "s:event", event_type_to_string(event.type), "i:event_x", event.x, "i:event_y", event.y, "s:label", layout->block->label, "i:x", layout->x, "i:y", layout->y, "i:width", layout->width, "i:height", layout->height, "b:action", layout->block->actions != NULL, "b:callback", event_fn != NULL); if (layout->block->actions != NULL) { action_perform(layout->block->actions, layout->block, state->config); } if (event_fn != NULL) { event_fn(layout, event); log_trace("Completed event callback"); } } static void event_dispatch_callback_block(event_state_t *state, block_t *block, event_t event) { layout_t tmp = { .block = block, }; // XXX: As a workaround we are making a mock layout when we have to update an hidden block // Fix this ugliness please // layout_t *l = block->hidden ? &tmp : event_dispatch_find(block, state->layout); event_dispatch_callback(state, l, event); } static bool event_dispatch_mouse(event_state_t *state, layout_t *layout, event_t event) { int width = layout->width - 2 * layout->x_padding; int height = layout->height - 2 * layout->y_padding; if (!check_capsule(event.x, event.y, layout->x, layout->y, width, height)) return false; for (size_t i = 0; i < layout->n_children; i++) { if (event_dispatch_mouse(state, &layout->children[i], event)) return true; } if (event_is_hover(event)) { // Compare only the part related to position and size // bool update = !memcmp(&state->hovered, layout, offsetof(layout_t, line_width)) && event.x == state->hover_x && event.y == state->hover_y; state->hover_x = event.x; state->hover_y = event.y; if (layout->block != state->hovered.block) { if (state->hovered.block != NULL) { event.type = EVENT_HOVER_STOP; event_dispatch_callback_block(state, state->hovered.block, event); } event.type = EVENT_HOVER_START; memcpy(&state->hovered, layout, sizeof(layout_t)); } else if (update) { // Ignore a move with equal cursor position // return true; } } event_dispatch_callback(state, layout, event); return true; } void event_dispatch(event_state_t *state, window_t *window) { // Check last cursor position again // if (state->hovered.block != NULL) { event_t event = { .type = EVENT_HOVER_MOVE, .x = state->hover_x, .y = state->hover_y, }; event_dispatch_mouse(state, state->layout, event); } xcb_generic_event_t *xevent; xcb_motion_notify_event_t *motion = NULL; while ((xevent = xcb_poll_for_event(window->display->connection)) != NULL) { int type = xevent->response_type & ~0x80; switch (type) { case 0: { xcb_generic_error_t *error = (xcb_generic_error_t *)xevent; const char *extension = NULL; const char *name = xcb_errors_get_name_for_error(window->display->errors, error->error_code, &extension); const char *major = xcb_errors_get_name_for_major_code(window->display->errors, error->major_code); const char *minor = xcb_errors_get_name_for_minor_code(window->display->errors, error->major_code, error->minor_code); log_value_error("Xcb error", "s:name", name, "s:extension", extension ? extension : "none", "s:major", major, "s:minor", minor ? minor : "none", "u:resource", (unsigned int)error->resource_id, "u:sequence", (unsigned int)error->sequence); // TODO: Handle errors instead of aborting abort(); break; } case XCB_EXPOSE: { //xcb_expose_event_t *expose = (xcb_expose_event_t *)xevent; log_trace("Processing 'Expose' event"); // XXX: Redraw when rendering ondemand break; } case XCB_CREATE_NOTIFY: { //xcb_create_notify_event_t *create = (xcb_create_notify_event_t *)xevent; log_trace("Processing 'CreateNotify' event"); if (window->cw_params.override_redirect) break; xcb_circulate_window(window->display->connection, XCB_CIRCULATE_RAISE_LOWEST, window->window); break; } case XCB_LEAVE_NOTIFY: { xcb_leave_notify_event_t *leave = (xcb_leave_notify_event_t *)xevent; log_trace("Processing 'LeaveNotify' event"); if (leave->event != window->window) break; if (state->hovered.block == NULL) break; event_t event = { .type = EVENT_HOVER_STOP, .x = leave->event_x, .y = leave->event_y, }; event_dispatch_callback_block(state, state->hovered.block, event); memset(&state->hovered, 0, sizeof(layout_t)); if (motion != NULL && motion->time <= leave->time) { log_trace("Suppressed older 'MotionNotify' event"); free(motion); motion = NULL; } break; } case XCB_BUTTON_RELEASE: { xcb_button_release_event_t *button = (xcb_button_release_event_t *)xevent; log_trace("Processing 'ButtonRelease' event"); if (button->detail < XCB_BUTTON_INDEX_1 || button->detail > XCB_BUTTON_INDEX_5) { log_value_trace("Ignoring button release", "u:value", button->detail); break; } // NOTE: The order of event_type_t is important! event_t event = { .type = button->detail, .x = button->event_x, .y = button->event_y, }; event_dispatch_mouse(state, state->layout, event); break; } case XCB_PROPERTY_NOTIFY: { xcb_property_notify_event_t *property = (xcb_property_notify_event_t *)xevent; log_trace("Processing 'PropertyNotify' event"); if (property->atom == XCB_ATOM_RESOURCE_MANAGER) { display_update_xrm(window->display); display_update_scale(window->display); // XXX: Redraw when rendering ondemand } break; } case XCB_MOTION_NOTIFY: { log_trace("Deferred processing 'MotionNotify' event"); free(motion); motion = (xcb_motion_notify_event_t *)xevent; continue; } default: { const char *extension = NULL; const char *name = xcb_errors_get_name_for_xcb_event(window->display->errors, xevent, &extension); log_value_trace("Ignoring Xcb event", "s:name", name, "u:type", type, "s:extension", extension ? extension : "none"); break; } } free(xevent); } // We only consider the last motion notify received // if (motion != NULL) { log_trace("Processing 'MotionNotify' event"); event_t event = { .type = EVENT_HOVER_MOVE, .x = motion->event_x, .y = motion->event_y, }; event_dispatch_mouse(state, state->layout, event); free(motion); } }