diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b7a2014..9087c8c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,12 +1,17 @@ -image: alpine:latest +image: alpine:edge stages: - build +variables: + GIT_SUBMODULE_STRATEGY: normal + before_script: + - echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories - apk update - apk add musl-dev eudev-libs eudev-dev linux-headers cmake ninja gcc - apk add libxcb-dev xcb-util-wm-dev xcb-util-cursor-dev cairo-dev yaml-dev + - apk add wayland-dev wayland-protocols wlroots-dev - apk add json-c-dev libmpdclient-dev alsa-lib-dev i3wm debug: @@ -15,7 +20,7 @@ debug: - mkdir -p bld/debug - cd bld/debug - cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ../../ - - ninja + - ninja -k0 release: stage: build @@ -23,4 +28,28 @@ release: - mkdir -p bld/release - cd bld/release - cmake -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel ../../ - - ninja + - ninja -k0 + +x11_only: + stage: build + script: + - mkdir -p bld/debug + - cd bld/debug + - cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DENABLE_X11=yes -DENABLE_WAYLAND=no ../../ + - ninja -k0 + +wayland_only: + stage: build + script: + - mkdir -p bld/debug + - cd bld/debug + - cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DENABLE_X11=no -DENABLE_WAYLAND=yes ../../ + - ninja -k0 + +plugins_as_shared_modules: + stage: build + script: + - mkdir -p bld/debug + - cd bld/debug + - cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCORE_PLUGINS_AS_SHARED_LIBRARIES=yes ../../ + - ninja -k0 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..887b1f9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/wlroots"] + path = external/wlroots + url = https://github.com/swaywm/wlroots.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cca58a..b94966d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,17 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.13) project(f00bar C) set(CORE_PLUGINS_AS_SHARED_LIBRARIES 0 CACHE BOOL "Compiles modules, particles and decorations as shared libraries, which are loaded on-demand") +set(ENABLE_X11 1 CACHE BOOL "Enables support for the XCB (X11) backend") +set_property(DIRECTORY . APPEND PROPERTY + COMPILE_DEFINITIONS $<$:ENABLE_X11>) + +set(ENABLE_WAYLAND 1 CACHE BOOL "Enables support for the wayland backend") +set_property(DIRECTORY . APPEND PROPERTY + COMPILE_DEFINITIONS $<$:ENABLE_WAYLAND>) + set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_C_STANDARD 11) @@ -23,15 +31,39 @@ set(CMAKE_C_FLAGS "-Wall -Werror ${CMAKE_C_FLAGS}") find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) -pkg_check_modules(XCB REQUIRED xcb xcb-aux xcb-cursor xcb-event xcb-ewmh - xcb-randr xcb-render) -pkg_check_modules(XCB_ERRORS xcb-errors) -pkg_check_modules(FONTCONFIG REQUIRED fontconfig) -pkg_check_modules(CAIRO REQUIRED cairo cairo-xcb cairo-ft) -pkg_check_modules(YAML REQUIRED yaml-0.1) +pkg_check_modules(fontconfig REQUIRED IMPORTED_TARGET fontconfig) +pkg_check_modules(cairo REQUIRED IMPORTED_TARGET cairo cairo-ft) +pkg_check_modules(yaml REQUIRED IMPORTED_TARGET yaml-0.1) + +if (ENABLE_X11) + pkg_check_modules(xcb REQUIRED IMPORTED_TARGET + xcb xcb-aux xcb-cursor xcb-event xcb-ewmh xcb-randr xcb-render cairo-xcb) + pkg_check_modules(xcb-errors IMPORTED_TARGET xcb-errors) + + add_library(xcb-stuff STATIC EXCLUDE_FROM_ALL xcb.c xcb.h) + target_link_libraries(xcb-stuff PkgConfig::xcb) + if (XCB_ERRORS_FOUND) + target_compile_definitions(xcb-stuff PRIVATE HAVE_XCB_ERRORS) + target_link_libraries(xcb-stuff PkgConfig::xcb-errors) + endif () + + # Since there are plugins linking against xcb-stuff, we need to + # ensure it's compiled with -fPIC when the plugins are compiled as + # shared libraries. + if (CORE_PLUGINS_AS_SHARED_LIBRARIES) + set_property(TARGET xcb-stuff PROPERTY POSITION_INDEPENDENT_CODE 1) + endif () + +endif () + +if (ENABLE_WAYLAND) + pkg_check_modules(wayland REQUIRED IMPORTED_TARGET + wayland-client wayland-cursor wlroots) +endif () + +add_subdirectory(bar) add_executable(f00bar - bar.c bar.h config.c config.h config-verify.c config-verify.h decoration.h @@ -42,43 +74,16 @@ add_executable(f00bar particle.c particle.h plugin.c plugin.h tag.c tag.h - xcb.c xcb.h yml.c yml.h ) -# Make global symbols in f00bar visible to dlopen:ed plugins -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic") - -if (XCB_ERRORS_FOUND) - target_compile_definitions(f00bar PRIVATE HAVE_XCB_ERRORS) -endif () - -target_compile_options(f00bar PRIVATE - ${XCB_CFLAGS_OTHER} - ${XCB_ERRORS_CFLAGS_OTHER} - ${FONTCONFIG_CFLAGS_OTHER} - ${CAIRO_CFLAGS_OTHER} - ${YAML_CFLAGS_OTHER} - ) - -target_include_directories(f00bar PRIVATE - ${XCB_INCLUDE_DIRS} - ${XCB_ERRORS_INCLUDE_DIRS} - ${FONTCONFIG_INCLUDE_DIRS} - ${CAIRO_INCLUDE_DIRS} - ${YAML_INCLUDE_DIRS} - ) - target_link_libraries(f00bar - ${CMAKE_THREAD_LIBS_INIT} - ${CMAKE_DL_LIBS} + bar + PkgConfig::cairo PkgConfig::fontconfig PkgConfig::yaml + ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) - ${XCB_LIBRARIES} - ${XCB_ERRORS_LIBRARIES} - ${FONTCONFIG_LIBRARIES} - ${CAIRO_LIBRARIES} - ${YAML_LIBRARIES} - ) +# Make global symbols in f00bar visible to dlopen:ed plugins +target_link_options(f00bar PRIVATE -rdynamic) set_property(TARGET f00bar PROPERTY INSTALL_RPATH \$ORIGIN/../lib/f00bar) set_property(TARGET f00bar PROPERTY @@ -86,13 +91,21 @@ set_property(TARGET f00bar PROPERTY install(TARGETS f00bar DESTINATION bin) +set(enabled_modules "") +set(enabled_particles "") +set(enabled_decorations "") + add_subdirectory(modules) add_subdirectory(particles) add_subdirectory(decorations) if (NOT CORE_PLUGINS_AS_SHARED_LIBRARIES) - target_link_libraries(f00bar background stack underline) - target_link_libraries(f00bar dynlist empty list map progress-bar ramp string) - target_link_libraries(f00bar alsa backlight battery clock i3 label mpd network - removables xkb xwindow) + target_link_libraries(f00bar ${enabled_decorations}) + target_link_libraries(f00bar ${enabled_particles}) + target_link_libraries(f00bar ${enabled_modules}) + + foreach (plug ${enabled_decorations} ${enabled_particles} ${enabled_modules}) + string(REPLACE "-" "_" fixed "${plug}") + target_compile_definitions(f00bar PRIVATE "HAVE_PLUGIN_${fixed}") + endforeach () endif () diff --git a/bar.c b/bar.c deleted file mode 100644 index 2f7e8a7..0000000 --- a/bar.c +++ /dev/null @@ -1,773 +0,0 @@ -#include "bar.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define LOG_MODULE "bar" -#define LOG_ENABLE_DBG 0 -#include "log.h" -#include "xcb.h" - -struct private { - /* From bar_config */ - char *monitor; - enum bar_location location; - int height; - int left_spacing, right_spacing; - int left_margin, right_margin; - - struct rgba background; - - struct { - int width; - struct rgba color; - } border; - - struct { - struct module **mods; - struct exposable **exps; - size_t count; - } left; - struct { - struct module **mods; - struct exposable **exps; - size_t count; - } center; - struct { - struct module **mods; - struct exposable **exps; - size_t count; - } right; - - /* Calculated run-time */ - int x, y; - int width; - int height_with_border; - - /* Resources */ - 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; - char *cursor_name; - - cairo_t *cairo; - cairo_surface_t *cairo_surface; -}; - -/* - * Calculate total width of left/center/rigth groups. - * Note: begin_expose() must have been called - */ -static void -calculate_widths(const struct private *b, int *left, int *center, int *right) -{ - *left = 0; - *center = 0; - *right = 0; - - for (size_t i = 0; i < b->left.count; i++) { - struct exposable *e = b->left.exps[i]; - assert(e != NULL); - *left += b->left_spacing + e->width + b->right_spacing; - } - - for (size_t i = 0; i < b->center.count; i++) { - struct exposable *e = b->center.exps[i]; - assert(e != NULL); - *center += b->left_spacing + e->width + b->right_spacing; - } - - for (size_t i = 0; i < b->right.count; i++) { - struct exposable *e = b->right.exps[i]; - assert(e != NULL); - *right += b->left_spacing + e->width + b->right_spacing; - } - - /* No spacing on the edges (that's what the margins are for) */ - *left -= b->left_spacing + b->right_spacing; - *center -= b->left_spacing + b->right_spacing; - *right -= b->left_spacing + b->right_spacing; -} - -static void -expose(const struct bar *_bar) -{ - const struct private *bar = _bar->private; - - double r, g, b, a; - r = bar->background.red; - g = bar->background.green; - b = bar->background.blue; - a = bar->background.alpha; - - cairo_set_source_rgba(bar->cairo, r, g, b, a); - cairo_set_operator(bar->cairo, CAIRO_OPERATOR_SOURCE); - cairo_paint(bar->cairo); - - if (bar->border.width > 0) { - /* TODO: actually use border width */ - r = bar->border.color.red; - g = bar->border.color.green; - b = bar->border.color.blue; - a = bar->border.color.alpha; - - cairo_set_line_width(bar->cairo, bar->border.width); - cairo_set_source_rgba(bar->cairo, r, g, b, a); - cairo_set_operator(bar->cairo, CAIRO_OPERATOR_OVER); - cairo_rectangle(bar->cairo, 0, 0, bar->width, bar->height_with_border); - cairo_stroke(bar->cairo); - } - - for (size_t i = 0; i < bar->left.count; i++) { - struct module *m = bar->left.mods[i]; - struct exposable *e = bar->left.exps[i]; - - if (e != NULL) - e->destroy(e); - - bar->left.exps[i] = module_begin_expose(m); - } - - for (size_t i = 0; i < bar->center.count; i++) { - struct module *m = bar->center.mods[i]; - struct exposable *e = bar->center.exps[i]; - - if (e != NULL) - e->destroy(e); - - bar->center.exps[i] = module_begin_expose(m); - } - - for (size_t i = 0; i < bar->right.count; i++) { - struct module *m = bar->right.mods[i]; - struct exposable *e = bar->right.exps[i]; - - if (e != NULL) - e->destroy(e); - - bar->right.exps[i] = module_begin_expose(m); - } - - int left_width, center_width, right_width; - calculate_widths(bar, &left_width, ¢er_width, &right_width); - - int y = bar->border.width; - int x = bar->border.width + bar->left_margin - bar->left_spacing; - for (size_t i = 0; i < bar->left.count; i++) { - const struct exposable *e = bar->left.exps[i]; - e->expose(e, bar->cairo, x + bar->left_spacing, y, bar->height); - x += bar->left_spacing + e->width + bar->right_spacing; - } - - x = bar->width / 2 - center_width / 2 - bar->left_spacing; - for (size_t i = 0; i < bar->center.count; i++) { - const struct exposable *e = bar->center.exps[i]; - e->expose(e, bar->cairo, x + bar->left_spacing, y, bar->height); - x += bar->left_spacing + e->width + bar->right_spacing; - } - - x = bar->width - ( - right_width + - bar->left_spacing + - bar->right_margin + - bar->border.width); - - for (size_t i = 0; i < bar->right.count; i++) { - const struct exposable *e = bar->right.exps[i]; - e->expose(e, bar->cairo, x + bar->left_spacing, y, bar->height); - x += bar->left_spacing + e->width + bar->right_spacing; - } - - cairo_surface_flush(bar->cairo_surface); - 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 -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 -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); -} - -static void -on_mouse(struct bar *bar, enum mouse_event event, int x, int y) -{ - struct private *b = 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))) - { - set_cursor(bar, "left_ptr"); - return; - } - - int left_width, center_width, right_width; - calculate_widths(b, &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]; - - mx += b->left_spacing; - if (x >= mx && x < mx + e->width) { - if (e->on_mouse != NULL) - e->on_mouse(e, bar, event, x - mx, y); - return; - } - - mx += e->width + b->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 += b->left_spacing; - if (x >= mx && x < mx + e->width) { - if (e->on_mouse != NULL) - e->on_mouse(e, bar, event, x - mx, y); - return; - } - - mx += e->width + b->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 += b->left_spacing; - if (x >= mx && x < mx + e->width) { - if (e->on_mouse != NULL) - e->on_mouse(e, bar, event, x - mx, y); - return; - } - - mx += e->width + b->right_spacing; - } - - set_cursor(bar, "left_ptr"); -} - -static int -run(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 1; - } - - 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 1; - } - - 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 1; - } - - 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"); - else - set_cursor(_bar, "left_ptr"); - - xcb_flush(bar->conn); - - /* Start modules */ - thrd_t thrd_left[bar->left.count]; - thrd_t thrd_center[bar->center.count]; - thrd_t thrd_right[bar->right.count]; - - for (size_t i = 0; i < bar->left.count; i++) { - struct module *mod = bar->left.mods[i]; - - mod->abort_fd = _bar->abort_fd; - thrd_create(&thrd_left[i], (int (*)(void *))bar->left.mods[i]->run, mod); - } - for (size_t i = 0; i < bar->center.count; i++) { - struct module *mod = bar->center.mods[i]; - - mod->abort_fd = _bar->abort_fd; - thrd_create(&thrd_center[i], (int (*)(void *))bar->center.mods[i]->run, mod); - } - for (size_t i = 0; i < bar->right.count; i++) { - struct module *mod = bar->right.mods[i]; - - mod->abort_fd = _bar->abort_fd; - thrd_create(&thrd_right[i], (int (*)(void *))bar->right.mods[i]->run, mod); - } - - LOG_DBG("all modules started"); - - 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); - } - } - - LOG_DBG("shutting down"); - - /* Wait for modules to terminate */ - int ret = 0; - int mod_ret; - for (size_t i = 0; i < bar->left.count; i++) { - thrd_join(thrd_left[i], &mod_ret); - if (mod_ret != 0) - LOG_ERR("module: LEFT #%zu: non-zero exit value: %d", i, mod_ret); - ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; - } - for (size_t i = 0; i < bar->center.count; i++) { - thrd_join(thrd_center[i], &mod_ret); - if (mod_ret != 0) - LOG_ERR("module: CENTER #%zu: non-zero exit value: %d", i, mod_ret); - ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; - } - for (size_t i = 0; i < bar->right.count; i++) { - thrd_join(thrd_right[i], &mod_ret); - if (mod_ret != 0) - LOG_ERR("module: RIGHT #%zu: non-zero exit value: %d", i, mod_ret); - ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; - } - - LOG_DBG("modules joined"); - - for (size_t i = 0; i < bar->left.count; i++) { - struct module *m = bar->left.mods[i]; - struct exposable *e = bar->left.exps[i]; - - if (e != NULL) - e->destroy(e); - m->destroy(m); - } - for (size_t i = 0; i < bar->center.count; i++) { - struct module *m = bar->center.mods[i]; - struct exposable *e = bar->center.exps[i]; - - if (e != NULL) - e->destroy(e); - m->destroy(m); - } - for (size_t i = 0; i < bar->right.count; i++) { - struct module *m = bar->right.mods[i]; - struct exposable *e = bar->right.exps[i]; - - if (e != NULL) - e->destroy(e); - m->destroy(m); - } - - cairo_destroy(bar->cairo); - cairo_device_finish(cairo_surface_get_device(bar->cairo_surface)); - cairo_surface_finish(bar->cairo_surface); - cairo_surface_destroy(bar->cairo_surface); - cairo_debug_reset_static_data(); - - 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); - - LOG_DBG("bar exiting"); - return ret; -} - -static void -destroy(struct bar *bar) -{ - struct private *b = bar->private; - - free(b->left.mods); - free(b->left.exps); - free(b->center.mods); - free(b->center.exps); - free(b->right.mods); - free(b->right.exps); - free(b->monitor); - - free(bar->private); - free(bar); -} - -struct bar * -bar_new(const struct bar_config *config) -{ - struct private *priv = malloc(sizeof(*priv)); - priv->monitor = config->monitor != NULL ? strdup(config->monitor) : NULL; - priv->location = config->location; - priv->height = config->height; - priv->background = config->background; - priv->left_spacing = config->left_spacing; - priv->right_spacing = config->right_spacing; - priv->left_margin = config->left_margin; - priv->right_margin = config->right_margin; - priv->border.width = config->border.width; - priv->border.color = config->border.color; - priv->left.mods = malloc(config->left.count * sizeof(priv->left.mods[0])); - priv->left.exps = malloc(config->left.count * sizeof(priv->left.exps[0])); - priv->center.mods = malloc(config->center.count * sizeof(priv->center.mods[0])); - priv->center.exps = malloc(config->center.count * sizeof(priv->center.exps[0])); - priv->right.mods = malloc(config->right.count * sizeof(priv->right.mods[0])); - priv->right.exps = malloc(config->right.count * sizeof(priv->right.exps[0])); - 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_name = NULL; - - for (size_t i = 0; i < priv->left.count; i++) { - priv->left.mods[i] = config->left.mods[i]; - priv->left.exps[i] = NULL; - } - for (size_t i = 0; i < priv->center.count; i++) { - priv->center.mods[i] = config->center.mods[i]; - priv->center.exps[i] = NULL; - } - for (size_t i = 0; i < priv->right.count; i++) { - priv->right.mods[i] = config->right.mods[i]; - priv->right.exps[i] = NULL; - } - - struct bar *bar = malloc(sizeof(*bar)); - bar->private = priv; - bar->run = &run; - bar->destroy = &destroy; - bar->refresh = &refresh; - bar->set_cursor = &set_cursor; - - for (size_t i = 0; i < priv->left.count; i++) - priv->left.mods[i]->bar = bar; - for (size_t i = 0; i < priv->center.count; i++) - priv->center.mods[i]->bar = bar; - for (size_t i = 0; i < priv->right.count; i++) - priv->right.mods[i]->bar = bar; - - return bar; -} diff --git a/bar/CMakeLists.txt b/bar/CMakeLists.txt new file mode 100644 index 0000000..e57442b --- /dev/null +++ b/bar/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.13) + +# X11/XCB bar backend +if (ENABLE_X11) + add_library(bar-xcb STATIC EXCLUDE_FROM_ALL xcb.c xcb.h) + target_link_libraries(bar-xcb xcb-stuff PkgConfig::cairo) +endif () + +# Wayland/wlroots bar backend +if (ENABLE_WAYLAND) + function (wayland_protocol _deps) + set(deps "") + foreach (xml_file ${ARGN}) + get_filename_component(base ${xml_file} NAME_WE) + set(out_c ${base}.c) + set(out_h ${base}.h) + + add_custom_command( + OUTPUT ${out_h} + COMMAND wayland-scanner client-header < ${xml_file} > ${out_h} + VERBATIM + MAIN_DEPENDENCY ${xml_file} + ) + add_custom_command( + OUTPUT ${out_c} + COMMAND wayland-scanner private-code < ${xml_file} > ${out_c} + VERBATIM + MAIN_DEPENDENCY ${xml_file} + ) + + list(APPEND deps ${out_h}) + list(APPEND deps ${out_c}) + endforeach () + + set(${_deps} ${deps} PARENT_SCOPE) + endfunction () + + pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols) + execute_process( + COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=pkgdatadir wayland-protocols + OUTPUT_VARIABLE WAYLAND_PROTOCOLS + OUTPUT_STRIP_TRAILING_WHITESPACE) + + wayland_protocol( + wayland_protos + ${PROJECT_SOURCE_DIR}/external/wlroots/protocol/wlr-layer-shell-unstable-v1.xml + ${WAYLAND_PROTOCOLS}/stable/xdg-shell/xdg-shell.xml + ${WAYLAND_PROTOCOLS}/unstable/xdg-output/xdg-output-unstable-v1.xml + ) + + add_library(wayland-protocols STATIC EXCLUDE_FROM_ALL ${wayland_protos}) + target_include_directories(wayland-protocols PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + + add_library(bar-wayland STATIC EXCLUDE_FROM_ALL wayland.c wayland.h) + target_compile_definitions(bar-wayland PRIVATE _GNU_SOURCE) + target_link_libraries( + bar-wayland wayland-protocols PkgConfig::wayland PkgConfig::cairo) +endif () + +add_library(bar STATIC EXCLUDE_FROM_ALL bar.c bar.h private.h backend.h) + +target_link_libraries(bar + $<$:bar-xcb> + $<$:bar-wayland> + ${CMAKE_THREAD_LIBS_INIT} +) diff --git a/bar/backend.h b/bar/backend.h new file mode 100644 index 0000000..c07e069 --- /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 new file mode 100644 index 0000000..065460b --- /dev/null +++ b/bar/bar.c @@ -0,0 +1,438 @@ +#include "bar.h" +#include "private.h" + +#include +#include +#include +#include +#include +#include + +#include + +#define LOG_MODULE "bar" +#define LOG_ENABLE_DBG 0 +#include "../log.h" + +#if defined(ENABLE_X11) + #include "xcb.h" +#endif + +#if defined(ENABLE_WAYLAND) + #include "wayland.h" +#endif + +/* + * Calculate total width of left/center/rigth groups. + * Note: begin_expose() must have been called + */ +static void +calculate_widths(const struct private *b, int *left, int *center, int *right) +{ + *left = 0; + *center = 0; + *right = 0; + + for (size_t i = 0; i < b->left.count; i++) { + struct exposable *e = b->left.exps[i]; + assert(e != NULL); + *left += b->left_spacing + e->width + b->right_spacing; + } + + for (size_t i = 0; i < b->center.count; i++) { + struct exposable *e = b->center.exps[i]; + assert(e != NULL); + *center += b->left_spacing + e->width + b->right_spacing; + } + + for (size_t i = 0; i < b->right.count; i++) { + struct exposable *e = b->right.exps[i]; + assert(e != NULL); + *right += b->left_spacing + e->width + b->right_spacing; + } + + /* No spacing on the edges (that's what the margins are for) */ + *left -= b->left_spacing + b->right_spacing; + *center -= b->left_spacing + b->right_spacing; + *right -= b->left_spacing + b->right_spacing; +} + +static void +expose(const struct bar *_bar) +{ + const struct private *bar = _bar->private; + + double r, g, b, a; + r = bar->background.red; + g = bar->background.green; + b = bar->background.blue; + a = bar->background.alpha; + + cairo_set_source_rgba(bar->cairo, r, g, b, a); + cairo_set_operator(bar->cairo, CAIRO_OPERATOR_SOURCE); + cairo_paint(bar->cairo); + + if (bar->border.width > 0) { + /* TODO: actually use border width */ + r = bar->border.color.red; + g = bar->border.color.green; + b = bar->border.color.blue; + a = bar->border.color.alpha; + + cairo_set_line_width(bar->cairo, bar->border.width); + cairo_set_source_rgba(bar->cairo, r, g, b, a); + cairo_set_operator(bar->cairo, CAIRO_OPERATOR_OVER); + cairo_rectangle(bar->cairo, 0, 0, bar->width, bar->height_with_border); + cairo_stroke(bar->cairo); + } + + for (size_t i = 0; i < bar->left.count; i++) { + struct module *m = bar->left.mods[i]; + struct exposable *e = bar->left.exps[i]; + + if (e != NULL) + e->destroy(e); + + bar->left.exps[i] = module_begin_expose(m); + } + + for (size_t i = 0; i < bar->center.count; i++) { + struct module *m = bar->center.mods[i]; + struct exposable *e = bar->center.exps[i]; + + if (e != NULL) + e->destroy(e); + + bar->center.exps[i] = module_begin_expose(m); + } + + for (size_t i = 0; i < bar->right.count; i++) { + struct module *m = bar->right.mods[i]; + struct exposable *e = bar->right.exps[i]; + + if (e != NULL) + e->destroy(e); + + bar->right.exps[i] = module_begin_expose(m); + } + + int left_width, center_width, right_width; + calculate_widths(bar, &left_width, ¢er_width, &right_width); + + int y = bar->border.width; + int x = bar->border.width + bar->left_margin - bar->left_spacing; + for (size_t i = 0; i < bar->left.count; i++) { + const struct exposable *e = bar->left.exps[i]; + e->expose(e, bar->cairo, x + bar->left_spacing, y, bar->height); + x += bar->left_spacing + e->width + bar->right_spacing; + } + + x = bar->width / 2 - center_width / 2 - bar->left_spacing; + for (size_t i = 0; i < bar->center.count; i++) { + const struct exposable *e = bar->center.exps[i]; + e->expose(e, bar->cairo, x + bar->left_spacing, y, bar->height); + x += bar->left_spacing + e->width + bar->right_spacing; + } + + x = bar->width - ( + right_width + + bar->left_spacing + + bar->right_margin + + bar->border.width); + + for (size_t i = 0; i < bar->right.count; i++) { + const struct exposable *e = bar->right.exps[i]; + e->expose(e, bar->cairo, x + bar->left_spacing, y, bar->height); + x += bar->left_spacing + e->width + bar->right_spacing; + } + + cairo_surface_flush(bar->cairo_surface); + bar->backend.iface->commit_surface(_bar); +} + + +static void +refresh(const struct bar *bar) +{ + const struct private *b = bar->private; + b->backend.iface->refresh(bar); +} + +static void +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; + + free(b->cursor_name); + b->cursor_name = strdup(cursor); + + b->backend.iface->set_cursor(bar, cursor); +} + +static void +on_mouse(struct bar *_bar, enum mouse_event event, int x, int y) +{ + struct private *bar = _bar->private; + + if ((y < bar->border.width || + y >= (bar->height_with_border - bar->border.width)) || + (x < bar->border.width || x >= (bar->width - bar->border.width))) + { + set_cursor(_bar, "left_ptr"); + return; + } + + int left_width, center_width, right_width; + calculate_widths(bar, &left_width, ¢er_width, &right_width); + + 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 += bar->left_spacing; + if (x >= mx && x < mx + e->width) { + if (e->on_mouse != NULL) + e->on_mouse(e, _bar, event, x - mx, y); + return; + } + + mx += e->width + bar->right_spacing; + } + + 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 += bar->left_spacing; + if (x >= mx && x < mx + e->width) { + if (e->on_mouse != NULL) + e->on_mouse(e, _bar, event, x - mx, y); + return; + } + + mx += e->width + bar->right_spacing; + } + + mx = bar->width - (right_width + + bar->left_spacing + + bar->right_margin + + bar->border.width); + + 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); + return; + } + + mx += e->width + bar->right_spacing; + } + + set_cursor(_bar, "left_ptr"); +} + + +static int +run(struct bar *_bar) +{ + struct private *bar = _bar->private; + + bar->height_with_border = bar->height + 2 * bar->border.width; + + if (!bar->backend.iface->setup(_bar)) { + bar->backend.iface->cleanup(_bar); + return 1; + } + + set_cursor(_bar, "left_ptr"); + + /* Start modules */ + thrd_t thrd_left[bar->left.count]; + thrd_t thrd_center[bar->center.count]; + thrd_t thrd_right[bar->right.count]; + + for (size_t i = 0; i < bar->left.count; i++) { + struct module *mod = bar->left.mods[i]; + + mod->abort_fd = _bar->abort_fd; + thrd_create(&thrd_left[i], (int (*)(void *))bar->left.mods[i]->run, mod); + } + for (size_t i = 0; i < bar->center.count; i++) { + struct module *mod = bar->center.mods[i]; + + mod->abort_fd = _bar->abort_fd; + thrd_create(&thrd_center[i], (int (*)(void *))bar->center.mods[i]->run, mod); + } + for (size_t i = 0; i < bar->right.count; i++) { + struct module *mod = bar->right.mods[i]; + + mod->abort_fd = _bar->abort_fd; + thrd_create(&thrd_right[i], (int (*)(void *))bar->right.mods[i]->run, mod); + } + + LOG_DBG("all modules started"); + + bar->backend.iface->loop(_bar, &expose, &on_mouse); + + LOG_DBG("shutting down"); + + /* Wait for modules to terminate */ + int ret = 0; + int mod_ret; + for (size_t i = 0; i < bar->left.count; i++) { + thrd_join(thrd_left[i], &mod_ret); + if (mod_ret != 0) + LOG_ERR("module: LEFT #%zu: non-zero exit value: %d", i, mod_ret); + ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; + } + for (size_t i = 0; i < bar->center.count; i++) { + thrd_join(thrd_center[i], &mod_ret); + if (mod_ret != 0) + LOG_ERR("module: CENTER #%zu: non-zero exit value: %d", i, mod_ret); + ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; + } + for (size_t i = 0; i < bar->right.count; i++) { + thrd_join(thrd_right[i], &mod_ret); + if (mod_ret != 0) + LOG_ERR("module: RIGHT #%zu: non-zero exit value: %d", i, mod_ret); + ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; + } + + LOG_DBG("modules joined"); + + for (size_t i = 0; i < bar->left.count; i++) { + struct module *m = bar->left.mods[i]; + struct exposable *e = bar->left.exps[i]; + + if (e != NULL) + e->destroy(e); + m->destroy(m); + } + for (size_t i = 0; i < bar->center.count; i++) { + struct module *m = bar->center.mods[i]; + struct exposable *e = bar->center.exps[i]; + + if (e != NULL) + e->destroy(e); + m->destroy(m); + } + for (size_t i = 0; i < bar->right.count; i++) { + struct module *m = bar->right.mods[i]; + struct exposable *e = bar->right.exps[i]; + + if (e != NULL) + e->destroy(e); + m->destroy(m); + } + + bar->backend.iface->cleanup(_bar); + + if (bar->cairo) + cairo_destroy(bar->cairo); + if (bar->cairo_surface) { + cairo_device_finish(cairo_surface_get_device(bar->cairo_surface)); + cairo_surface_finish(bar->cairo_surface); + cairo_surface_destroy(bar->cairo_surface); + } + cairo_debug_reset_static_data(); + + LOG_DBG("bar exiting"); + return ret; +} + +static void +destroy(struct bar *bar) +{ + struct private *b = bar->private; + + free(b->left.mods); + free(b->left.exps); + free(b->center.mods); + free(b->center.exps); + free(b->right.mods); + free(b->right.exps); + free(b->monitor); + free(b->backend.data); + + free(bar->private); + free(bar); +} + +struct bar * +bar_new(const struct bar_config *config) +{ + struct private *priv = malloc(sizeof(*priv)); + priv->monitor = config->monitor != NULL ? strdup(config->monitor) : NULL; + priv->location = config->location; + priv->height = config->height; + priv->background = config->background; + priv->left_spacing = config->left_spacing; + priv->right_spacing = config->right_spacing; + priv->left_margin = config->left_margin; + priv->right_margin = config->right_margin; + priv->border.width = config->border.width; + priv->border.color = config->border.color; + priv->left.mods = malloc(config->left.count * sizeof(priv->left.mods[0])); + priv->left.exps = malloc(config->left.count * sizeof(priv->left.exps[0])); + priv->center.mods = malloc(config->center.count * sizeof(priv->center.mods[0])); + priv->center.exps = malloc(config->center.count * sizeof(priv->center.exps[0])); + priv->right.mods = malloc(config->right.count * sizeof(priv->right.mods[0])); + priv->right.exps = malloc(config->right.count * sizeof(priv->right.exps[0])); + priv->left.count = config->left.count; + priv->center.count = config->center.count; + priv->right.count = config->right.count; + priv->cursor_name = NULL; + +#if defined(ENABLE_X11) && !defined(ENABLE_WAYLAND) + priv->backend.data = bar_backend_xcb_new(); + priv->backend.iface = &xcb_backend_iface; +#else +#if !defined(ENABLE_X11) && defined(ENABLE_WAYLAND) + priv->backend.data = bar_backend_wayland_new(); + priv->backend.iface = &wayland_backend_iface; +#else + if (getenv("WAYLAND_DISPLAY") != NULL) { + priv->backend.data = bar_backend_wayland_new(); + priv->backend.iface = &wayland_backend_iface; + } else { + priv->backend.data = bar_backend_xcb_new(); + priv->backend.iface = &xcb_backend_iface; + } +#endif +#endif + + for (size_t i = 0; i < priv->left.count; i++) { + priv->left.mods[i] = config->left.mods[i]; + priv->left.exps[i] = NULL; + } + for (size_t i = 0; i < priv->center.count; i++) { + priv->center.mods[i] = config->center.mods[i]; + priv->center.exps[i] = NULL; + } + for (size_t i = 0; i < priv->right.count; i++) { + priv->right.mods[i] = config->right.mods[i]; + priv->right.exps[i] = NULL; + } + + struct bar *bar = malloc(sizeof(*bar)); + bar->private = priv; + bar->run = &run; + bar->destroy = &destroy; + bar->refresh = &refresh; + bar->set_cursor = &set_cursor; + + for (size_t i = 0; i < priv->left.count; i++) + priv->left.mods[i]->bar = bar; + for (size_t i = 0; i < priv->center.count; i++) + priv->center.mods[i]->bar = bar; + for (size_t i = 0; i < priv->right.count; i++) + priv->right.mods[i]->bar = bar; + + return bar; +} diff --git a/bar.h b/bar/bar.h similarity index 94% rename from bar.h rename to bar/bar.h index 3839b01..b4765a2 100644 --- a/bar.h +++ b/bar/bar.h @@ -1,7 +1,7 @@ #pragma once -#include "color.h" -#include "module.h" +#include "../color.h" +#include "../module.h" struct bar { int abort_fd; diff --git a/bar/private.h b/bar/private.h new file mode 100644 index 0000000..647fb20 --- /dev/null +++ b/bar/private.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#include "../bar/bar.h" +#include "backend.h" + +struct private { + /* From bar_config */ + char *monitor; + enum bar_location location; + int height; + int left_spacing, right_spacing; + int left_margin, right_margin; + + struct rgba background; + + struct { + int width; + struct rgba color; + } border; + + struct { + struct module **mods; + struct exposable **exps; + size_t count; + } left; + struct { + struct module **mods; + struct exposable **exps; + size_t count; + } center; + struct { + struct module **mods; + struct exposable **exps; + size_t count; + } right; + + /* Calculated run-time */ + int x, y; + int width; + int height_with_border; + + /* Name of currently active cursor */ + char *cursor_name; + + cairo_t *cairo; + cairo_surface_t *cairo_surface; + + struct { + void *data; + const struct backend *iface; + } backend; +#if 0 + /* Backend specifics */ + 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; +#endif +}; diff --git a/bar/wayland.c b/bar/wayland.c new file mode 100644 index 0000000..bb33ac7 --- /dev/null +++ b/bar/wayland.c @@ -0,0 +1,926 @@ +#include "wayland.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#define LOG_MODULE "bar:wayland" +#include "../log.h" +#include "../tllist.h" + +#include "private.h" + +struct buffer { + bool busy; + size_t size; + void *mmapped; + + struct wl_buffer *wl_buf; + + cairo_surface_t *cairo_surface; + cairo_t *cairo; +}; + +struct monitor { + struct wayland_backend *backend; + + struct wl_output *output; + struct zxdg_output_v1 *xdg; + char *name; + + int x; + int y; + + int width_mm; + int height_mm; + + int width_px; + int height_px; + + int scale; +}; + +struct wayland_backend { + struct bar *bar; + + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_surface *surface; + struct zwlr_layer_shell_v1 *layer_shell; + struct zwlr_layer_surface_v1 *layer_surface; + struct wl_shm *shm; + struct wl_seat *seat; + + struct { + struct wl_pointer *pointer; + uint32_t serial; + + int x; + int y; + + struct wl_surface *surface; + struct wl_cursor_theme *theme; + struct wl_cursor *cursor; + } pointer; + + tll(struct monitor) monitors; + const struct monitor *monitor; + + struct zxdg_output_manager_v1 *xdg_output_manager; + + /* TODO: set directly in bar instead */ + int width, height; + + /* Used to signal e.g. refresh */ + int pipe_fds[2]; + + /* We're already waiting for a frame done callback */ + bool render_scheduled; + + tll(struct buffer) buffers; /* List of SHM buffers */ + struct buffer *next_buffer; /* Bar is rendering to this one */ + struct buffer *pending_buffer; /* Finished, but not yet rendered */ + + void (*bar_expose)(const struct bar *bar); + void (*bar_on_mouse)(struct bar *bar, enum mouse_event event, int x, int y); +}; + +void * +bar_backend_wayland_new(void) +{ + return calloc(1, sizeof(struct wayland_backend)); +} + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + //printf("SHM format: 0x%08x\n", format); +} + +static const struct wl_shm_listener shm_listener = { + .format = &shm_format, +}; + +static void +update_cursor_surface(struct wayland_backend *backend) +{ + if (backend->pointer.cursor == NULL) + return; + + struct wl_cursor_image *image = backend->pointer.cursor->images[0]; + + wl_surface_attach( + backend->pointer.surface, wl_cursor_image_get_buffer(image), 0, 0); + + wl_pointer_set_cursor( + backend->pointer.pointer, backend->pointer.serial, + backend->pointer.surface, image->hotspot_x, image->hotspot_y); + + wl_surface_damage_buffer( + backend->pointer.surface, 0, 0, INT32_MAX, INT32_MAX); + + wl_surface_commit(backend->pointer.surface); +} + +static void +wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + struct wayland_backend *backend = data; + backend->pointer.serial = serial; + backend->pointer.x = wl_fixed_to_int(surface_x); + backend->pointer.y = wl_fixed_to_int(surface_y); + + update_cursor_surface(backend); +} + +static void +wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + struct wayland_backend *backend = data; + + backend->pointer.x = wl_fixed_to_int(surface_x); + backend->pointer.y = wl_fixed_to_int(surface_y); + + backend->bar_on_mouse( + backend->bar, ON_MOUSE_MOTION, backend->pointer.x, backend->pointer.y); +} + +static void +wl_pointer_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t state) +{ + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + return; + + struct wayland_backend *backend = data; + backend->bar_on_mouse( + backend->bar, ON_MOUSE_CLICK, backend->pointer.x, backend->pointer.y); +} + +static void +wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ +} + +static void +wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) +{ +} + +static void +wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, + uint32_t axis_source) +{ +} + +static void +wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) +{ +} + +static void +wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t discrete) +{ +} + +static const struct wl_pointer_listener pointer_listener = { + .enter = wl_pointer_enter, + .leave = wl_pointer_leave, + .motion = wl_pointer_motion, + .button = wl_pointer_button, + .axis = wl_pointer_axis, + .frame = wl_pointer_frame, + .axis_source = wl_pointer_axis_source, + .axis_stop = wl_pointer_axis_stop, + .axis_discrete = wl_pointer_axis_discrete, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) +{ + struct wayland_backend *backend = data; + + if (backend->pointer.pointer != NULL) { + wl_pointer_release(backend->pointer.pointer); + backend->pointer.pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_POINTER)) { + backend->pointer.pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(backend->pointer.pointer, &pointer_listener, backend); + } +} + +static void +seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) +{ +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +}; + +static void +output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, + int32_t physical_width, int32_t physical_height, + int32_t subpixel, const char *make, const char *model, + int32_t transform) +{ + struct monitor *mon = data; + mon->x = x; + mon->y = y; + mon->width_mm = physical_width; + mon->height_mm = physical_height; +} + +static void +output_mode(void *data, struct wl_output *wl_output, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) +{ + struct monitor *mon = data; + mon->width_px = width; + mon->height_px = height; +} + +static void +output_done(void *data, struct wl_output *wl_output) +{ +} + +static void +output_scale(void *data, struct wl_output *wl_output, int32_t factor) +{ + struct monitor *mon = data; + mon->scale = factor; +} + + +static const struct wl_output_listener output_listener = { + .geometry = &output_geometry, + .mode = &output_mode, + .done = &output_done, + .scale = &output_scale, +}; + +static void +xdg_output_handle_logical_position(void *data, + struct zxdg_output_v1 *xdg_output, + int32_t x, int32_t y) +{ +} + +static void +xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, + int32_t width, int32_t height) +{ +} + +static void +xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) +{ +} + +static void +xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, + const char *name) +{ + struct monitor *mon = data; + mon->name = strdup(name); +} + +static void +xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, + const char *description) +{ +} + +static struct zxdg_output_v1_listener xdg_output_listener = { + .logical_position = xdg_output_handle_logical_position, + .logical_size = xdg_output_handle_logical_size, + .done = xdg_output_handle_done, + .name = xdg_output_handle_name, + .description = xdg_output_handle_description, +}; + +static void +handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + struct wayland_backend *backend = data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + backend->compositor = wl_registry_bind( + registry, name, &wl_compositor_interface, 4); + } + + else if (strcmp(interface, wl_shm_interface.name) == 0) { + backend->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(backend->shm, &shm_listener, backend); + wl_display_roundtrip(backend->display); + } + + else if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *output = wl_registry_bind( + registry, name, &wl_output_interface, 3); + + tll_push_back(backend->monitors, ((struct monitor){ + .backend = backend, + .output = output})); + + struct monitor *mon = &tll_back(backend->monitors); + wl_output_add_listener(output, &output_listener, mon); + + /* + * The "output" interface doesn't give us the monitors' + * identifiers (e.g. "LVDS-1"). Use the XDG output interface + * for that. + */ + + assert(backend->xdg_output_manager != NULL); + mon->xdg = zxdg_output_manager_v1_get_xdg_output( + backend->xdg_output_manager, mon->output); + + zxdg_output_v1_add_listener(mon->xdg, &xdg_output_listener, mon); + wl_display_roundtrip(backend->display); + } + + else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + backend->layer_shell = wl_registry_bind( + registry, name, &zwlr_layer_shell_v1_interface, 1); + } + + else if (strcmp(interface, wl_seat_interface.name) == 0) { + backend->seat = wl_registry_bind(registry, name, &wl_seat_interface, 3); + wl_seat_add_listener(backend->seat, &seat_listener, backend); + wl_display_roundtrip(backend->display); + } + + else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { + backend->xdg_output_manager = wl_registry_bind( + registry, name, &zxdg_output_manager_v1_interface, 2); + } +} + +static void +handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + .global = &handle_global, + .global_remove = &handle_global_remove, +}; + +static void +layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t w, uint32_t h) +{ + struct wayland_backend *backend = data; + backend->width = w; + backend->height = h; + + zwlr_layer_surface_v1_ack_configure(surface, serial); +} + +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, +}; + +static void +buffer_release(void *data, struct wl_buffer *wl_buffer) +{ + //printf("buffer release\n"); + struct buffer *buffer = data; + assert(buffer->busy); + buffer->busy = false; +} + +static const struct wl_buffer_listener buffer_listener = { + .release = &buffer_release, +}; + +static struct buffer * +get_buffer(struct wayland_backend *backend) +{ + tll_foreach(backend->buffers, it) { + if (!it->item.busy) { + it->item.busy = true; + return &it->item; + } + } + + /* + * No existing buffer available. Create a new one by: + * + * 1. open a memory backed "file" with memfd_create() + * 2. mmap() the memory file, to be used by the cairo surface + * 3. create a wayland shm buffer for the same memory file + * + * The cairo surface and the wayland buffer are now sharing + * memory. + */ + + int pool_fd = -1; + void *mmapped = NULL; + size_t size = 0; + + struct wl_shm_pool *pool = NULL; + struct wl_buffer *buf = NULL; + + cairo_surface_t *cairo_surface = NULL; + cairo_t *cairo = NULL; + + /* Backing memory for SHM */ + pool_fd = memfd_create("wayland-test-buffer-pool", MFD_CLOEXEC); + if (pool_fd == -1) { + LOG_ERRNO("failed to create SHM backing memory file"); + goto err; + } + + /* Total size */ + uint32_t stride = backend->width * 4; + size = stride * backend->height; + ftruncate(pool_fd, size); + + mmapped = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, pool_fd, 0); + if (mmapped == MAP_FAILED) { + LOG_ERR("failed to mmap SHM backing memory file"); + goto err; + } + + pool = wl_shm_create_pool(backend->shm, pool_fd, size); + if (pool == NULL) { + LOG_ERR("failed to create SHM pool"); + goto err; + } + + buf = wl_shm_pool_create_buffer( + pool, 0, backend->width, backend->height, stride, WL_SHM_FORMAT_ARGB8888); + if (buf == NULL) { + LOG_ERR("failed to create SHM buffer"); + goto err; + } + + /* We use the entire pool for our single buffer */ + wl_shm_pool_destroy(pool); pool = NULL; + close(pool_fd); pool_fd = -1; + + /* Create a cairo surface around the mmapped memory */ + cairo_surface = cairo_image_surface_create_for_data( + mmapped, CAIRO_FORMAT_ARGB32, backend->width, backend->height, stride); + if (cairo_surface_status(cairo_surface) != CAIRO_STATUS_SUCCESS) { + LOG_ERR("failed to create cairo surface: %s", + cairo_status_to_string(cairo_surface_status(cairo_surface))); + goto err; + } + + cairo = cairo_create(cairo_surface); + if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS) { + LOG_ERR("failed to create cairo context: %s", + cairo_status_to_string(cairo_status(cairo))); + goto err; + } + + /* Push to list of available buffers, but marked as 'busy' */ + tll_push_back( + backend->buffers, + ((struct buffer){ + .busy = true, + .size = size, + .mmapped = mmapped, + .wl_buf = buf, + .cairo_surface = cairo_surface, + .cairo = cairo} + ) + ); + + struct buffer *ret = &tll_back(backend->buffers); + wl_buffer_add_listener(ret->wl_buf, &buffer_listener, ret); + return ret; + +err: + if (cairo != NULL) + cairo_destroy(cairo); + if (cairo_surface != NULL) + cairo_surface_destroy(cairo_surface); + if (buf != NULL) + wl_buffer_destroy(buf); + if (pool != NULL) + wl_shm_pool_destroy(pool); + if (pool_fd != -1) + close(pool_fd); + if (mmapped != NULL) + munmap(mmapped, size); + + return NULL; +} + +static bool +setup(struct bar *_bar) +{ + struct private *bar = _bar->private; + struct wayland_backend *backend = bar->backend.data; + + backend->bar = _bar; + + backend->display = wl_display_connect(NULL); + if (backend->display == NULL) { + LOG_ERR("failed to connect to wayland; no compistor running?"); + return false; + } + + backend->registry = wl_display_get_registry(backend->display); + if (backend->registry == NULL) { + LOG_ERR("failed to get wayland registry"); + return false; + } + + wl_registry_add_listener(backend->registry, ®istry_listener, backend); + wl_display_roundtrip(backend->display); + + if (backend->compositor == NULL) { + LOG_ERR("no compositor"); + return false; + } + if (backend->layer_shell == NULL) { + LOG_ERR("no layer shell interface"); + return false; + } + if (backend->shm == NULL) { + LOG_ERR("no shared memory buffers interface"); + return false; + } + + if (tll_length(backend->monitors) == 0) { + LOG_ERR("no monitors"); + return false; + } + + tll_foreach(backend->monitors, it) { + const struct monitor *mon = &it->item; + LOG_INFO("monitor: %s: %dx%d+%d+%d (%dx%dmm)", + mon->name, mon->width_px, mon->height_px, + mon->x, mon->y, mon->width_mm, mon->height_mm); + + /* TODO: detect primary output when user hasn't specified a monitor */ + if (bar->monitor == NULL) + backend->monitor = mon; + else if (strcmp(bar->monitor, mon->name) == 0) + backend->monitor = mon; + } + + backend->surface = wl_compositor_create_surface(backend->compositor); + if (backend->surface == NULL) { + LOG_ERR("failed to create panel surface"); + return false; + } + + backend->pointer.surface = wl_compositor_create_surface(backend->compositor); + if (backend->pointer.surface == NULL) { + LOG_ERR("failed to create cursor surface"); + return false; + } + + backend->pointer.theme = wl_cursor_theme_load(NULL, 24, backend->shm); + if (backend->pointer.theme == NULL) { + LOG_ERR("failed to load cursor theme"); + return false; + } + + backend->layer_surface = zwlr_layer_shell_v1_get_layer_surface( + backend->layer_shell, backend->surface, backend->monitor->output, + ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, "f00bar"); + if (backend->layer_surface == NULL) { + LOG_ERR("failed to create layer shell surface"); + return false; + } + + /* Aligned to top, maximum width */ + enum zwlr_layer_surface_v1_anchor top_or_bottom = bar->location == BAR_TOP + ? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + : ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + + zwlr_layer_surface_v1_set_anchor( + backend->layer_surface, + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + top_or_bottom); + + zwlr_layer_surface_v1_set_size( + backend->layer_surface, 0, bar->height_with_border); + zwlr_layer_surface_v1_set_exclusive_zone( + backend->layer_surface, bar->height_with_border); + + //zwlr_layer_surface_v1_set_margin( + // layer_surface, margin_top, margin_right, margin_bottom, margin_left); + zwlr_layer_surface_v1_set_keyboard_interactivity(backend->layer_surface, 1); + + zwlr_layer_surface_v1_add_listener( + backend->layer_surface, &layer_surface_listener, backend); + + /* Trigger a 'configure' event, after which we'll have the width */ + wl_surface_commit(backend->surface); + wl_display_roundtrip(backend->display); + + if (backend->width == -1 || backend->height != bar->height_with_border) { + LOG_ERR("failed to get panel width"); + return false; + } + + bar->width = backend->width; + bar->x = backend->monitor->x; + bar->y = backend->monitor->y; + bar->y += bar->location == BAR_TOP + ? 0 + : backend->monitor->height_px - bar->height_with_border; + + if (pipe(backend->pipe_fds) == -1) { + LOG_ERRNO("failed to create pipe"); + return false; + } + + backend->render_scheduled = false; + + //wl_surface_commit(backend->surface); + //wl_display_roundtrip(backend->display); + + /* Prepare a buffer + cairo context for bar to draw to */ + backend->next_buffer = get_buffer(backend); + assert(backend->next_buffer != NULL && backend->next_buffer->busy); + + bar->cairo_surface = backend->next_buffer->cairo_surface; + bar->cairo = backend->next_buffer->cairo; + + return true; +} + +static void +cleanup(struct bar *_bar) +{ + struct private *bar = _bar->private; + struct wayland_backend *backend = bar->backend.data; + + tll_foreach(backend->buffers, it) { + if (it->item.wl_buf != NULL) + wl_buffer_destroy(it->item.wl_buf); + if (it->item.cairo != NULL) + cairo_destroy(it->item.cairo); + if (it->item.cairo_surface != NULL) + cairo_surface_destroy(it->item.cairo_surface); + + munmap(it->item.mmapped, it->item.size); + tll_remove(backend->buffers, it); + } + + tll_foreach(backend->monitors, it) { + struct monitor *mon = &it->item; + free(mon->name); + + if (mon->xdg != NULL) + zxdg_output_v1_destroy(mon->xdg); + if (mon->output != NULL) + wl_output_destroy(mon->output); + tll_remove(backend->monitors, it); + } + + if (backend->xdg_output_manager != NULL) + zxdg_output_manager_v1_destroy(backend->xdg_output_manager); + + /* TODO: move to bar */ + free(bar->cursor_name); + + if (backend->layer_surface != NULL) + zwlr_layer_surface_v1_destroy(backend->layer_surface); + if (backend->layer_shell != NULL) + zwlr_layer_shell_v1_destroy(backend->layer_shell); + if (backend->pointer.theme != NULL) + wl_cursor_theme_destroy(backend->pointer.theme); + if (backend->pointer.pointer != NULL) + wl_pointer_destroy(backend->pointer.pointer); + if (backend->pointer.surface != NULL) + wl_surface_destroy(backend->pointer.surface); + if (backend->surface != NULL) + wl_surface_destroy(backend->surface); + if (backend->seat != NULL) + wl_seat_destroy(backend->seat); + if (backend->compositor != NULL) + wl_compositor_destroy(backend->compositor); + if (backend->shm != NULL) + wl_shm_destroy(backend->shm); + if (backend->registry != NULL) + wl_registry_destroy(backend->registry); + if (backend->display != NULL) + wl_display_disconnect(backend->display); + + /* Destroyed when freeing buffer list */ + bar->cairo_surface = NULL; + bar->cairo = NULL; + +} + +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 wayland_backend *backend = bar->backend.data; + + backend->bar_expose = expose; + backend->bar_on_mouse = on_mouse; + +#if 0 + while (wl_display_prepare_read(backend->display) != 0){ + //printf("initial wayland event\n"); + wl_display_dispatch_pending(backend->display); + } + wl_display_flush(backend->display); +#endif + + wl_display_dispatch_pending(backend->display); + wl_display_flush(backend->display); + + while (true) { + struct pollfd fds[] = { + {.fd = _bar->abort_fd, .events = POLLIN}, + {.fd = wl_display_get_fd(backend->display), .events = POLLIN}, + {.fd = backend->pipe_fds[0], .events = POLLIN}, + }; + + wl_display_flush(backend->display); + + //printf("polling\n"); + poll(fds, sizeof(fds) / sizeof(fds[0]), -1); + if (fds[0].revents & POLLIN) { + //wl_display_cancel_read(backend->display); + break; + } + + if (fds[1].revents & POLLHUP) { + LOG_WARN("disconnceted from wayland"); + write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)); + break; + } + + if (fds[2].revents & POLLIN) { + uint8_t command; + //wl_display_cancel_read(backend->display); + read(backend->pipe_fds[0], &command, sizeof(command)); + + assert(command == 1); + //printf("refresh\n"); + expose(_bar); +#if 0 + while (wl_display_prepare_read(backend->display) != 0) { + //printf("queued wayland events\n"); + wl_display_dispatch_pending(backend->display); + } + wl_display_flush(backend->display); +#endif + } + + if (fds[1].revents & POLLIN) { +#if 0 + //printf("wayland events\n"); + wl_display_read_events(backend->display); + wl_display_dispatch_pending(backend->display); +#endif + //printf("wayland events\n"); + wl_display_dispatch(backend->display); + } + } +} + +static void frame_callback( + void *data, struct wl_callback *wl_callback, uint32_t callback_data); + +static const struct wl_callback_listener frame_listener = { + .done = &frame_callback, +}; + +static void +frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data) +{ + //printf("frame callback\n"); + struct private *bar = data; + struct wayland_backend *backend = bar->backend.data; + + backend->render_scheduled = false; + + wl_callback_destroy(wl_callback); + + if (backend->pending_buffer != NULL) { + struct buffer *buffer = backend->pending_buffer; + assert(buffer->busy); + + wl_surface_attach(backend->surface, buffer->wl_buf, 0, 0); + wl_surface_damage(backend->surface, 0, 0, backend->width, backend->height); + + struct wl_callback *cb = wl_surface_frame(backend->surface); + wl_callback_add_listener(cb, &frame_listener, bar); + wl_surface_commit(backend->surface); + + backend->pending_buffer = NULL; + backend->render_scheduled = true; + } else + ;//printf("nothing more to do\n"); +} + +static void +commit_surface(const struct bar *_bar) +{ + struct private *bar = _bar->private; + struct wayland_backend *backend = bar->backend.data; + + //printf("commit: %dxl%d\n", backend->width, backend->height); + + assert(backend->next_buffer != NULL); + assert(backend->next_buffer->busy); + + if (backend->render_scheduled) { + //printf("already scheduled\n"); + + if (backend->pending_buffer != NULL) + backend->pending_buffer->busy = false; + + backend->pending_buffer = backend->next_buffer; + backend->next_buffer = NULL; + } else { + + //printf("scheduling new frame callback\n"); + struct buffer *buffer = backend->next_buffer; + assert(buffer->busy); + + wl_surface_attach(backend->surface, buffer->wl_buf, 0, 0); + wl_surface_damage(backend->surface, 0, 0, backend->width, backend->height); + + struct wl_callback *cb = wl_surface_frame(backend->surface); + wl_callback_add_listener(cb, &frame_listener, bar); + wl_surface_commit(backend->surface); + + backend->render_scheduled = true; + } + + backend->next_buffer = get_buffer(backend); + assert(backend->next_buffer != NULL && backend->next_buffer->busy); + + bar->cairo_surface = backend->next_buffer->cairo_surface; + bar->cairo = backend->next_buffer->cairo; +} + +static void +refresh(const struct bar *_bar) +{ + const struct private *bar = _bar->private; + const struct wayland_backend *backend = bar->backend.data; + + write(backend->pipe_fds[1], &(uint8_t){1}, sizeof(uint8_t)); +} + +static void +set_cursor(struct bar *_bar, const char *cursor) +{ + struct private *bar = _bar->private; + struct wayland_backend *backend = bar->backend.data; + + backend->pointer.cursor = wl_cursor_theme_get_cursor( + backend->pointer.theme, cursor); + + update_cursor_surface(backend); +} + +const struct backend wayland_backend_iface = { + .setup = &setup, + .cleanup = &cleanup, + .loop = &loop, + .commit_surface = &commit_surface, + .refresh = &refresh, + .set_cursor = &set_cursor, +}; diff --git a/bar/wayland.h b/bar/wayland.h new file mode 100644 index 0000000..465ef46 --- /dev/null +++ b/bar/wayland.h @@ -0,0 +1,7 @@ +#pragma once + +#include "backend.h" + +extern const struct backend wayland_backend_iface; + +void *bar_backend_wayland_new(void); diff --git a/bar/xcb.c b/bar/xcb.c new file mode 100644 index 0000000..3f0d284 --- /dev/null +++ b/bar/xcb.c @@ -0,0 +1,422 @@ +#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) +{ + xcb_init(); + 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; + } + + /* 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->conn == NULL) + return; + + if (backend->cursor != 0) + xcb_free_cursor(backend->conn, backend->cursor); + if (backend->cursor_ctx != NULL) + xcb_cursor_context_free(backend->cursor_ctx); + + /* TODO: move to bar.c */ + free(bar->cursor_name); + + if (backend->gc != 0) + xcb_free_gc(backend->conn, backend->gc); + if (backend->pixmap != 0) + xcb_free_pixmap(backend->conn, backend->pixmap); + if (backend->win != 0) + xcb_destroy_window(backend->conn, backend->win); + if (backend->colormap != 0) + xcb_free_colormap(backend->conn, backend->colormap); + + xcb_flush(backend->conn); + xcb_disconnect(backend->conn); + backend->conn = NULL; +} + +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 (backend->cursor_ctx == NULL) + return; + + if (backend->cursor != 0) + xcb_free_cursor(backend->conn, backend->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); diff --git a/config.c b/config.c index fdfc1b4..73590ea 100644 --- a/config.c +++ b/config.c @@ -7,7 +7,7 @@ #include -#include "bar.h" +#include "bar/bar.h" #include "color.h" #include "config-verify.h" #include "module.h" diff --git a/decorations/CMakeLists.txt b/decorations/CMakeLists.txt index 3702be6..414d0d1 100644 --- a/decorations/CMakeLists.txt +++ b/decorations/CMakeLists.txt @@ -1,8 +1,7 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.13) add_library(decoration-sdk INTERFACE) -target_compile_options(decoration-sdk INTERFACE ${CAIRO_CFLAGS_OTHER}) -target_include_directories(decoration-sdk INTERFACE ${CAIRO_INCLUDE_DIRS}) +target_link_libraries(decoration-sdk INTERFACE PkgConfig::cairo) set(CMAKE_SHARED_MODULE_PREFIX decoration_) @@ -22,3 +21,5 @@ endforeach () if (CORE_PLUGINS_AS_SHARED_LIBRARIES) install(TARGETS ${decorations} DESTINATION lib/f00bar) endif () + +set(enabled_decorations ${decorations} PARENT_SCOPE) diff --git a/external/wlroots b/external/wlroots new file mode 160000 index 0000000..59d1b67 --- /dev/null +++ b/external/wlroots @@ -0,0 +1 @@ +Subproject commit 59d1b6790dd187c2e110e98a2fa17c1fbfee40ca diff --git a/main.c b/main.c index f765eb7..56460e1 100644 --- a/main.c +++ b/main.c @@ -14,10 +14,9 @@ #include #include -#include "bar.h" +#include "bar/bar.h" #include "config.h" #include "yml.h" -#include "xcb.h" #define LOG_MODULE "main" #include "log.h" @@ -47,7 +46,7 @@ get_config_path(void) path_max = 1024; char *path = malloc(path_max + 1); - snprintf(path, path_max + 1, "%s/.config/f00bar/config.yml", home_dir); + snprintf(path, path_max + 1, "%s/.config/f00bar/config-wayland.yml", home_dir); return path; } @@ -121,17 +120,6 @@ main(int argc, const char *const *argv) return 1; } - /* Connect to XCB, to be able to detect a disconnect (allowing us - * to exit) */ - xcb_connection_t *xcb = xcb_connect(NULL, NULL); - if (xcb_connection_has_error(xcb) > 0) { - LOG_ERR("failed to connect to X"); - xcb_disconnect(xcb); - return 1; - } - - xcb_init(); - bar->abort_fd = abort_fd; thrd_t bar_thread; @@ -140,27 +128,19 @@ main(int argc, const char *const *argv) /* Now unblock. We should be only thread receiving SIGINT */ pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); - /* Wait for SIGINT, or XCB disconnect */ while (!aborted) { - struct pollfd fds[] = { - {.fd = xcb_get_file_descriptor(xcb), .events = POLLPRI} - }; + struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}}; + int r __attribute__((unused)) = poll(fds, 1, -1); - poll(fds, 1, -1); - - if (aborted) - break; - - LOG_INFO("XCB poll data"); - - if (fds[0].revents & POLLHUP) { - LOG_INFO("disconnected from XCB, exiting"); - break; - } + /* + * Either the bar aborted (triggering the abort_fd), or user + * killed us (triggering the signal handler which sets + * 'aborted') + */ + assert(aborted || r == 1); + break; } - xcb_disconnect(xcb); - if (aborted) LOG_INFO("aborted: %s (%d)", strsignal(aborted), aborted); diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index d4dc54f..723537d 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,9 +1,8 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.13) add_library(module-sdk INTERFACE) -target_compile_options(module-sdk INTERFACE ${CAIRO_CFLAGS_OTHER}) -target_include_directories(module-sdk INTERFACE ${CAIRO_INCLUDE_DIRS}) -target_link_libraries(module-sdk INTERFACE ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries( + module-sdk INTERFACE PkgConfig::cairo ${CMAKE_THREAD_LIBS_INIT}) if (CORE_PLUGINS_AS_SHARED_LIBRARIES) set(lib_type MODULE) @@ -13,76 +12,70 @@ endif () set(CMAKE_SHARED_${lib_type}_PREFIX module_) -pkg_check_modules(ALSA REQUIRED alsa) -add_library(alsa ${lib_type} alsa.c) -target_compile_options(alsa PRIVATE ${ALSA_CFLAGS_OTHER}) -target_include_directories(alsa PRIVATE ${ALSA_INCLUDE_DIRS}) -target_link_libraries(alsa module-sdk ${ALSA_LIBRARIES}) +set(enabled "") -pkg_check_modules(UDEV REQUIRED libudev) +pkg_check_modules(alsa REQUIRED IMPORTED_TARGET alsa) +add_library(alsa ${lib_type} alsa.c) +target_link_libraries(alsa module-sdk PkgConfig::alsa) +list(APPEND enabled alsa) + +pkg_check_modules(udev REQUIRED IMPORTED_TARGET libudev) add_library(backlight ${lib_type} backlight.c) -target_compile_options(backlight PRIVATE ${UDEV_CFLAGS_OTHER}) -target_include_directories(backlight PRIVATE ${UDEV_INCLUDE_DIRS}) -target_link_libraries(backlight module-sdk ${UDEV_LIBRARIES}) +target_link_libraries(backlight module-sdk PkgConfig::udev) +list(APPEND enabled backlight) add_library(battery ${lib_type} battery.c) -target_compile_options(battery PRIVATE ${UDEV_CFLAGS_OTHER}) -target_include_directories(battery PRIVATE ${UDEV_INCLUDE_DIRS}) -target_link_libraries(battery module-sdk ${UDEV_LIBRARIES}) +target_link_libraries(battery module-sdk PkgConfig::udev) +list(APPEND enabled battery) add_library(clock ${lib_type} clock.c) target_link_libraries(clock module-sdk) +list(APPEND enabled clock) -pkg_check_modules(JSON REQUIRED json-c) +pkg_check_modules(json REQUIRED IMPORTED_TARGET json-c) find_file(I3_IPC_H i3/ipc.h) if (NOT I3_IPC_H) message(FATAL_ERROR "cannot find header file: i3/ipc.h") endif () add_library(i3 ${lib_type} i3.c) -target_compile_options(i3 PRIVATE ${JSON_CFLAGS_OTHER}) -target_include_directories(i3 PRIVATE ${JSON_INCLUDE_DIRS}) -target_link_libraries(i3 module-sdk dynlist ${JSON_LIBRARIES}) +target_link_libraries(i3 module-sdk dynlist PkgConfig::json) +if (ENABLE_X11) + target_link_libraries(i3 xcb-stuff) +endif () +list(APPEND enabled i3) add_library(label ${lib_type} label.c) target_link_libraries(label module-sdk) +list(APPEND enabled label) -pkg_check_modules(MPD REQUIRED libmpdclient) +pkg_check_modules(mpd REQUIRED IMPORTED_TARGET libmpdclient) add_library(mpd ${lib_type} mpd.c) -target_compile_options(mpd PRIVATE ${MPD_CFLAGS_OTHER}) -target_include_directories(mpd PRIVATE ${MPD_INCLUDE_DIRS}) -target_link_libraries(mpd module-sdk ${MPD_LIBRARIES}) +target_link_libraries(mpd module-sdk PkgConfig::mpd) +list(APPEND enabled mpd) add_library(network ${lib_type} network.c) target_link_libraries(network module-sdk) +list(APPEND enabled network) add_library(removables ${lib_type} removables.c) -target_compile_options(removables PRIVATE ${UDEV_CFLAGS_OTHER}) -target_include_directories(removables PRIVATE ${UDEV_INCLUDE_DIRS}) -target_link_libraries(removables module-sdk dynlist ${UDEV_LIBRARIES}) +target_link_libraries(removables module-sdk dynlist PkgConfig::udev) +list(APPEND enabled removables) -pkg_check_modules(XCB_XKB REQUIRED xcb-xkb) -add_library(xkb ${lib_type} xkb.c) -target_compile_options(xkb PRIVATE ${XCB_XKB_CFLAGS_OTHER}) -target_include_directories(xkb PRIVATE ${XCB_XKB_INCLUDE_DIRS}) -target_link_libraries(xkb module-sdk ${XCB_XKB_LIBRARIES}) +if (ENABLE_X11) + pkg_check_modules(xkb REQUIRED IMPORTED_TARGET xcb-xkb) + add_library(xkb ${lib_type} xkb.c) + target_link_libraries(xkb module-sdk PkgConfig::xcb PkgConfig::xkb) + list(APPEND enabled xkb) +endif () -add_library(xwindow ${lib_type} xwindow.c) -target_link_libraries(xwindow module-sdk) +if (ENABLE_X11) + add_library(xwindow ${lib_type} xwindow.c) + target_link_libraries(xwindow module-sdk xcb-stuff) + list(APPEND enabled xwindow) +endif () if (CORE_PLUGINS_AS_SHARED_LIBRARIES) - install( - TARGETS - alsa - backlight - battery - clock - i3 - label - mpd - network - removables - xkb - xwindow - - DESTINATION lib/f00bar) + install(TARGETS ${enabled} DESTINATION lib/f00bar) endif () + +set(enabled_modules ${enabled} PARENT_SCOPE) diff --git a/modules/alsa.c b/modules/alsa.c index 01fdc8b..0cb5e41 100644 --- a/modules/alsa.c +++ b/modules/alsa.c @@ -6,7 +6,7 @@ #define LOG_MODULE "alsa" #define LOG_ENABLE_DBG 0 #include "../log.h" -#include "../bar.h" +#include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../plugin.h" diff --git a/modules/backlight.c b/modules/backlight.c index 69c67ae..69cb8f5 100644 --- a/modules/backlight.c +++ b/modules/backlight.c @@ -12,7 +12,7 @@ #define LOG_MODULE "backlight" #include "../log.h" -#include "../bar.h" +#include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" #include "../plugin.h" diff --git a/modules/battery.c b/modules/battery.c index 777260d..2192bb0 100644 --- a/modules/battery.c +++ b/modules/battery.c @@ -13,7 +13,7 @@ #define LOG_MODULE "battery" #include "../log.h" -#include "../bar.h" +#include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" #include "../plugin.h" diff --git a/modules/clock.c b/modules/clock.c index a374239..27e19fd 100644 --- a/modules/clock.c +++ b/modules/clock.c @@ -5,7 +5,7 @@ #include -#include "../bar.h" +#include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" #include "../plugin.h" diff --git a/modules/i3.c b/modules/i3.c index fbd32c7..b4951a2 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -10,8 +10,11 @@ #include #include -#include -#include +#if defined(ENABLE_X11) + #include + #include +#endif + #include #include @@ -21,12 +24,15 @@ #define LOG_MODULE "i3" #define LOG_ENABLE_DBG 0 #include "../log.h" -#include "../bar.h" +#include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" #include "../particles/dynlist.h" #include "../plugin.h" -#include "../xcb.h" + +#if defined(ENABLE_X11) + #include "../xcb.h" +#endif struct ws_content { char *name; @@ -371,57 +377,83 @@ handle_workspace_event(struct private *m, const struct json_object *json) return true; } +#if defined(ENABLE_X11) +static bool +get_socket_address_x11(struct sockaddr_un *addr) +{ + int default_screen; + xcb_connection_t *conn = xcb_connect(NULL, &default_screen); + if (xcb_connection_has_error(conn) > 0) { + LOG_ERR("failed to connect to X"); + xcb_disconnect(conn); + return false; + } + + xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen); + + xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH"); + assert(atom != XCB_ATOM_NONE); + + xcb_get_property_cookie_t cookie + = xcb_get_property_unchecked( + conn, false, screen->root, atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path)); + + xcb_generic_error_t *err; + xcb_get_property_reply_t *reply = + xcb_get_property_reply(conn, cookie, &err); + + if (err != NULL) { + LOG_ERR("failed to get i3 socket path: %s", xcb_error(err)); + free(err); + free(reply); + return false; + } + + const int len = xcb_get_property_value_length(reply); + assert(len < sizeof(addr->sun_path)); + + if (len == 0) { + LOG_ERR("failed to get i3 socket path: empty reply"); + free(reply); + return false; + } + + memcpy(addr->sun_path, xcb_get_property_value(reply), len); + addr->sun_path[len] = '\0'; + + free(reply); + xcb_disconnect(conn); + return true; +} +#endif + +static bool +get_socket_address(struct sockaddr_un *addr) +{ + *addr = (struct sockaddr_un){.sun_family = AF_UNIX}; + + const char *sway_sock = getenv("SWAYSOCK"); + if (sway_sock == NULL) { +#if defined(ENABLE_X11) + return get_socket_address_x11(addr); +#else + return false; +#endif + } + + strncpy(addr->sun_path, sway_sock, sizeof(addr->sun_path) - 1); + return true; +} + static int run(struct module *mod) { struct private *m = mod->private; - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - { - int default_screen; - xcb_connection_t *conn = xcb_connect(NULL, &default_screen); - if (xcb_connection_has_error(conn) > 0) { - LOG_ERR("failed to connect to X"); - xcb_disconnect(conn); - return 1; - } - - xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen); - - xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH"); - assert(atom != XCB_ATOM_NONE); - - xcb_get_property_cookie_t cookie - = xcb_get_property_unchecked( - conn, false, screen->root, atom, - XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr.sun_path)); - - xcb_generic_error_t *err; - xcb_get_property_reply_t *reply = - xcb_get_property_reply(conn, cookie, &err); - - if (err != NULL) { - LOG_ERR("failed to get i3 socket path: %s", xcb_error(err)); - free(err); - free(reply); - return 1; - } - - const int len = xcb_get_property_value_length(reply); - assert(len < sizeof(addr.sun_path)); - - if (len == 0) { - LOG_ERR("failed to get i3 socket path: empty reply"); - free(reply); - return 1; - } - - memcpy(addr.sun_path, xcb_get_property_value(reply), len); - addr.sun_path[len] = '\0'; - - free(reply); - xcb_disconnect(conn); - } + struct sockaddr_un addr; + if (!get_socket_address(&addr)) + return 1; int sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock == -1) { @@ -440,8 +472,10 @@ run(struct module *mod) send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\"]"); - /* Some replies are *big*. TODO: grow dynamically */ - static const size_t reply_buf_size = 1 * 1024 * 1024; + /* Initial reply typically requires a couple of KB. But we often + * need more later. For example, switching workspaces can result + * in quite big notification messages. */ + size_t reply_buf_size = 4096; char *buf = malloc(reply_buf_size); size_t buf_idx = 0; @@ -461,6 +495,23 @@ run(struct module *mod) break; assert(fds[1].revents & POLLIN); + + /* Grow receive buffer, if necessary */ + if (buf_idx == reply_buf_size) { + LOG_DBG("growing reply buffer: %zu -> %zu", + reply_buf_size, reply_buf_size * 2); + + char *new_buf = realloc(buf, reply_buf_size * 2); + if (new_buf == NULL) { + LOG_ERR("failed to grow reply buffer from %zu to %zu bytes", + reply_buf_size, reply_buf_size * 2); + break; + } + + buf = new_buf; + reply_buf_size *= 2; + } + assert(reply_buf_size > buf_idx); ssize_t bytes = read(sock, &buf[buf_idx], reply_buf_size - buf_idx); @@ -472,6 +523,8 @@ run(struct module *mod) buf_idx += bytes; bool err = false; + bool need_bar_refresh = false; + while (!err && buf_idx >= sizeof(i3_ipc_header_t)) { const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf; if (strncmp(hdr->magic, I3_IPC_MAGIC, sizeof(hdr->magic)) != 0) { @@ -519,12 +572,12 @@ run(struct module *mod) case I3_IPC_REPLY_TYPE_WORKSPACES: handle_get_workspaces_reply(m, json); - mod->bar->refresh(mod->bar); + need_bar_refresh = true; break; case I3_IPC_EVENT_WORKSPACE: handle_workspace_event(m, json); - mod->bar->refresh(mod->bar); + need_bar_refresh = true; break; case I3_IPC_EVENT_OUTPUT: @@ -551,6 +604,9 @@ run(struct module *mod) if (err) break; + + if (need_bar_refresh) + mod->bar->refresh(mod->bar); } free(buf); diff --git a/modules/mpd.c b/modules/mpd.c index 8c3a7ae..b91c057 100644 --- a/modules/mpd.c +++ b/modules/mpd.c @@ -22,7 +22,7 @@ #define LOG_MODULE "mpd" #define LOG_ENABLE_DBG 0 #include "../log.h" -#include "../bar.h" +#include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" #include "../plugin.h" @@ -195,12 +195,15 @@ wait_for_socket_create(const struct module *mod) LOG_DBG("monitoring %s for %s to be created", directory, base); int fd = inotify_init(); - if (fd == -1) + if (fd == -1) { + free(copy); return false; + } int wd = inotify_add_watch(fd, directory, IN_CREATE); if (wd == -1) { close(fd); + free(copy); return false; } @@ -213,7 +216,7 @@ wait_for_socket_create(const struct module *mod) int s = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr = {.sun_family = AF_UNIX}; - strncpy(addr.sun_path, m->host, sizeof(addr.sun_path)); + strncpy(addr.sun_path, m->host, sizeof(addr.sun_path) - 1); int r = connect(s, (const struct sockaddr *)&addr, sizeof(addr)); @@ -231,6 +234,7 @@ wait_for_socket_create(const struct module *mod) if (!have_mpd_socket) LOG_WARN("MPD doesn't appear to be running"); + bool ret = false; while (!have_mpd_socket) { struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, @@ -239,8 +243,10 @@ wait_for_socket_create(const struct module *mod) poll(fds, 2, -1); - if (fds[0].revents & POLLIN) - return true; + if (fds[0].revents & POLLIN) { + ret = true; + break; + } assert(fds[1].revents & POLLIN); @@ -264,7 +270,7 @@ wait_for_socket_create(const struct module *mod) inotify_rm_watch(fd, wd); close(fd); free(copy); - return false; + return ret; } static struct mpd_connection * diff --git a/modules/network.c b/modules/network.c index 6da16af..2c6522f 100644 --- a/modules/network.c +++ b/modules/network.c @@ -16,7 +16,7 @@ #define LOG_MODULE "network" #define LOG_ENABLE_DBG 0 #include "../log.h" -#include "../bar.h" +#include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" #include "../module.h" diff --git a/modules/removables.c b/modules/removables.c index 5bbaf2c..e05db7d 100644 --- a/modules/removables.c +++ b/modules/removables.c @@ -15,7 +15,7 @@ #define LOG_MODULE "removables" #define LOG_ENABLE_DBG 0 #include "../log.h" -#include "../bar.h" +#include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" #include "../particles/dynlist.h" diff --git a/modules/xkb.c b/modules/xkb.c index 04eb74a..ae86c38 100644 --- a/modules/xkb.c +++ b/modules/xkb.c @@ -11,7 +11,7 @@ #define LOG_MODULE "xkb" #define LOG_ENABLE_DBG 0 #include "../log.h" -#include "../bar.h" +#include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" #include "../plugin.h" diff --git a/modules/xwindow.c b/modules/xwindow.c index bb07ea4..a784e37 100644 --- a/modules/xwindow.c +++ b/modules/xwindow.c @@ -15,7 +15,7 @@ #define LOG_MODULE "xwindow" #include "../log.h" -#include "../bar.h" +#include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" #include "../plugin.h" diff --git a/particle.c b/particle.c index 0c926bc..dfca044 100644 --- a/particle.c +++ b/particle.c @@ -10,7 +10,7 @@ #define LOG_MODULE "particle" #define LOG_ENABLE_DBG 0 #include "log.h" -#include "bar.h" +#include "bar/bar.h" void particle_default_destroy(struct particle *particle) diff --git a/particles/CMakeLists.txt b/particles/CMakeLists.txt index 0ca7674..6fd211c 100644 --- a/particles/CMakeLists.txt +++ b/particles/CMakeLists.txt @@ -1,8 +1,7 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.13) add_library(particle-sdk INTERFACE) -target_compile_options(particle-sdk INTERFACE ${CAIRO_CFLAGS_OTHER}) -target_include_directories(particle-sdk INTERFACE ${CAIRO_INCLUDE_DIRS}) +target_link_libraries(particle-sdk INTERFACE PkgConfig::cairo) if (CORE_PLUGINS_AS_SHARED_LIBRARIES) set(lib_type MODULE) @@ -30,3 +29,5 @@ target_link_libraries(string ${CAIRO_LIBRARIES}) if (CORE_PLUGINS_AS_SHARED_LIBRARIES) install(TARGETS ${particles} dynlist DESTINATION lib/f00bar) endif () + +set(enabled_particles "dynlist;${particles}" PARENT_SCOPE) diff --git a/plugin.c b/plugin.c index 5731f40..3227b06 100644 --- a/plugin.c +++ b/plugin.c @@ -112,8 +112,12 @@ init(void) REGISTER_CORE_MODULE(mpd, mpd); REGISTER_CORE_MODULE(network, network); REGISTER_CORE_MODULE(removables, removables); +#if defined(HAVE_PLUGIN_xkb) REGISTER_CORE_MODULE(xkb, xkb); +#endif +#if defined(HAVE_PLUGIN_xwindow) REGISTER_CORE_MODULE(xwindow, xwindow); +#endif REGISTER_CORE_PARTICLE(empty, empty); REGISTER_CORE_PARTICLE(list, list);