aboutsummaryrefslogtreecommitdiff
path: root/src/connect.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/connect.c')
-rw-r--r--src/connect.c297
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