#include #include #include #include #include #include #include #include "log.h" #include "connect.h" #include "state.h" static bool query_xrm(Connection *con, const char *res, char **value) { if (xcb_xrm_resource_get_string(con->database, res, res, value) >= 0) { log_debug("Xrm query '%s' found '%s'", res, *value); return true; } log_debug("Xrm query '%s' not found", res); return false; } static void update_xrm(Connection *con) { xcb_flush(con->connection); xcb_xrm_database_t *database = xcb_xrm_database_from_default(con->connection); if (database == NULL) { log_warning("Xrm database couldn't be updated"); return; } xcb_xrm_database_free(con->database); con->database = database; log_debug("Xrm database updated"); } static void update_scale(Connection *con) { char *dpi_value; if (query_xrm(con, "Xft.dpi", &dpi_value)) { con->screen_dpi = strtod(dpi_value, NULL); g_free(dpi_value); } else { con->screen_dpi = (double)con->screen_size->height * 25.4 / (double)con->screen_size->mheight; log_debug("Fallback dpi value '%.2lf'", con->screen_dpi); } } // Check if point (px, py) is inside a rectangle in (x, y), (x+w, y), (x, y+h) and (w+h, y+h) static inline bool in_rect(int px, int py, int x, int y, int w, int h) { return px >= x && px <= x + w && py >= y && py <= y + h; } // Check if point (px, py) is inside a circle of radius r and center (x, y) static inline bool in_circle(int px, int py, int x, int y, int r) { int dx = x - px; int dy = y - py; return (dx * dx + dy * dy) <= r * r; } // Check if point (px, py) is inside a capsule in (x, y), (x+w, y), (x, y+h) and (w+h, y+h) static inline bool in_capsule(int px, int py, int x, int y, int w, int h) { g_assert(w >= h); int radius = h / 2; // Circle case if (w == h) return in_circle(px, py, x + radius, y + radius, radius); // Capsule case return in_circle(px, py, x + radius, y + radius, radius) || in_circle(px, py, x + w - radius, y + radius, radius) || in_rect(px, py, x + radius, y, w - 2 * radius, h); } static void button_action(State *state, const char *event, int x, int y) { log_debug("Checking %s event [x=%d, y=%d]", event, x, y); for (GList *it = state->draw->layouts; it != NULL; it = it->next) { Layout *layout = it->data; // Skip if (layout->x + layout->width < x) continue; if (layout->x > x) break; if (layout->btn->simple) { log_debug("Button layout [x=%d, y=%d, w=%d, h=%d]", layout->x, layout->y, layout->width, layout->height); if (in_capsule(x, y, layout->x, layout->y, layout->width, layout->height)) { Button *btn = layout->btn; ButtonAction action = button_simple_get_action(btn); // TODO: Button labels log_debug("Triggering action for button [button=%p]", btn); if (action != NULL) { // NOTE: Animations may change layout! //Animation *anim = animation_shine_create(300 * G_TIME_SPAN_MILLISECOND); Animation *anim = animation_pulse_create(300 * G_TIME_SPAN_MILLISECOND); if (button_set_animation(btn, anim)) state_request_animation(state); else animation_destroy(anim); action(btn); } return; } } else { log_debug("Button group layout [x=%d, y=%d, w=%d, h=%d]", layout->x, layout->y, layout->width, layout->height); } } log_debug("Ignoring %s", event); } typedef struct { GSource source; Connection *con; gpointer fd_tag; xcb_generic_event_t *event; } EventSource; static gboolean source_check(GSource *source) { EventSource *xsource = (EventSource *)source; xcb_flush(xsource->con->connection); g_assert_null(xsource->event); GIOCondition flags = g_source_query_unix_fd(source, xsource->fd_tag); if (flags & G_IO_IN) { g_assert_false(xcb_connection_has_error(xsource->con->connection)); xsource->event = xcb_poll_for_event(xsource->con->connection); } return xsource->event != NULL; } static gboolean source_dispatch(GSource *source, GSourceFunc callback, gpointer data) { EventSource *xsource = (EventSource *)source; g_assert_nonnull(xsource->event); do { switch (xsource->event->response_type & ~0x80) { case 0: { xcb_generic_error_t *error = (xcb_generic_error_t *)xsource->event; const char *extension; const char *name = xcb_errors_get_name_for_error(xsource->con->errors, error->error_code, &extension); const char *major = xcb_errors_get_name_for_major_code(xsource->con->errors, error->major_code); const char *minor = xcb_errors_get_name_for_minor_code(xsource->con->errors, error->major_code, error->minor_code); // TODO: Handle errors instead of aborting log_error("Xcb error '%s' [extension=%s, major=%s, minor=%s, resource=%u, sequence=%u]", name, extension ? extension : "none", major, minor ? minor : "none", (unsigned int)error->resource_id, (unsigned int)error->sequence); break; } case XCB_EXPOSE: { xcb_expose_event_t *expose = (xcb_expose_event_t *)xsource->event; log_debug("Processing event 'Expose' [type=%d]", XCB_EXPOSE); state_request_redraw(xsource->con->state, true); break; } case XCB_CREATE_NOTIFY: { xcb_create_notify_event_t *create = (xcb_create_notify_event_t *)xsource->event; log_debug("Processing event 'CreateNotify' [type=%d]", XCB_CREATE_NOTIFY); // TODO: Circulate top the window if override_redirect == 0 break; } case XCB_BUTTON_RELEASE: { xcb_button_release_event_t *button = (xcb_button_release_event_t *)xsource->event; log_debug("Processing event 'ButtonRelease' [type=%d]", XCB_BUTTON_RELEASE); // TODO: Handle different actions properly switch (button->detail) { case XCB_BUTTON_INDEX_2: // left click case XCB_BUTTON_INDEX_1: // right click case XCB_BUTTON_INDEX_3: // middle click button_action(xsource->con->state, "button release", button->event_x, button->event_y); break; default: log_debug("Ignoring button release [button=%d]", button->detail); break; } break; } // TODO: Implement correctly hovering case XCB_MOTION_NOTIFY: { xcb_motion_notify_event_t *motion = (xcb_motion_notify_event_t *)xsource->event; log_debug("Processing event 'MotionNotify' [type=%d]", XCB_MOTION_NOTIFY); button_action(xsource->con->state, "motion notify", motion->event_x, motion->event_y); break; } case XCB_PROPERTY_NOTIFY: { xcb_property_notify_event_t *property = (xcb_property_notify_event_t *)xsource->event; log_debug("Processing event 'PropertyNotify' [type=%d]", XCB_PROPERTY_NOTIFY); if (property->atom == XCB_ATOM_RESOURCE_MANAGER) { update_xrm(xsource->con); update_scale(xsource->con); state_request_redraw(xsource->con->state, true); } break; } default: { const char *extension; const char *name = xcb_errors_get_name_for_xcb_event(xsource->con->errors, xsource->event, &extension); // TODO: Handle XCB_RANDR_SCREEN_CHANGE_NOTIFY log_debug("Ignoring event '%s' [type=%d, extension=%s]", name, xsource->event->response_type & 0x7f, extension ? extension : "none"); } } } while ((xsource->event = xcb_poll_for_event(xsource->con->connection)) != NULL); return G_SOURCE_CONTINUE; } static GSourceFuncs source_fns = { NULL, source_check, source_dispatch, NULL, }; static void attach_source(Connection *con) { EventSource *source = (EventSource *)g_source_new(&source_fns, sizeof(EventSource)); con->source = (GSource *)source; g_source_set_static_name(con->source, "EventSource"); source->con = con; source->event = NULL; source->fd_tag = g_source_add_unix_fd(con->source, xcb_get_file_descriptor(con->connection), G_IO_IN | G_IO_HUP | G_IO_ERR); g_source_attach(con->source, NULL); } Connection *connect_create() { Connection *con = g_malloc0(sizeof(Connection)); g_assert_nonnull(con); int preferred_screen = 0; con->connection = xcb_connect(NULL, &preferred_screen); g_assert_true(con->connection != NULL && !xcb_connection_has_error(con->connection)); log_debug("Xcb connection established"); log_debug("Default screen '%d'", preferred_screen); xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(con->connection)); while (preferred_screen != 0 && iter.rem) { xcb_screen_next(&iter); preferred_screen--; } con->screen = iter.data; xcb_generic_error_t *error; xcb_randr_query_version_cookie_t version_cookie = xcb_randr_query_version(con->connection, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION); xcb_randr_query_version_reply_t *randr_version = xcb_randr_query_version_reply(con->connection, version_cookie, &error); g_assert_null(error); log_debug("RandR loaded [version=%d.%d]", randr_version->major_version, randr_version->minor_version); g_assert_cmpint(randr_version->major_version, >=, 1); xcb_randr_get_screen_info_cookie_t cookie = xcb_randr_get_screen_info(con->connection, con->screen->root); xcb_randr_get_screen_info_reply_t *info_reply = xcb_randr_get_screen_info_reply(con->connection, cookie, &error); g_assert_null(error); con->screen_size = xcb_randr_get_screen_info_sizes(info_reply); g_assert_nonnull(con->screen_size); log_debug("Screen size [width=%d, height=%d]", con->screen_size->width, con->screen_size->height); xcb_randr_select_input(con->connection, con->screen->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY); log_debug("Xcb searching 32 bit visual"); con->visual_type = xcb_aux_find_visual_by_attrs(con->screen, XCB_VISUAL_CLASS_TRUE_COLOR, 32); con->screen_depth = 32; if (con->visual_type == NULL) { // NOTE: 24 bit visuals don't have the alpha channel for transparency log_debug("Fallback to 24 bit visual"); con->visual_type = xcb_aux_find_visual_by_attrs(con->screen, XCB_VISUAL_CLASS_TRUE_COLOR, 24); con->screen_depth = 24; } g_assert_nonnull(con->visual_type); log_debug("Xcb visual type found [id=%u]", con->visual_type->visual_id); xcb_intern_atom_cookie_t *ewmh_cookie = xcb_ewmh_init_atoms(con->connection, &con->ewmh); g_assert_true(xcb_ewmh_init_atoms_replies(&con->ewmh, ewmh_cookie, &error)); g_assert_null(error); log_debug("Xcb ewmh initialized"); con->database = xcb_xrm_database_from_default(con->connection); g_assert_nonnull(con->database); update_scale(con); g_assert(xcb_errors_context_new(con->connection, &con->errors) == 0); log_debug("Xcb errors loaded"); xcb_flush(con->connection); log_debug("Xcb set up"); return con; } void connect_attach_source(Connection *con) { g_assert_null(con->source); attach_source(con); log_debug("Xcb event loop attached"); } void connect_attach_state(Connection *con, State *state) { g_assert_nonnull(state); con->state = state; log_debug("Attached state to event loop"); } void connect_destroy(Connection *con) { g_source_destroy(con->source); g_source_unref(con->source); xcb_ewmh_connection_wipe(&con->ewmh); xcb_errors_context_free(con->errors); xcb_xrm_database_free(con->database); xcb_disconnect(con->connection); g_free(con); } // vim: ts=4 sw=4 et