diff --git a/CMakeLists.txt b/CMakeLists.txt index 07d4ada..92d65cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,9 @@ add_executable(f00bar xcb.c xcb.h yml.c yml.h - bar/bar.c bar/private.h bar.h + bar.h + bar/bar.c bar/private.h bar/backend.h + bar/xcb.c bar/xcb.h ) # Make global symbols in f00bar visible to dlopen:ed plugins diff --git a/bar/backend.h b/bar/backend.h new file mode 100644 index 0000000..fc33c37 --- /dev/null +++ b/bar/backend.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include "../bar.h" + +struct backend { + bool (*setup)(struct bar *bar); + void (*cleanup)(struct bar *bar); + void (*loop)(struct bar *bar, + void (*expose)(const struct bar *bar), + void (*on_mouse)(struct bar *bar, enum mouse_event event, + int x, int y)); + void (*commit_surface)(const struct bar *bar); + void (*refresh)(const struct bar *bar); + void (*set_cursor)(struct bar *bar, const char *cursor); +}; diff --git a/bar/bar.c b/bar/bar.c index 39d48fb..dd9b8fd 100644 --- a/bar/bar.c +++ b/bar/bar.c @@ -1,6 +1,7 @@ #include "../bar.h" #include "private.h" +#include #include #include #include @@ -18,6 +19,7 @@ #include "../log.h" #include "../xcb.h" +#include "xcb.h" /* * Calculate total width of left/center/rigth groups. @@ -54,15 +56,6 @@ calculate_widths(const struct private *b, int *left, int *center, int *right) *right -= b->left_spacing + b->right_spacing; } -static void -backend_commit_surface(const struct bar *_bar) -{ - const struct private *bar = _bar->private; - xcb_copy_area(bar->conn, bar->pixmap, bar->win, bar->gc, - 0, 0, 0, 0, bar->width, bar->height_with_border); - xcb_flush(bar->conn); -} - static void expose(const struct bar *_bar) { @@ -153,443 +146,99 @@ expose(const struct bar *_bar) } cairo_surface_flush(bar->cairo_surface); - backend_commit_surface(_bar); + bar->backend.iface->commit_surface(_bar); } -static void -backend_refresh(const struct bar *bar) -{ - const struct private *b = bar->private; - - /* Send an event to handle refresh from main thread */ - - /* Note: docs say that all X11 events are 32 bytes, reglardless of - * the size of the event structure */ - xcb_expose_event_t *evt = calloc(32, 1); - - *evt = (xcb_expose_event_t){ - .response_type = XCB_EXPOSE, - .window = b->win, - .x = 0, - .y = 0, - .width = b->width, - .height = b->height, - .count = 1 - }; - - xcb_send_event(b->conn, false, b->win, XCB_EVENT_MASK_EXPOSURE, (char *)evt); - xcb_flush(b->conn); - free(evt); -} - static void refresh(const struct bar *bar) { - backend_refresh(bar); -} - -static void -backend_set_cursor(struct bar *bar, const char *cursor) -{ - struct private *b = bar->private; - - if (b->cursor_name != NULL && strcmp(b->cursor_name, cursor) == 0) - return; - - if (b->cursor_ctx == NULL) - return; - - if (b->cursor != 0) { - xcb_free_cursor(b->conn, b->cursor); - free(b->cursor_name); - b->cursor_name = NULL; - } - - b->cursor_name = strdup(cursor); - b->cursor = xcb_cursor_load_cursor(b->cursor_ctx, cursor); - xcb_change_window_attributes(b->conn, b->win, XCB_CW_CURSOR, &b->cursor); + const struct private *b = bar->private; + b->backend.iface->refresh(bar); } static void set_cursor(struct bar *bar, const char *cursor) { - backend_set_cursor(bar, cursor); + struct private *b = bar->private; + b->backend.iface->set_cursor(bar, cursor); } static void -on_mouse(struct bar *bar, enum mouse_event event, int x, int y) +on_mouse(struct bar *_bar, enum mouse_event event, int x, int y) { - struct private *b = bar->private; + struct private *bar = _bar->private; - if ((y < b->border.width || y >= (b->height_with_border - b->border.width)) || - (x < b->border.width || x >= (b->width - b->border.width))) + if ((y < bar->border.width || + y >= (bar->height_with_border - bar->border.width)) || + (x < bar->border.width || x >= (bar->width - bar->border.width))) { - backend_set_cursor(bar, "left_ptr"); + set_cursor(_bar, "left_ptr"); return; } int left_width, center_width, right_width; - calculate_widths(b, &left_width, ¢er_width, &right_width); + calculate_widths(bar, &left_width, ¢er_width, &right_width); - int mx = b->border.width + b->left_margin - b->left_spacing; - for (size_t i = 0; i < b->left.count; i++) { - struct exposable *e = b->left.exps[i]; + int mx = bar->border.width + bar->left_margin - bar->left_spacing; + for (size_t i = 0; i < bar->left.count; i++) { + struct exposable *e = bar->left.exps[i]; - mx += b->left_spacing; + mx += bar->left_spacing; if (x >= mx && x < mx + e->width) { if (e->on_mouse != NULL) - e->on_mouse(e, bar, event, x - mx, y); + e->on_mouse(e, _bar, event, x - mx, y); return; } - mx += e->width + b->right_spacing; + mx += e->width + bar->right_spacing; } - mx = b->width / 2 - center_width / 2 - b->left_spacing; - for (size_t i = 0; i < b->center.count; i++) { - struct exposable *e = b->center.exps[i]; + mx = bar->width / 2 - center_width / 2 - bar->left_spacing; + for (size_t i = 0; i < bar->center.count; i++) { + struct exposable *e = bar->center.exps[i]; - mx += b->left_spacing; + mx += bar->left_spacing; if (x >= mx && x < mx + e->width) { if (e->on_mouse != NULL) - e->on_mouse(e, bar, event, x - mx, y); + e->on_mouse(e, _bar, event, x - mx, y); return; } - mx += e->width + b->right_spacing; + mx += e->width + bar->right_spacing; } - mx = b->width - (right_width + b->left_spacing + b->right_margin + b->border.width); - for (size_t i = 0; i < b->right.count; i++) { - struct exposable *e = b->right.exps[i]; + mx = bar->width - (right_width + + bar->left_spacing + + bar->right_margin + + bar->border.width); - mx += b->left_spacing; + for (size_t i = 0; i < bar->right.count; i++) { + struct exposable *e = bar->right.exps[i]; + + mx += bar->left_spacing; if (x >= mx && x < mx + e->width) { if (e->on_mouse != NULL) - e->on_mouse(e, bar, event, x - mx, y); + e->on_mouse(e, _bar, event, x - mx, y); return; } - mx += e->width + b->right_spacing; + mx += e->width + bar->right_spacing; } - backend_set_cursor(bar, "left_ptr"); + set_cursor(_bar, "left_ptr"); } -static bool -backend_setup(struct bar *_bar) -{ - struct private *bar = _bar->private; - - /* TODO: a lot of this (up to mapping the window) could be done in bar_new() */ - xcb_generic_error_t *e; - - int default_screen; - bar->conn = xcb_connect(NULL, &default_screen); - if (xcb_connection_has_error(bar->conn) > 0) { - LOG_ERR("failed to connect to X"); - xcb_disconnect(bar->conn); - return false; - } - - xcb_screen_t *screen = xcb_aux_get_screen(bar->conn, default_screen); - - xcb_randr_get_monitors_reply_t *monitors = xcb_randr_get_monitors_reply( - bar->conn, - xcb_randr_get_monitors(bar->conn, screen->root, 0), - &e); - - if (e != NULL) { - LOG_ERR("failed to get monitor list: %s", xcb_error(e)); - free(e); - /* TODO: cleanup (disconnect) */ - return false; - } - - bar->height_with_border = bar->height + 2 * bar->border.width; - - /* Find monitor coordinates and width/height */ - bool found_monitor = false; - for (xcb_randr_monitor_info_iterator_t it = - xcb_randr_get_monitors_monitors_iterator(monitors); - it.rem > 0; - xcb_randr_monitor_info_next(&it)) - { - const xcb_randr_monitor_info_t *mon = it.data; - char *name = get_atom_name(bar->conn, mon->name); - - LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name, - mon->width, mon->height, mon->x, mon->y, - mon->width_in_millimeters, mon->height_in_millimeters); - - if (!((bar->monitor == NULL && mon->primary) || - (bar->monitor != NULL && strcmp(bar->monitor, name) == 0))) - { - free(name); - continue; - } - - free(name); - - bar->x = mon->x; - bar->y = mon->y; - bar->width = mon->width; - bar->y += bar->location == BAR_TOP ? 0 - : screen->height_in_pixels - bar->height_with_border; - found_monitor = true; - break; - } - free(monitors); - - if (!found_monitor) { - LOG_ERR("no matching monitor"); - /* TODO: cleanup */ - return false; - } - - uint8_t depth = 0; - xcb_visualtype_t *vis = xcb_aux_find_visual_by_attrs(screen, -1, 32); - - if (vis != NULL) - depth = 32; - else { - vis = xcb_aux_find_visual_by_attrs(screen, -1, 24); - if (vis != NULL) - depth = 24; - } - - assert(depth == 32 || depth == 24); - assert(vis != NULL); - LOG_DBG("using a %hhu-bit visual", depth); - - bar->colormap = xcb_generate_id(bar->conn); - xcb_create_colormap(bar->conn, 0, bar->colormap, screen->root, vis->visual_id); - - bar->win = xcb_generate_id(bar->conn); - xcb_create_window( - bar->conn, - depth, bar->win, screen->root, - bar->x, bar->y, bar->width, bar->height_with_border, - 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id, - (XCB_CW_BACK_PIXEL | - XCB_CW_BORDER_PIXEL | - XCB_CW_EVENT_MASK | - XCB_CW_COLORMAP), - (const uint32_t []){ - screen->black_pixel, - screen->white_pixel, - (XCB_EVENT_MASK_EXPOSURE | - XCB_EVENT_MASK_BUTTON_RELEASE | - XCB_EVENT_MASK_BUTTON_PRESS | - XCB_EVENT_MASK_POINTER_MOTION | - XCB_EVENT_MASK_STRUCTURE_NOTIFY), - bar->colormap} - ); - - const char *title = "f00bar"; - xcb_change_property( - bar->conn, - XCB_PROP_MODE_REPLACE, bar->win, - XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, - strlen(title), title); - - xcb_change_property( - bar->conn, - XCB_PROP_MODE_REPLACE, bar->win, - _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){getpid()}); - xcb_change_property( - bar->conn, - XCB_PROP_MODE_REPLACE, bar->win, - _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32, - 1, (const uint32_t []){_NET_WM_WINDOW_TYPE_DOCK}); - xcb_change_property( - bar->conn, - XCB_PROP_MODE_REPLACE, bar->win, - _NET_WM_STATE, XCB_ATOM_ATOM, 32, - 2, (const uint32_t []){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY}); - xcb_change_property( - bar->conn, - XCB_PROP_MODE_REPLACE, bar->win, - _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){0xffffffff}); - - /* Always on top */ - xcb_configure_window( - bar->conn, bar->win, XCB_CONFIG_WINDOW_STACK_MODE, - (const uint32_t []){XCB_STACK_MODE_ABOVE}); - - uint32_t top_strut, bottom_strut; - uint32_t top_pair[2], bottom_pair[2]; - - if (bar->location == BAR_TOP) { - top_strut = bar->y + bar->height_with_border; - top_pair[0] = bar->x; - top_pair[1] = bar->x + bar->width - 1; - - bottom_strut = 0; - bottom_pair[0] = bottom_pair[1] = 0; - } else { - bottom_strut = screen->height_in_pixels - bar->y; - bottom_pair[0] = bar->x; - bottom_pair[1] = bar->x + bar->width - 1; - - top_strut = 0; - top_pair[0] = top_pair[1] = 0; - } - - uint32_t strut[] = { - /* left/right/top/bottom */ - 0, 0, - top_strut, - bottom_strut, - - /* start/end pairs for left/right/top/bottom */ - 0, 0, - 0, 0, - top_pair[0], top_pair[1], - bottom_pair[0], bottom_pair[1], - }; - - xcb_change_property( - bar->conn, - XCB_PROP_MODE_REPLACE, bar->win, - _NET_WM_STRUT, XCB_ATOM_CARDINAL, 32, - 4, strut); - - xcb_change_property( - bar->conn, - XCB_PROP_MODE_REPLACE, bar->win, - _NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32, - 12, strut); - - bar->pixmap = xcb_generate_id(bar->conn); - xcb_create_pixmap(bar->conn, depth, bar->pixmap, bar->win, - bar->width, bar->height_with_border); - - bar->gc = xcb_generate_id(bar->conn); - xcb_create_gc(bar->conn, bar->gc, bar->pixmap, - XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES, - (const uint32_t []){screen->white_pixel, 0}); - - LOG_DBG("cairo: %s", cairo_version_string()); - bar->cairo_surface = cairo_xcb_surface_create( - bar->conn, bar->pixmap, vis, bar->width, bar->height_with_border); - bar->cairo = cairo_create(bar->cairo_surface); - - xcb_map_window(bar->conn, bar->win); - - if (xcb_cursor_context_new(bar->conn, screen, &bar->cursor_ctx) < 0) - LOG_WARN("failed to create XCB cursor context"); - - xcb_flush(bar->conn); - return true; -} - -static void -backend_loop(struct bar *_bar) -{ - struct private *bar = _bar->private; - const int fd = xcb_get_file_descriptor(bar->conn); - - while (true) { - struct pollfd fds[] = { - {.fd = _bar->abort_fd, .events = POLLIN}, - {.fd = fd, .events = POLLIN} - }; - - poll(fds, sizeof(fds) / sizeof(fds[0]), -1); - - if (fds[0].revents && POLLIN) - break; - - if (fds[1].revents & POLLHUP) { - LOG_WARN("disconnected from XCB"); - write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)); - break; - } - - for (xcb_generic_event_t *e = xcb_wait_for_event(bar->conn); - e != NULL; - e = xcb_poll_for_event(bar->conn)) - { - switch (XCB_EVENT_RESPONSE_TYPE(e)) { - case 0: - LOG_ERR("XCB: %s", xcb_error((const xcb_generic_error_t *)e)); - break; - - case XCB_EXPOSE: - expose(_bar); - break; - - case XCB_MOTION_NOTIFY: { - const xcb_motion_notify_event_t *evt = (void *)e; - on_mouse(_bar, ON_MOUSE_MOTION, evt->event_x, evt->event_y); - break; - } - - case XCB_BUTTON_PRESS: - break; - - case XCB_BUTTON_RELEASE: { - const xcb_button_release_event_t *evt = (void *)e; - on_mouse(_bar, ON_MOUSE_CLICK, evt->event_x, evt->event_y); - break; - } - - case XCB_DESTROY_NOTIFY: - LOG_WARN("unimplemented event: XCB_DESTROY_NOTIFY"); - break; - - case XCB_REPARENT_NOTIFY: - case XCB_CONFIGURE_NOTIFY: - case XCB_MAP_NOTIFY: - case XCB_MAPPING_NOTIFY: - /* Just ignore */ - break; - - default: - LOG_ERR("unsupported event: %d", XCB_EVENT_RESPONSE_TYPE(e)); - break; - } - - free(e); - xcb_flush(bar->conn); - } - } -} - -static void -backend_cleanup(struct bar *_bar) -{ - struct private *bar = _bar->private; - - if (bar->cursor_ctx != NULL) { - xcb_free_cursor(bar->conn, bar->cursor); - xcb_cursor_context_free(bar->cursor_ctx); - - free(bar->cursor_name); - bar->cursor_name = NULL; - } - - xcb_free_gc(bar->conn, bar->gc); - xcb_free_pixmap(bar->conn, bar->pixmap); - xcb_destroy_window(bar->conn, bar->win); - xcb_free_colormap(bar->conn, bar->colormap); - xcb_flush(bar->conn); - - xcb_disconnect(bar->conn); -} static int run(struct bar *_bar) { struct private *bar = _bar->private; - if (!backend_setup(_bar)) + if (!bar->backend.iface->setup(_bar)) return 1; - backend_set_cursor(_bar, "left_ptr"); + set_cursor(_bar, "left_ptr"); /* Start modules */ thrd_t thrd_left[bar->left.count]; @@ -617,7 +266,7 @@ run(struct bar *_bar) LOG_DBG("all modules started"); - backend_loop(_bar); + bar->backend.iface->loop(_bar, &expose, &on_mouse); LOG_DBG("shutting down"); @@ -676,7 +325,7 @@ run(struct bar *_bar) cairo_surface_destroy(bar->cairo_surface); cairo_debug_reset_static_data(); - backend_cleanup(_bar); + bar->backend.iface->cleanup(_bar); LOG_DBG("bar exiting"); return ret; @@ -694,6 +343,7 @@ destroy(struct bar *bar) free(b->right.mods); free(b->right.exps); free(b->monitor); + free(b->backend.data); free(bar->private); free(bar); @@ -722,9 +372,11 @@ bar_new(const struct bar_config *config) priv->left.count = config->left.count; priv->center.count = config->center.count; priv->right.count = config->right.count; - priv->cursor_ctx = NULL; - priv->cursor = 0; + //priv->cursor_ctx = NULL; + //priv->cursor = 0; priv->cursor_name = NULL; + priv->backend.data = bar_backend_xcb_new(); + priv->backend.iface = &xcb_backend_iface; for (size_t i = 0; i < priv->left.count; i++) { priv->left.mods[i] = config->left.mods[i]; diff --git a/bar/private.h b/bar/private.h index 7415e06..5b202ab 100644 --- a/bar/private.h +++ b/bar/private.h @@ -1,16 +1,11 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include - #include #include +#include "../bar.h" +#include "backend.h" + struct private { /* From bar_config */ char *monitor; @@ -53,6 +48,11 @@ struct private { cairo_t *cairo; cairo_surface_t *cairo_surface; + struct { + void *data; + const struct backend *iface; + } backend; +#if 0 /* Backend specifics */ xcb_connection_t *conn; @@ -62,4 +62,5 @@ struct private { xcb_gc_t gc; xcb_cursor_context_t *cursor_ctx; xcb_cursor_t cursor; +#endif }; diff --git a/bar/xcb.c b/bar/xcb.c new file mode 100644 index 0000000..35da106 --- /dev/null +++ b/bar/xcb.c @@ -0,0 +1,421 @@ +#include "xcb.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "private.h" + +#define LOG_MODULE "bar:xcb" +#include "../log.h" +#include "../xcb.h" + +struct xcb_backend { + xcb_connection_t *conn; + + xcb_window_t win; + xcb_colormap_t colormap; + xcb_pixmap_t pixmap; + xcb_gc_t gc; + xcb_cursor_context_t *cursor_ctx; + xcb_cursor_t cursor; +}; + +void * +bar_backend_xcb_new(void) +{ + return calloc(1, sizeof(struct xcb_backend)); +} + +static bool +setup(struct bar *_bar) +{ + struct private *bar = _bar->private; + struct xcb_backend *backend = bar->backend.data; + + /* TODO: a lot of this (up to mapping the window) could be done in bar_new() */ + xcb_generic_error_t *e; + + int default_screen; + backend->conn = xcb_connect(NULL, &default_screen); + if (xcb_connection_has_error(backend->conn) > 0) { + LOG_ERR("failed to connect to X"); + xcb_disconnect(backend->conn); + return false; + } + + xcb_screen_t *screen = xcb_aux_get_screen(backend->conn, default_screen); + + xcb_randr_get_monitors_reply_t *monitors = xcb_randr_get_monitors_reply( + backend->conn, + xcb_randr_get_monitors(backend->conn, screen->root, 0), + &e); + + if (e != NULL) { + LOG_ERR("failed to get monitor list: %s", xcb_error(e)); + free(e); + /* TODO: cleanup (disconnect) */ + return false; + } + + bar->height_with_border = bar->height + 2 * bar->border.width; + + /* Find monitor coordinates and width/height */ + bool found_monitor = false; + for (xcb_randr_monitor_info_iterator_t it = + xcb_randr_get_monitors_monitors_iterator(monitors); + it.rem > 0; + xcb_randr_monitor_info_next(&it)) + { + const xcb_randr_monitor_info_t *mon = it.data; + char *name = get_atom_name(backend->conn, mon->name); + + LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name, + mon->width, mon->height, mon->x, mon->y, + mon->width_in_millimeters, mon->height_in_millimeters); + + if (!((bar->monitor == NULL && mon->primary) || + (bar->monitor != NULL && strcmp(bar->monitor, name) == 0))) + { + free(name); + continue; + } + + free(name); + + bar->x = mon->x; + bar->y = mon->y; + bar->width = mon->width; + bar->y += bar->location == BAR_TOP ? 0 + : screen->height_in_pixels - bar->height_with_border; + found_monitor = true; + break; + } + free(monitors); + + if (!found_monitor) { + LOG_ERR("no matching monitor"); + /* TODO: cleanup */ + return false; + } + + uint8_t depth = 0; + xcb_visualtype_t *vis = xcb_aux_find_visual_by_attrs(screen, -1, 32); + + if (vis != NULL) + depth = 32; + else { + vis = xcb_aux_find_visual_by_attrs(screen, -1, 24); + if (vis != NULL) + depth = 24; + } + + assert(depth == 32 || depth == 24); + assert(vis != NULL); + LOG_DBG("using a %hhu-bit visual", depth); + + backend->colormap = xcb_generate_id(backend->conn); + xcb_create_colormap(backend->conn, 0, backend->colormap, screen->root, vis->visual_id); + + backend->win = xcb_generate_id(backend->conn); + xcb_create_window( + backend->conn, + depth, backend->win, screen->root, + bar->x, bar->y, bar->width, bar->height_with_border, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id, + (XCB_CW_BACK_PIXEL | + XCB_CW_BORDER_PIXEL | + XCB_CW_EVENT_MASK | + XCB_CW_COLORMAP), + (const uint32_t []){ + screen->black_pixel, + screen->white_pixel, + (XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_BUTTON_RELEASE | + XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_POINTER_MOTION | + XCB_EVENT_MASK_STRUCTURE_NOTIFY), + backend->colormap} + ); + + const char *title = "f00bar"; + xcb_change_property( + backend->conn, + XCB_PROP_MODE_REPLACE, backend->win, + XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, + strlen(title), title); + + xcb_change_property( + backend->conn, + XCB_PROP_MODE_REPLACE, backend->win, + _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){getpid()}); + xcb_change_property( + backend->conn, + XCB_PROP_MODE_REPLACE, backend->win, + _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32, + 1, (const uint32_t []){_NET_WM_WINDOW_TYPE_DOCK}); + xcb_change_property( + backend->conn, + XCB_PROP_MODE_REPLACE, backend->win, + _NET_WM_STATE, XCB_ATOM_ATOM, 32, + 2, (const uint32_t []){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY}); + xcb_change_property( + backend->conn, + XCB_PROP_MODE_REPLACE, backend->win, + _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){0xffffffff}); + + /* Always on top */ + xcb_configure_window( + backend->conn, backend->win, XCB_CONFIG_WINDOW_STACK_MODE, + (const uint32_t []){XCB_STACK_MODE_ABOVE}); + + uint32_t top_strut, bottom_strut; + uint32_t top_pair[2], bottom_pair[2]; + + if (bar->location == BAR_TOP) { + top_strut = bar->y + bar->height_with_border; + top_pair[0] = bar->x; + top_pair[1] = bar->x + bar->width - 1; + + bottom_strut = 0; + bottom_pair[0] = bottom_pair[1] = 0; + } else { + bottom_strut = screen->height_in_pixels - bar->y; + bottom_pair[0] = bar->x; + bottom_pair[1] = bar->x + bar->width - 1; + + top_strut = 0; + top_pair[0] = top_pair[1] = 0; + } + + uint32_t strut[] = { + /* left/right/top/bottom */ + 0, 0, + top_strut, + bottom_strut, + + /* start/end pairs for left/right/top/bottom */ + 0, 0, + 0, 0, + top_pair[0], top_pair[1], + bottom_pair[0], bottom_pair[1], + }; + + xcb_change_property( + backend->conn, + XCB_PROP_MODE_REPLACE, backend->win, + _NET_WM_STRUT, XCB_ATOM_CARDINAL, 32, + 4, strut); + + xcb_change_property( + backend->conn, + XCB_PROP_MODE_REPLACE, backend->win, + _NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32, + 12, strut); + + backend->pixmap = xcb_generate_id(backend->conn); + xcb_create_pixmap(backend->conn, depth, backend->pixmap, backend->win, + bar->width, bar->height_with_border); + + backend->gc = xcb_generate_id(backend->conn); + xcb_create_gc(backend->conn, backend->gc, backend->pixmap, + XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES, + (const uint32_t []){screen->white_pixel, 0}); + + LOG_DBG("cairo: %s", cairo_version_string()); + bar->cairo_surface = cairo_xcb_surface_create( + backend->conn, backend->pixmap, vis, bar->width, bar->height_with_border); + bar->cairo = cairo_create(bar->cairo_surface); + + xcb_map_window(backend->conn, backend->win); + + if (xcb_cursor_context_new(backend->conn, screen, &backend->cursor_ctx) < 0) + LOG_WARN("failed to create XCB cursor context"); + + xcb_flush(backend->conn); + return true; +} + +static void +cleanup(struct bar *_bar) +{ + struct private *bar = _bar->private; + struct xcb_backend *backend = bar->backend.data; + + if (backend->cursor_ctx != NULL) { + xcb_free_cursor(backend->conn, backend->cursor); + xcb_cursor_context_free(backend->cursor_ctx); + + free(bar->cursor_name); + bar->cursor_name = NULL; + } + + xcb_free_gc(backend->conn, backend->gc); + xcb_free_pixmap(backend->conn, backend->pixmap); + xcb_destroy_window(backend->conn, backend->win); + xcb_free_colormap(backend->conn, backend->colormap); + xcb_flush(backend->conn); + + xcb_disconnect(backend->conn); +} + +static void +loop(struct bar *_bar, + void (*expose)(const struct bar *bar), + void (*on_mouse)(struct bar *bar, enum mouse_event event, int x, int y)) +{ + struct private *bar = _bar->private; + struct xcb_backend *backend = bar->backend.data; + + const int fd = xcb_get_file_descriptor(backend->conn); + + while (true) { + struct pollfd fds[] = { + {.fd = _bar->abort_fd, .events = POLLIN}, + {.fd = fd, .events = POLLIN} + }; + + poll(fds, sizeof(fds) / sizeof(fds[0]), -1); + + if (fds[0].revents && POLLIN) + break; + + if (fds[1].revents & POLLHUP) { + LOG_WARN("disconnected from XCB"); + write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)); + break; + } + + for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn); + e != NULL; + e = xcb_poll_for_event(backend->conn)) + { + switch (XCB_EVENT_RESPONSE_TYPE(e)) { + case 0: + LOG_ERR("XCB: %s", xcb_error((const xcb_generic_error_t *)e)); + break; + + case XCB_EXPOSE: + expose(_bar); + break; + + case XCB_MOTION_NOTIFY: { + const xcb_motion_notify_event_t *evt = (void *)e; + on_mouse(_bar, ON_MOUSE_MOTION, evt->event_x, evt->event_y); + break; + } + + case XCB_BUTTON_PRESS: + break; + + case XCB_BUTTON_RELEASE: { + const xcb_button_release_event_t *evt = (void *)e; + on_mouse(_bar, ON_MOUSE_CLICK, evt->event_x, evt->event_y); + break; + } + + case XCB_DESTROY_NOTIFY: + LOG_WARN("unimplemented event: XCB_DESTROY_NOTIFY"); + break; + + case XCB_REPARENT_NOTIFY: + case XCB_CONFIGURE_NOTIFY: + case XCB_MAP_NOTIFY: + case XCB_MAPPING_NOTIFY: + /* Just ignore */ + break; + + default: + LOG_ERR("unsupported event: %d", XCB_EVENT_RESPONSE_TYPE(e)); + break; + } + + free(e); + xcb_flush(backend->conn); + } + } +} + +static void +commit_surface(const struct bar *_bar) +{ + const struct private *bar = _bar->private; + const struct xcb_backend *backend = bar->backend.data; + xcb_copy_area(backend->conn, backend->pixmap, backend->win, backend->gc, + 0, 0, 0, 0, bar->width, bar->height_with_border); + xcb_flush(backend->conn); +} + +static void +refresh(const struct bar *_bar) +{ + const struct private *bar = _bar->private; + const struct xcb_backend *backend = bar->backend.data; + + /* Send an event to handle refresh from main thread */ + + /* Note: docs say that all X11 events are 32 bytes, reglardless of + * the size of the event structure */ + xcb_expose_event_t *evt = calloc(32, 1); + + *evt = (xcb_expose_event_t){ + .response_type = XCB_EXPOSE, + .window = backend->win, + .x = 0, + .y = 0, + .width = bar->width, + .height = bar->height, + .count = 1 + }; + + xcb_send_event( + backend->conn, false, backend->win, XCB_EVENT_MASK_EXPOSURE, + (char *)evt); + + xcb_flush(backend->conn); + free(evt); +} + +static void +set_cursor(struct bar *_bar, const char *cursor) +{ + struct private *bar = _bar->private; + struct xcb_backend *backend = bar->backend.data; + + if (bar->cursor_name != NULL && strcmp(bar->cursor_name, cursor) == 0) + return; + + if (backend->cursor_ctx == NULL) + return; + + if (backend->cursor != 0) { + xcb_free_cursor(backend->conn, backend->cursor); + free(bar->cursor_name); + bar->cursor_name = NULL; + } + + bar->cursor_name = strdup(cursor); + backend->cursor = xcb_cursor_load_cursor(backend->cursor_ctx, cursor); + xcb_change_window_attributes( + backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor); +} + +const struct backend xcb_backend_iface = { + .setup = &setup, + .cleanup = &cleanup, + .loop = &loop, + .commit_surface = &commit_surface, + .refresh = &refresh, + .set_cursor = &set_cursor, +}; diff --git a/bar/xcb.h b/bar/xcb.h new file mode 100644 index 0000000..d9e1529 --- /dev/null +++ b/bar/xcb.h @@ -0,0 +1,7 @@ +#pragma once + +#include "backend.h" + +extern const struct backend xcb_backend_iface; + +void *bar_backend_xcb_new(void);