diff options
Diffstat (limited to 'src/connect.c')
| -rw-r--r-- | src/connect.c | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/src/connect.c b/src/connect.c new file mode 100644 index 0000000..0ef12f9 --- /dev/null +++ b/src/connect.c @@ -0,0 +1,297 @@ +#include <glib.h> +#include <xcb/xcb.h> +#include <xcb/xcb_aux.h> +#include <xcb/xcb_ewmh.h> +#include <xcb/xcb_xrm.h> +#include <xcb/xcb_errors.h> +#include <xcb/randr.h> + +#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); + } +} + +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 void redraw_window(Connection *con, xcb_window_t id) +{ + for (int i = 0; i < con->states->len; ++i) { + State *state = con->states->pdata[i]; + + // Redraw matching window + if (state->win->window == id) { + log_debug("Redrawing window [id=%d]", id); + draw_paint(state->draw, state->win); + break; + } + } +} + +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); + redraw_window(xsource->con, expose->window); + 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: { + log_debug("Mouse event"); + + // TODO: Handle click generically by translating the button code + 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); + redraw_window(xsource->con, property->window); + } + 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"); + + con->states = g_ptr_array_new(); + g_assert_nonnull(con->states); + + 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); + g_ptr_array_add(con->states, state); + log_debug("Attached state to event loop"); +} + +void connect_destroy(Connection *con) +{ + g_ptr_array_free(con->states, true); + + 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 |
