diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d2853a..4b9ed27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9) +cmake_minimum_required(VERSION 3.13) project(f00bar C) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -19,11 +19,6 @@ pkg_check_modules(FONTCONFIG REQUIRED fontconfig) # Core pkg_check_modules(CAIRO REQUIRED cairo cairo-xcb cairo-ft) # Core pkg_check_modules(YAML REQUIRED yaml-0.1) # Core (configuration) -pkg_check_modules(XCB_XKB REQUIRED xcb-xkb) # Module/xkb -pkg_check_modules(JSON REQUIRED json-c) # Module/i3 -pkg_check_modules(UDEV REQUIRED libudev) # Module/battery -pkg_check_modules(MPD REQUIRED libmpdclient) # Module/mpd -pkg_check_modules(ALSA REQUIRED alsa) # Module/alsa add_executable(f00bar bar.c bar.h @@ -35,6 +30,7 @@ add_executable(f00bar main.c module.c module.h particle.c particle.h + plugin.c plugin.h tag.c tag.h xcb.c xcb.h yml.c yml.h @@ -50,32 +46,19 @@ add_executable(f00bar particles/progress-bar.c particles/progress-bar.h particles/ramp.c particles/ramp.h particles/string.c particles/string.h - - modules/alsa.c modules/alsa.h - modules/backlight.c modules/backlight.h - modules/battery.c modules/battery.h - modules/clock.c modules/clock.h - modules/i3.c modules/i3.h - modules/label.c modules/label.h - modules/mpd.c modules/mpd.h - modules/network.c modules/network.h - modules/removables.c modules/removables.h - modules/xkb.c modules/xkb.h - modules/xwindow.c modules/xwindow.h ) +# TODO: directory global target_compile_definitions(f00bar PRIVATE _GNU_SOURCE) +# Make global symbols in f00bar visible to dlopen:ed plugins +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic") + target_compile_options(f00bar PRIVATE ${XCB_CFLAGS_OTHER} ${FONTCONFIG_CFLAGS_OTHER} ${CAIRO_CFLAGS_OTHER} ${YAML_CFLAGS_OTHER} - ${XCB_XKB_CFLAGS_OTHER} - ${JSON_CFLAGS_OTHER} - ${UDEV_CFLAGS_OTHER} - ${MPD_CFLAGS_OTHER} - ${ALSA_CFLAGS_OTHER} ) target_include_directories(f00bar PRIVATE @@ -83,22 +66,27 @@ target_include_directories(f00bar PRIVATE ${FONTCONFIG_INCLUDE_DIRS} ${CAIRO_INCLUDE_DIRS} ${YAML_INCLUDE_DIRS} - ${XCB_XKB_INCLUDE_DIRS} - ${JSON_INCLUDE_DIRS} - ${UDEV_INCLUDE_DIRS} - ${MPD_INCLUDE_DIRS} - ${ALSA_INCLUDE_DIRS} ) target_link_libraries(f00bar ${CMAKE_THREAD_LIBS_INIT} + ${CMAKE_DL_LIBS} + ${XCB_LIBRARIES} ${FONTCONFIG_LIBRARIES} ${CAIRO_LIBRARIES} ${YAML_LIBRARIES} - ${XCB_XKB_LIBRARIES} - ${JSON_LIBRARIES} - ${UDEV_LIBRARIES} - ${MPD_LIBRARIES} - ${ALSA_LIBRARIES} ) + +add_library(module-sdk INTERFACE) +target_compile_definitions(module-sdk INTERFACE _GNU_SOURCE) +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}) + +set_property(TARGET f00bar PROPERTY INSTALL_RPATH \$ORIGIN/../lib/f00bar) +set_property(TARGET f00bar PROPERTY BUILD_RPATH \$ORIGIN/modules) + +install(TARGETS f00bar DESTINATION bin) + +add_subdirectory(modules) diff --git a/config-verify.c b/config-verify.c index 4ac6c63..6597b39 100644 --- a/config-verify.c +++ b/config-verify.c @@ -1,4 +1,4 @@ -#include "config-verify.h" +#include "config.h" #include #include @@ -6,31 +6,11 @@ #define LOG_MODULE "config:verify" #define LOG_ENABLE_DBG 0 #include "log.h" +#include "plugin.h" #include "tllist.h" -typedef tll(const char *) keychain_t; - -struct attr_info { - const char *name; - bool required; - bool (*verify)(keychain_t *chain, const struct yml_node *node); -}; - -static keychain_t * -chain_push(keychain_t *chain, const char *key) -{ - tll_push_back(*chain, key); - return chain; -} - -static void -chain_pop(keychain_t *chain) -{ - tll_pop_back(*chain); -} - -static const char * -err_prefix(const keychain_t *chain, const struct yml_node *node) +const char * +conf_err_prefix(const keychain_t *chain, const struct yml_node *node) { static char msg[4096]; int idx = 0; @@ -46,36 +26,36 @@ err_prefix(const keychain_t *chain, const struct yml_node *node) return msg; } -static bool -verify_string(keychain_t *chain, const struct yml_node *node) +bool +conf_verify_string(keychain_t *chain, const struct yml_node *node) { const char *s = yml_value_as_string(node); if (s == NULL) { - LOG_ERR("%s: value must be a string", err_prefix(chain, node)); + LOG_ERR("%s: value must be a string", conf_err_prefix(chain, node)); return false; } return true; } -static bool -verify_int(keychain_t *chain, const struct yml_node *node) +bool +conf_verify_int(keychain_t *chain, const struct yml_node *node) { if (yml_value_is_int(node)) return true; LOG_ERR("%s: value is not an integer: '%s'", - err_prefix(chain, node), yml_value_as_string(node)); + conf_err_prefix(chain, node), yml_value_as_string(node)); return false; } -static bool -verify_enum(keychain_t *chain, const struct yml_node *node, - const char *values[], size_t count) +bool +conf_verify_enum(keychain_t *chain, const struct yml_node *node, + const char *values[], size_t count) { const char *s = yml_value_as_string(node); if (s == NULL) { - LOG_ERR("%s: value must be a string", err_prefix(chain, node)); + LOG_ERR("%s: value must be a string", conf_err_prefix(chain, node)); return false; } @@ -84,19 +64,19 @@ verify_enum(keychain_t *chain, const struct yml_node *node, return true; } - LOG_ERR("%s: value must be one of:", err_prefix(chain, node)); + LOG_ERR("%s: value must be one of:", conf_err_prefix(chain, node)); for (size_t i = 0; i < count; i++) LOG_ERR(" %s", values[i]); return false; } -static bool -verify_dict(keychain_t *chain, const struct yml_node *node, - const struct attr_info info[], size_t count) +bool +conf_verify_dict(keychain_t *chain, const struct yml_node *node, + const struct attr_info info[], size_t count) { if (!yml_is_dict(node)) { - LOG_ERR("%s: must be a dictionary", err_prefix(chain, node)); + LOG_ERR("%s: must be a dictionary", conf_err_prefix(chain, node)); return false; } @@ -109,7 +89,7 @@ verify_dict(keychain_t *chain, const struct yml_node *node, { const char *key = yml_value_as_string(it.key); if (key == NULL) { - LOG_ERR("%s: key must be a string", err_prefix(chain, it.key)); + LOG_ERR("%s: key must be a string", conf_err_prefix(chain, it.key)); return false; } @@ -123,7 +103,7 @@ verify_dict(keychain_t *chain, const struct yml_node *node, } if (attr == NULL) { - LOG_ERR("%s: invalid key: %s", err_prefix(chain, it.key), key); + LOG_ERR("%s: invalid key: %s", conf_err_prefix(chain, it.key), key); return false; } @@ -139,19 +119,19 @@ verify_dict(keychain_t *chain, const struct yml_node *node, if (!info[i].required || exists[i]) continue; - LOG_ERR("%s: missing required key: %s", err_prefix(chain, node), info[i].name); + LOG_ERR("%s: missing required key: %s", conf_err_prefix(chain, node), info[i].name); return false; } return true; } -static bool -verify_color(keychain_t *chain, const struct yml_node *node) +bool +conf_verify_color(keychain_t *chain, const struct yml_node *node) { const char *s = yml_value_as_string(node); if (s == NULL) { - LOG_ERR("%s: value must be a string", err_prefix(chain, node)); + LOG_ERR("%s: value must be a string", conf_err_prefix(chain, node)); return false; } @@ -160,7 +140,7 @@ verify_color(keychain_t *chain, const struct yml_node *node) if (strlen(s) != 8 || v != 4) { LOG_ERR("%s: value must be a color ('rrggbbaa', e.g ff00ffff)", - err_prefix(chain, node)); + conf_err_prefix(chain, node)); return false; } @@ -168,14 +148,14 @@ verify_color(keychain_t *chain, const struct yml_node *node) } -static bool -verify_font(keychain_t *chain, const struct yml_node *node) +bool +conf_verify_font(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { - {"family", true, &verify_string}, + {"family", true, &conf_verify_string}, }; - return verify_dict(chain, node, attrs, sizeof(attrs) / sizeof(attrs[0])); + return conf_verify_dict(chain, node, attrs, sizeof(attrs) / sizeof(attrs[0])); } static bool verify_decoration(keychain_t *chain, const struct yml_node *node); @@ -184,7 +164,7 @@ static bool verify_decoration_stack(keychain_t *chain, const struct yml_node *node) { if (!yml_is_list(node)) { - LOG_ERR("%s: must be a list of decorations", err_prefix(chain, node)); + LOG_ERR("%s: must be a list of decorations", conf_err_prefix(chain, node)); return false; } @@ -206,7 +186,7 @@ verify_decoration(keychain_t *chain, const struct yml_node *node) if (yml_dict_length(node) != 1) { LOG_ERR("%s: decoration must be a dictionary with a single key; " - "the name of the particle", err_prefix(chain, node)); + "the name of the particle", conf_err_prefix(chain, node)); return false; } @@ -216,7 +196,7 @@ verify_decoration(keychain_t *chain, const struct yml_node *node) const char *deco_name = yml_value_as_string(deco); if (deco_name == NULL) { - LOG_ERR("%s: decoration name must be a string", err_prefix(chain, deco)); + LOG_ERR("%s: decoration name must be a string", conf_err_prefix(chain, deco)); return false; } @@ -227,12 +207,12 @@ verify_decoration(keychain_t *chain, const struct yml_node *node) } static const struct attr_info background[] = { - {"color", true, &verify_color}, + {"color", true, &conf_verify_color}, }; static const struct attr_info underline[] = { - {"size", true, &verify_int}, - {"color", true, &verify_color}, + {"size", true, &conf_verify_int}, + {"color", true, &conf_verify_color}, }; static const struct { @@ -248,7 +228,7 @@ verify_decoration(keychain_t *chain, const struct yml_node *node) if (strcmp(decos[i].name, deco_name) != 0) continue; - if (!verify_dict(chain_push(chain, deco_name), + if (!conf_verify_dict(chain_push(chain, deco_name), values, decos[i].attrs, decos[i].count)) { return false; @@ -259,12 +239,10 @@ verify_decoration(keychain_t *chain, const struct yml_node *node) } LOG_ERR( - "%s: invalid decoration name: %s", err_prefix(chain, deco), deco_name); + "%s: invalid decoration name: %s", conf_err_prefix(chain, deco), deco_name); return false; } -static bool verify_particle(keychain_t *chain, const struct yml_node *node); - static bool verify_list_items(keychain_t *chain, const struct yml_node *node) { @@ -274,7 +252,7 @@ verify_list_items(keychain_t *chain, const struct yml_node *node) it.node != NULL; yml_list_next(&it)) { - if (!verify_particle(chain, it.node)) + if (!conf_verify_particle(chain, it.node)) return false; } @@ -287,7 +265,7 @@ verify_map_values(keychain_t *chain, const struct yml_node *node) if (!yml_is_dict(node)) { LOG_ERR( "%s: must be a dictionary of workspace-name: particle mappings", - err_prefix(chain, node)); + conf_err_prefix(chain, node)); return false; } @@ -297,12 +275,11 @@ verify_map_values(keychain_t *chain, const struct yml_node *node) { const char *key = yml_value_as_string(it.key); if (key == NULL) { - LOG_ERR("%s: key must be a string (a i3 workspace name)", - err_prefix(chain, it.key)); + LOG_ERR("%s: key must be a string", conf_err_prefix(chain, it.key)); return false; } - if (!verify_particle(chain_push(chain, key), it.value)) + if (!conf_verify_particle(chain_push(chain, key), it.value)) return false; chain_pop(chain); @@ -312,13 +289,13 @@ verify_map_values(keychain_t *chain, const struct yml_node *node) } static bool -verify_particle_dictionary(keychain_t *chain, const struct yml_node *node) +conf_verify_particle_dictionary(keychain_t *chain, const struct yml_node *node) { assert(yml_is_dict(node)); if (yml_dict_length(node) != 1) { LOG_ERR("%s: particle must be a dictionary with a single key; " - "the name of the particle", err_prefix(chain, node)); + "the name of the particle", conf_err_prefix(chain, node)); return false; } @@ -328,15 +305,15 @@ verify_particle_dictionary(keychain_t *chain, const struct yml_node *node) const char *particle_name = yml_value_as_string(particle); if (particle_name == NULL) { - LOG_ERR("%s: particle name must be a string", err_prefix(chain, particle)); + LOG_ERR("%s: particle name must be a string", conf_err_prefix(chain, particle)); return false; } #define COMMON_ATTRS \ - {"margin", false, &verify_int}, \ - {"left-margin", false, &verify_int}, \ - {"right-margin", false, &verify_int}, \ - {"on-click", false, &verify_string}, + {"margin", false, &conf_verify_int}, \ + {"left-margin", false, &conf_verify_int}, \ + {"right-margin", false, &conf_verify_int}, \ + {"on-click", false, &conf_verify_string}, static const struct attr_info empty[] = { COMMON_ATTRS @@ -344,42 +321,42 @@ verify_particle_dictionary(keychain_t *chain, const struct yml_node *node) static const struct attr_info list[] = { {"items", true, &verify_list_items}, - {"spacing", false, &verify_int}, - {"left-spacing", false, &verify_int}, - {"right-spacing", false, &verify_int}, + {"spacing", false, &conf_verify_int}, + {"left-spacing", false, &conf_verify_int}, + {"right-spacing", false, &conf_verify_int}, COMMON_ATTRS }; static const struct attr_info map[] = { - {"tag", true, &verify_string}, + {"tag", true, &conf_verify_string}, {"values", true, &verify_map_values}, - {"default", false, &verify_particle}, + {"default", false, &conf_verify_particle}, COMMON_ATTRS }; static const struct attr_info progress_bar[] = { - {"tag", true, &verify_string}, - {"length", true, &verify_int}, + {"tag", true, &conf_verify_string}, + {"length", true, &conf_verify_int}, /* TODO: make these optional? Default to empty */ - {"start", true, &verify_particle}, - {"end", true, &verify_particle}, - {"fill", true, &verify_particle}, - {"empty", true, &verify_particle}, - {"indicator", true, &verify_particle}, + {"start", true, &conf_verify_particle}, + {"end", true, &conf_verify_particle}, + {"fill", true, &conf_verify_particle}, + {"empty", true, &conf_verify_particle}, + {"indicator", true, &conf_verify_particle}, COMMON_ATTRS }; static const struct attr_info ramp[] = { - {"tag", true, &verify_string}, + {"tag", true, &conf_verify_string}, {"items", true, &verify_list_items}, COMMON_ATTRS }; static const struct attr_info string[] = { - {"text", true, &verify_string}, - {"max", false, &verify_int}, - {"font", false, &verify_font}, - {"foreground", false, &verify_color}, + {"text", true, &conf_verify_string}, + {"max", false, &conf_verify_int}, + {"font", false, &conf_verify_font}, + {"foreground", false, &conf_verify_color}, {"deco", false, &verify_decoration}, COMMON_ATTRS }; @@ -403,7 +380,7 @@ verify_particle_dictionary(keychain_t *chain, const struct yml_node *node) if (strcmp(particles[i].name, particle_name) != 0) continue; - if (!verify_dict(chain_push(chain, particle_name), values, + if (!conf_verify_dict(chain_push(chain, particle_name), values, particles[i].attrs, particles[i].count)) { return false; @@ -414,60 +391,31 @@ verify_particle_dictionary(keychain_t *chain, const struct yml_node *node) } LOG_ERR( - "%s: invalid particle name: %s", err_prefix(chain, particle), particle_name); + "%s: invalid particle name: %s", conf_err_prefix(chain, particle), particle_name); return false; } -static bool -verify_particle(keychain_t *chain, const struct yml_node *node) +bool +conf_verify_particle(keychain_t *chain, const struct yml_node *node) { if (yml_is_dict(node)) - return verify_particle_dictionary(chain, node); + return conf_verify_particle_dictionary(chain, node); else if (yml_is_list(node)) return verify_list_items(chain, node); else { LOG_ERR("%s: particle must be either a dictionary or a list", - err_prefix(chain, node)); + conf_err_prefix(chain, node)); return false; } } -static bool -verify_i3_content(keychain_t *chain, const struct yml_node *node) -{ - if (!yml_is_dict(node)) { - LOG_ERR( - "%s: must be a dictionary of workspace-name: particle mappings", - err_prefix(chain, node)); - return false; - } - - for (struct yml_dict_iter it = yml_dict_iter(node); - it.key != NULL; - yml_dict_next(&it)) - { - const char *key = yml_value_as_string(it.key); - if (key == NULL) { - LOG_ERR("%s: key must be a string (a i3 workspace name)", - err_prefix(chain, it.key)); - return false; - } - - if (!verify_particle(chain_push(chain, key), it.value)) - return false; - - chain_pop(chain); - } - - return true; -} static bool verify_module(keychain_t *chain, const struct yml_node *node) { if (!yml_is_dict(node) || yml_dict_length(node) != 1) { LOG_ERR("%s: module must be a dictionary with a single key; " - "the name of the module", err_prefix(chain, node)); + "the name of the module", conf_err_prefix(chain, node)); return false; } @@ -477,122 +425,30 @@ verify_module(keychain_t *chain, const struct yml_node *node) const char *mod_name = yml_value_as_string(module); if (mod_name == NULL) { - LOG_ERR("%s: module name must be a string", err_prefix(chain, module)); + LOG_ERR("%s: module name must be a string", conf_err_prefix(chain, module)); return false; } - static const struct attr_info alsa[] = { - {"card", true, &verify_string}, - {"mixer", true, &verify_string}, - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct attr_info backlight[] = { - {"name", true, &verify_string}, - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct attr_info battery[] = { - {"name", true, &verify_string}, - {"poll-interval", false, &verify_int}, - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct attr_info clock[] = { - {"date-format", false, &verify_string}, - {"time-format", false, &verify_string}, - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct attr_info label[] = { - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct attr_info mpd[] = { - {"host", true, &verify_string}, - {"port", false, &verify_int}, - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct attr_info i3[] = { - {"spacing", false, &verify_int}, - {"left-spacing", false, &verify_int}, - {"right-spacing", false, &verify_int}, - {"content", true, &verify_i3_content}, - {"anchors", false, NULL}, - }; - - static const struct attr_info network[] = { - {"name", true, &verify_string}, - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct attr_info removables[] = { - {"spacing", false, &verify_int}, - {"left-spacing", false, &verify_int}, - {"right-spacing", false, &verify_int}, - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct attr_info xkb[] = { - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct attr_info xwindow[] = { - {"content", true, &verify_particle}, - {"anchors", false, NULL}, - }; - - static const struct { - const char *name; - const struct attr_info *attrs; - size_t count; - } modules[] = { - {"alsa", alsa, sizeof(alsa) / sizeof(alsa[0])}, - {"backlight", backlight, sizeof(backlight) / sizeof(backlight[0])}, - {"battery", battery, sizeof(battery) / sizeof(battery[0])}, - {"clock", clock, sizeof(clock) / sizeof(clock[0])}, - {"i3", i3, sizeof(i3) / sizeof(i3[0])}, - {"label", label, sizeof(label) / sizeof(label[0])}, - {"mpd", mpd, sizeof(mpd) / sizeof(mpd[0])}, - {"network", network, sizeof(network) / sizeof(network[0])}, - {"removables", removables, sizeof(removables) / sizeof(removables[0])}, - {"xkb", xkb, sizeof(xkb) / sizeof(xkb[0])}, - {"xwindow", xwindow, sizeof(xwindow) / sizeof(xwindow[0])}, - }; - - for (size_t i = 0; i < sizeof(modules) / sizeof(modules[0]); i++) { - if (strcmp(modules[i].name, mod_name) != 0) - continue; - - if (!verify_dict(chain_push(chain, mod_name), values, - modules[i].attrs, modules[i].count)) - { - return false; - } - - chain_pop(chain); - return true; + const struct module_info *info = plugin_load_module(mod_name); + if (info == NULL) { + LOG_ERR( + "%s: invalid module name: %s", conf_err_prefix(chain, node), mod_name); + return false; } - LOG_ERR("%s: invalid module name: %s", err_prefix(chain, module), mod_name); - return false; + if (!conf_verify_dict(chain_push(chain, mod_name), values, + info->attrs, info->attr_count)) + return false; + + chain_pop(chain); + return true; } static bool verify_module_list(keychain_t *chain, const struct yml_node *node) { if (!yml_is_list(node)) { - LOG_ERR("%s: must be a list of modules", err_prefix(chain, node)); + LOG_ERR("%s: must be a list of modules", conf_err_prefix(chain, node)); return false; } @@ -611,21 +467,21 @@ static bool verify_bar_border(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { - {"width", true, &verify_int}, - {"color", true, &verify_color}, + {"width", true, &conf_verify_int}, + {"color", true, &conf_verify_color}, }; - return verify_dict(chain, node, attrs, sizeof(attrs) / sizeof(attrs[0])); + return conf_verify_dict(chain, node, attrs, sizeof(attrs) / sizeof(attrs[0])); } static bool verify_bar_location(keychain_t *chain, const struct yml_node *node) { - return verify_enum(chain, node, (const char *[]){"top", "bottom"}, 2); + return conf_verify_enum(chain, node, (const char *[]){"top", "bottom"}, 2); } bool -config_verify_bar(const struct yml_node *bar) +conf_verify_bar(const struct yml_node *bar) { if (!yml_is_dict(bar)) { LOG_ERR("bar is not a dictionary"); @@ -636,27 +492,27 @@ config_verify_bar(const struct yml_node *bar) chain_push(&chain, "bar"); static const struct attr_info attrs[] = { - {"height", true, &verify_int}, + {"height", true, &conf_verify_int}, {"location", true, &verify_bar_location}, - {"background", true, &verify_color}, + {"background", true, &conf_verify_color}, - {"spacing", false, &verify_int}, - {"left-spacing", false, &verify_int}, - {"right-spacing", false, &verify_int}, + {"spacing", false, &conf_verify_int}, + {"left-spacing", false, &conf_verify_int}, + {"right-spacing", false, &conf_verify_int}, - {"margin", false, &verify_int}, - {"left_margin", false, &verify_int}, - {"right_margin", false, &verify_int}, + {"margin", false, &conf_verify_int}, + {"left_margin", false, &conf_verify_int}, + {"right_margin", false, &conf_verify_int}, {"border", false, &verify_bar_border}, - {"font", false, &verify_font}, + {"font", false, &conf_verify_font}, {"left", false, &verify_module_list}, {"center", false, &verify_module_list}, {"right", false, &verify_module_list}, }; - bool ret = verify_dict(&chain, bar, attrs, sizeof(attrs) / sizeof(attrs[0])); + bool ret = conf_verify_dict(&chain, bar, attrs, sizeof(attrs) / sizeof(attrs[0])); tll_free(chain); return ret; } diff --git a/config-verify.h b/config-verify.h index 92b3941..a6f4660 100644 --- a/config-verify.h +++ b/config-verify.h @@ -1,5 +1,44 @@ #pragma once + #include + +#include "tllist.h" #include "yml.h" -bool config_verify_bar(const struct yml_node *bar); +typedef tll(const char *) keychain_t; + +struct attr_info { + const char *name; + bool required; + bool (*verify)(keychain_t *chain, const struct yml_node *node); +}; + +static inline keychain_t * +chain_push(keychain_t *chain, const char *key) +{ + tll_push_back(*chain, key); + return chain; +} + +static inline void +chain_pop(keychain_t *chain) +{ + tll_pop_back(*chain); +} + +const char *conf_err_prefix( + const keychain_t *chain, const struct yml_node *node); + + +bool conf_verify_string(keychain_t *chain, const struct yml_node *node); +bool conf_verify_int(keychain_t *chain, const struct yml_node *node); + +bool conf_verify_enum(keychain_t *chain, const struct yml_node *node, + const char *values[], size_t count); +bool conf_verify_dict(keychain_t *chain, const struct yml_node *node, + const struct attr_info info[], size_t count); + +bool conf_verify_color(keychain_t *chain, const struct yml_node *node); +bool conf_verify_font(keychain_t *chain, const struct yml_node *node); + +bool conf_verify_particle(keychain_t *chain, const struct yml_node *node); diff --git a/config.c b/config.c index fcedfef..e18660c 100644 --- a/config.c +++ b/config.c @@ -21,19 +21,8 @@ #include "particles/string.h" #include "module.h" -#include "modules/alsa.h" -#include "modules/backlight.h" -#include "modules/battery.h" -#include "modules/clock.h" -#include "modules/i3.h" -#include "modules/label.h" -#include "modules/mpd.h" -#include "modules/network.h" -#include "modules/removables.h" -#include "modules/xkb.h" -#include "modules/xwindow.h" - #include "config-verify.h" +#include "plugin.h" static uint8_t hex_nibble(char hex) @@ -179,9 +168,6 @@ particle_string_from_config(const struct yml_node *node, fg_color, left_margin, right_margin, on_click_template); } -static struct particle * particle_from_config( - const struct yml_node *node, const struct font *parent_font); - static struct particle * particle_list_from_config(const struct yml_node *node, const struct font *parent_font, @@ -191,8 +177,8 @@ particle_list_from_config(const struct yml_node *node, const struct yml_node *items = yml_get_value(node, "items"); const struct yml_node *spacing = yml_get_value(node, "spacing"); - const struct yml_node *_left_spacing = yml_get_value(node, "left_spacing"); - const struct yml_node *_right_spacing = yml_get_value(node, "right_spacing"); + const struct yml_node *_left_spacing = yml_get_value(node, "left-spacing"); + const struct yml_node *_right_spacing = yml_get_value(node, "right-spacing"); int left_spacing = spacing != NULL ? yml_value_as_int(spacing) : _left_spacing != NULL ? yml_value_as_int(_left_spacing) : 0; @@ -207,7 +193,7 @@ particle_list_from_config(const struct yml_node *node, it.node != NULL; yml_list_next(&it), idx++) { - parts[idx] = particle_from_config(it.node, parent_font); + parts[idx] = conf_to_particle(it.node, parent_font); } return particle_list_new( @@ -233,11 +219,11 @@ particle_map_from_config(const struct yml_node *node, yml_dict_next(&it), idx++) { particle_map[idx].tag_value = yml_value_as_string(it.key); - particle_map[idx].particle = particle_from_config(it.value, parent_font); + particle_map[idx].particle = conf_to_particle(it.value, parent_font); } struct particle *default_particle = def != NULL - ? particle_from_config(def, parent_font) + ? conf_to_particle(def, parent_font) : NULL; return particle_map_new( @@ -262,7 +248,7 @@ particle_ramp_from_config(const struct yml_node *node, it.node != NULL; yml_list_next(&it), idx++) { - parts[idx] = particle_from_config(it.node, parent_font); + parts[idx] = conf_to_particle(it.node, parent_font); } return particle_ramp_new( @@ -287,11 +273,11 @@ particle_progress_bar_from_config(const struct yml_node *node, return particle_progress_bar_new( yml_value_as_string(tag), yml_value_as_int(length), - particle_from_config(start, parent_font), - particle_from_config(end, parent_font), - particle_from_config(fill, parent_font), - particle_from_config(empty, parent_font), - particle_from_config(indicator, parent_font), + conf_to_particle(start, parent_font), + conf_to_particle(end, parent_font), + conf_to_particle(fill, parent_font), + conf_to_particle(empty, parent_font), + conf_to_particle(indicator, parent_font), left_margin, right_margin, on_click_template); } @@ -307,14 +293,14 @@ particle_simple_list_from_config(const struct yml_node *node, it.node != NULL; yml_list_next(&it), idx++) { - parts[idx] = particle_from_config(it.node, parent_font); + parts[idx] = conf_to_particle(it.node, parent_font); } return particle_list_new(parts, count, 0, 2, 0, 0, NULL); } -static struct particle * -particle_from_config(const struct yml_node *node, const struct font *parent_font) +struct particle * +conf_to_particle(const struct yml_node *node, const struct font *parent_font) { if (yml_is_list(node)) return particle_simple_list_from_config(node, parent_font); @@ -323,9 +309,9 @@ particle_from_config(const struct yml_node *node, const struct font *parent_font const char *type = yml_value_as_string(pair.key); const struct yml_node *margin = yml_get_value(pair.value, "margin"); - const struct yml_node *left_margin = yml_get_value(pair.value, "left_margin"); - const struct yml_node *right_margin = yml_get_value(pair.value, "right_margin"); - const struct yml_node *on_click = yml_get_value(pair.value, "on_click"); + const struct yml_node *left_margin = yml_get_value(pair.value, "left-margin"); + const struct yml_node *right_margin = yml_get_value(pair.value, "right-margin"); + const struct yml_node *on_click = yml_get_value(pair.value, "on-click"); int left = margin != NULL ? yml_value_as_int(margin) : left_margin != NULL ? yml_value_as_int(left_margin) : 0; @@ -365,154 +351,10 @@ particle_from_config(const struct yml_node *node, const struct font *parent_font return ret; } -static struct module * -module_label_from_config(const struct yml_node *node, const struct font *parent_font) -{ - const struct yml_node *c = yml_get_value(node, "content"); - return module_label(particle_from_config(c, parent_font)); -} - -static struct module * -module_clock_from_config(const struct yml_node *node, const struct font *parent_font) -{ - const struct yml_node *c = yml_get_value(node, "content"); - const struct yml_node *date_format = yml_get_value(node, "date-format"); - const struct yml_node *time_format = yml_get_value(node, "time-format"); - - return module_clock( - particle_from_config(c, parent_font), - date_format != NULL ? yml_value_as_string(date_format) : "%x", - time_format != NULL ? yml_value_as_string(time_format) : "%H:%M"); -} - -static struct module * -module_xwindow_from_config(const struct yml_node *node, const struct font *parent_font) -{ - const struct yml_node *c = yml_get_value(node, "content"); - return module_xwindow(particle_from_config(c, parent_font)); -} - -static struct module * -module_i3_from_config(const struct yml_node *node, const struct font *parent_font) -{ - const struct yml_node *c = yml_get_value(node, "content"); - const struct yml_node *spacing = yml_get_value(node, "spacing"); - const struct yml_node *left_spacing = yml_get_value(node, "left_spacing"); - const struct yml_node *right_spacing = yml_get_value(node, "right_spacing"); - - int left = spacing != NULL ? yml_value_as_int(spacing) : - left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; - int right = spacing != NULL ? yml_value_as_int(spacing) : - right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; - - struct i3_workspaces workspaces[yml_dict_length(c)]; - - size_t idx = 0; - for (struct yml_dict_iter it = yml_dict_iter(c); - it.key != NULL; - yml_dict_next(&it), idx++) - { - workspaces[idx].name = yml_value_as_string(it.key); - workspaces[idx].content = particle_from_config(it.value, parent_font); - } - - return module_i3(workspaces, yml_dict_length(c), left, right); -} - -static struct module * -module_battery_from_config(const struct yml_node *node, - const struct font *parent_font) -{ - const struct yml_node *c = yml_get_value(node, "content"); - const struct yml_node *name = yml_get_value(node, "name"); - const struct yml_node *poll_interval = yml_get_value(node, "poll_interval"); - - return module_battery( - yml_value_as_string(name), - particle_from_config(c, parent_font), - poll_interval != NULL ? yml_value_as_int(poll_interval) : 60); -} - -static struct module * -module_xkb_from_config(const struct yml_node *node, - const struct font *parent_font) -{ - const struct yml_node *c = yml_get_value(node, "content"); - return module_xkb(particle_from_config(c, parent_font)); -} - -static struct module * -module_backlight_from_config(const struct yml_node *node, - const struct font *parent_font) -{ - const struct yml_node *name = yml_get_value(node, "name"); - const struct yml_node *c = yml_get_value(node, "content"); - - return module_backlight( - yml_value_as_string(name), particle_from_config(c, parent_font)); -} - -static struct module * -module_mpd_from_config(const struct yml_node *node, - const struct font *parent_font) -{ - const struct yml_node *host = yml_get_value(node, "host"); - const struct yml_node *port = yml_get_value(node, "port"); - const struct yml_node *c = yml_get_value(node, "content"); - - return module_mpd( - yml_value_as_string(host), - port != NULL ? yml_value_as_int(port) : 0, - particle_from_config(c, parent_font)); -} - -static struct module * -module_network_from_config(const struct yml_node *node, - const struct font *parent_font) -{ - const struct yml_node *name = yml_get_value(node, "name"); - const struct yml_node *content = yml_get_value(node, "content"); - - return module_network( - yml_value_as_string(name), particle_from_config(content, parent_font)); -} - -static struct module * -module_removables_from_config(const struct yml_node *node, - const struct font *parent_font) -{ - const struct yml_node *content = yml_get_value(node, "content"); - const struct yml_node *spacing = yml_get_value(node, "spacing"); - const struct yml_node *left_spacing = yml_get_value(node, "left_spacing"); - const struct yml_node *right_spacing = yml_get_value(node, "right_spacing"); - - int left = spacing != NULL ? yml_value_as_int(spacing) : - left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; - int right = spacing != NULL ? yml_value_as_int(spacing) : - right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; - - return module_removables( - particle_from_config(content, parent_font), left, right); -} - -static struct module * -module_alsa_from_config(const struct yml_node *node, - const struct font *parent_font) -{ - const struct yml_node *card = yml_get_value(node, "card"); - const struct yml_node *mixer = yml_get_value(node, "mixer"); - const struct yml_node *content = yml_get_value(node, "content"); - - return module_alsa( - yml_value_as_string(card), - yml_value_as_string(mixer), - particle_from_config(content, parent_font)); -} - struct bar * conf_to_bar(const struct yml_node *bar) { - if (!config_verify_bar(bar)) + if (!conf_verify_bar(bar)) return NULL; struct bar_config conf = {0}; @@ -539,11 +381,11 @@ conf_to_bar(const struct yml_node *bar) if (spacing != NULL) conf.left_spacing = conf.right_spacing = yml_value_as_int(spacing); - const struct yml_node *left_spacing = yml_get_value(bar, "left_spacing"); + const struct yml_node *left_spacing = yml_get_value(bar, "left-spacing"); if (left_spacing != NULL) conf.left_spacing = yml_value_as_int(left_spacing); - const struct yml_node *right_spacing = yml_get_value(bar, "right_spacing"); + const struct yml_node *right_spacing = yml_get_value(bar, "right-spacing"); if (right_spacing != NULL) conf.right_spacing = yml_value_as_int(right_spacing); @@ -551,11 +393,11 @@ conf_to_bar(const struct yml_node *bar) if (margin != NULL) conf.left_margin = conf.right_margin = yml_value_as_int(margin); - const struct yml_node *left_margin = yml_get_value(bar, "left_margin"); + const struct yml_node *left_margin = yml_get_value(bar, "left-margin"); if (left_margin != NULL) conf.left_margin = yml_value_as_int(left_margin); - const struct yml_node *right_margin = yml_get_value(bar, "right_margin"); + const struct yml_node *right_margin = yml_get_value(bar, "right-margin"); if (right_margin != NULL) conf.right_margin = yml_value_as_int(right_margin); @@ -599,30 +441,8 @@ conf_to_bar(const struct yml_node *bar) struct yml_dict_iter m = yml_dict_iter(it.node); const char *mod_name = yml_value_as_string(m.key); - if (strcmp(mod_name, "label") == 0) - mods[idx] = module_label_from_config(m.value, font); - else if (strcmp(mod_name, "clock") == 0) - mods[idx] = module_clock_from_config(m.value, font); - else if (strcmp(mod_name, "xwindow") == 0) - mods[idx] = module_xwindow_from_config(m.value, font); - else if (strcmp(mod_name, "i3") == 0) - mods[idx] = module_i3_from_config(m.value, font); - else if (strcmp(mod_name, "battery") == 0) - mods[idx] = module_battery_from_config(m.value, font); - else if (strcmp(mod_name, "xkb") == 0) - mods[idx] = module_xkb_from_config(m.value, font); - else if (strcmp(mod_name, "backlight") == 0) - mods[idx] = module_backlight_from_config(m.value, font); - else if (strcmp(mod_name, "mpd") == 0) - mods[idx] = module_mpd_from_config(m.value, font); - else if (strcmp(mod_name, "network") == 0) - mods[idx] = module_network_from_config(m.value, font); - else if (strcmp(mod_name, "removables") == 0) - mods[idx] = module_removables_from_config(m.value, font); - else if (strcmp(mod_name, "alsa") == 0) - mods[idx] = module_alsa_from_config(m.value, font); - else - assert(false); + const struct module_info *info = plugin_load_module(mod_name); + mods[idx] = info->from_conf(m.value, font); } if (i == 0) { diff --git a/config.h b/config.h index 422f782..c659e0c 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,16 @@ #pragma once #include "bar.h" -#include "yml.h" #include "font.h" +#include "particle.h" +#include "yml.h" +bool conf_verify_bar(const struct yml_node *bar); struct bar *conf_to_bar(const struct yml_node *bar); + +/* + * Utility functions, for e.g. modules + */ + +struct particle * conf_to_particle( + const struct yml_node *node, const struct font *parent_font); diff --git a/module.h b/module.h index 012a2fd..450dcd6 100644 --- a/module.h +++ b/module.h @@ -3,12 +3,22 @@ #include #include +#include "config-verify.h" #include "particle.h" #include "tag.h" +#include "yml.h" struct bar; struct module; +struct module_info { + struct module *(*from_conf)(const struct yml_node *node, + const struct font *parent_font); + + size_t attr_count; /* TODO: remove, NULL-terminate attr list instead */ + const struct attr_info attrs[]; +}; + struct module_run_context { struct module *module; int ready_fd; diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt new file mode 100644 index 0000000..9ea7116 --- /dev/null +++ b/modules/CMakeLists.txt @@ -0,0 +1,69 @@ +cmake_minimum_required(VERSION 3.13) + +pkg_check_modules(ALSA REQUIRED alsa) +add_library(alsa MODULE 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}) + +pkg_check_modules(UDEV REQUIRED libudev) +add_library(backlight MODULE 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}) + +add_library(battery MODULE 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}) + +add_library(clock MODULE clock.c) +target_link_libraries(clock module-sdk) + +pkg_check_modules(JSON REQUIRED json-c) +add_library(i3 MODULE i3.c) +target_compile_options(i3 PRIVATE ${JSON_CFLAGS_OTHER}) +target_include_directories(i3 PRIVATE ${JSON_INCLUDE_DIRS}) +target_link_libraries(i3 module-sdk ${JSON_LIBRARIES}) + +add_library(label MODULE label.c) +target_link_libraries(label module-sdk) + +pkg_check_modules(MPD REQUIRED libmpdclient) +add_library(mpd MODULE 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}) + +add_library(network MODULE network.c) +target_link_libraries(network module-sdk) + +add_library(removables MODULE removables.c) +target_compile_options(removables PRIVATE ${UDEV_CFLAGS_OTHER}) +target_include_directories(removables PRIVATE ${UDEV_INCLUDE_DIRS}) +target_link_libraries(removables module-sdk ${UDEV_LIBRARIES}) + +pkg_check_modules(XCB_XKB REQUIRED xcb-xkb) +add_library(xkb MODULE 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}) + +add_library(xwindow MODULE xwindow.c) +target_link_libraries(xwindow module-sdk) + +install( + TARGETS + alsa + backlight + battery + clock + i3 + label + mpd + network + removables + xkb + xwindow + + DESTINATION lib/f00bar) diff --git a/modules/alsa.c b/modules/alsa.c index b67aabb..968f777 100644 --- a/modules/alsa.c +++ b/modules/alsa.c @@ -1,5 +1,3 @@ -#include "alsa.h" - #include #include @@ -9,6 +7,7 @@ #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../bar.h" +#include "../config.h" #include "../tllist.h" struct private { @@ -249,8 +248,8 @@ run(struct module_run_context *ctx) return 0; } -struct module * -module_alsa(const char *card, const char *mixer, struct particle *label) +static struct module * +alsa_new(const char *card, const char *mixer, struct particle *label) { struct private *priv = malloc(sizeof(*priv)); priv->label = label; @@ -267,3 +266,28 @@ module_alsa(const char *card, const char *mixer, struct particle *label) mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *card = yml_get_value(node, "card"); + const struct yml_node *mixer = yml_get_value(node, "mixer"); + const struct yml_node *content = yml_get_value(node, "content"); + + return alsa_new( + yml_value_as_string(card), + yml_value_as_string(mixer), + conf_to_particle(content, parent_font)); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 4, + .attrs = { + {"card", true, &conf_verify_string}, + {"mixer", true, &conf_verify_string}, + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL} + }, +}; diff --git a/modules/alsa.h b/modules/alsa.h deleted file mode 100644 index ec539e1..0000000 --- a/modules/alsa.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "../module.h" -#include "../particle.h" - -struct module *module_alsa( - const char *card, const char *mixer, struct particle *label); diff --git a/modules/backlight.c b/modules/backlight.c index 7a3e08d..8c06851 100644 --- a/modules/backlight.c +++ b/modules/backlight.c @@ -1,5 +1,3 @@ -#include "backlight.h" - #include #include #include @@ -15,6 +13,7 @@ #define LOG_MODULE "battery" #include "../log.h" #include "../bar.h" +#include "../config.h" struct private { struct particle *label; @@ -198,8 +197,8 @@ run(struct module_run_context *ctx) return 0; } -struct module * -module_backlight(const char *device, struct particle *label) +static struct module * +backlight_new(const char *device, struct particle *label) { struct private *m = malloc(sizeof(*m)); m->label = label; @@ -214,3 +213,24 @@ module_backlight(const char *device, struct particle *label) mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *name = yml_get_value(node, "name"); + const struct yml_node *c = yml_get_value(node, "content"); + + return backlight_new( + yml_value_as_string(name), conf_to_particle(c, parent_font)); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 3, + .attrs = { + {"name", true, &conf_verify_string}, + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/backlight.h b/modules/backlight.h deleted file mode 100644 index f1e3a90..0000000 --- a/modules/backlight.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "../module.h" -#include "../particle.h" - -struct module *module_backlight(const char *device, struct particle *label); diff --git a/modules/battery.c b/modules/battery.c index 43c2bee..3c42a16 100644 --- a/modules/battery.c +++ b/modules/battery.c @@ -1,5 +1,3 @@ -#include "battery.h" - #include #include #include @@ -16,6 +14,7 @@ #define LOG_MODULE "battery" #include "../log.h" #include "../bar.h" +#include "../config.h" enum state { STATE_FULL, STATE_CHARGING, STATE_DISCHARGING }; @@ -326,9 +325,8 @@ out: return ret; } -struct module * -module_battery(const char *battery, struct particle *label, - int poll_interval_secs) +static struct module * +battery_new(const char *battery, struct particle *label, int poll_interval_secs) { struct private *m = malloc(sizeof(*m)); m->label = label; @@ -344,3 +342,28 @@ module_battery(const char *battery, struct particle *label, mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *c = yml_get_value(node, "content"); + const struct yml_node *name = yml_get_value(node, "name"); + const struct yml_node *poll_interval = yml_get_value(node, "poll-interval"); + + return battery_new( + yml_value_as_string(name), + conf_to_particle(c, parent_font), + poll_interval != NULL ? yml_value_as_int(poll_interval) : 60); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 4, + .attrs = { + {"name", true, &conf_verify_string}, + {"poll-interval", false, &conf_verify_int}, + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/battery.h b/modules/battery.h deleted file mode 100644 index 1d268ab..0000000 --- a/modules/battery.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "../module.h" -#include "../particle.h" - -struct module *module_battery( - const char *battery, struct particle *label, int poll_interval_secs); diff --git a/modules/clock.c b/modules/clock.c index a9f57e6..6a9841e 100644 --- a/modules/clock.c +++ b/modules/clock.c @@ -1,4 +1,3 @@ -#include "clock.h" #include #include #include @@ -7,6 +6,7 @@ #include #include "../bar.h" +#include "../config.h" struct private { struct particle *label; @@ -78,9 +78,8 @@ run(struct module_run_context *ctx) return 0; } -struct module * -module_clock(struct particle *label, - const char *date_format, const char *time_format) +static struct module * +clock_new(struct particle *label, const char *date_format, const char *time_format) { struct private *m = malloc(sizeof(*m)); m->label = label; @@ -94,3 +93,28 @@ module_clock(struct particle *label, mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *c = yml_get_value(node, "content"); + const struct yml_node *date_format = yml_get_value(node, "date-format"); + const struct yml_node *time_format = yml_get_value(node, "time-format"); + + return clock_new( + conf_to_particle(c, parent_font), + date_format != NULL ? yml_value_as_string(date_format) : "%x", + time_format != NULL ? yml_value_as_string(time_format) : "%H:%M"); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 4, + .attrs = { + {"date-format", false, &conf_verify_string}, + {"time-format", false, &conf_verify_string}, + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/clock.h b/modules/clock.h deleted file mode 100644 index 2af48db..0000000 --- a/modules/clock.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "../module.h" -#include "../particle.h" - -struct module *module_clock( - struct particle *label, const char *date_format, const char *time_format); diff --git a/modules/i3.c b/modules/i3.c index b910355..9b6bb2d 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -1,5 +1,3 @@ -#include "i3.h" - #include #include #include @@ -21,6 +19,7 @@ #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../bar.h" +#include "../config.h" #include "../particles/dynlist.h" @@ -611,9 +610,15 @@ content(struct module *mod) particles, particle_count, m->left_spacing, m->right_spacing); } -struct module * -module_i3(struct i3_workspaces workspaces[], size_t workspace_count, - int left_spacing, int right_spacing) +/* Maps workspace name to a content particle. */ +struct i3_workspaces { + const char *name; + struct particle *content; +}; + +static struct module * +i3_new(struct i3_workspaces workspaces[], size_t workspace_count, + int left_spacing, int right_spacing) { struct private *m = malloc(sizeof(*m)); @@ -638,3 +643,73 @@ module_i3(struct i3_workspaces workspaces[], size_t workspace_count, mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *c = yml_get_value(node, "content"); + const struct yml_node *spacing = yml_get_value(node, "spacing"); + const struct yml_node *left_spacing = yml_get_value(node, "left-spacing"); + const struct yml_node *right_spacing = yml_get_value(node, "right-spacing"); + + int left = spacing != NULL ? yml_value_as_int(spacing) : + left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; + int right = spacing != NULL ? yml_value_as_int(spacing) : + right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; + + struct i3_workspaces workspaces[yml_dict_length(c)]; + + size_t idx = 0; + for (struct yml_dict_iter it = yml_dict_iter(c); + it.key != NULL; + yml_dict_next(&it), idx++) + { + workspaces[idx].name = yml_value_as_string(it.key); + workspaces[idx].content = conf_to_particle(it.value, parent_font); + } + + return i3_new(workspaces, yml_dict_length(c), left, right); +} + +static bool +verify_content(keychain_t *chain, const struct yml_node *node) +{ + if (!yml_is_dict(node)) { + LOG_ERR( + "%s: must be a dictionary of workspace-name: particle mappings", + conf_err_prefix(chain, node)); + return false; + } + + for (struct yml_dict_iter it = yml_dict_iter(node); + it.key != NULL; + yml_dict_next(&it)) + { + const char *key = yml_value_as_string(it.key); + if (key == NULL) { + LOG_ERR("%s: key must be a string (a i3 workspace name)", + conf_err_prefix(chain, it.key)); + return false; + } + + if (!conf_verify_particle(chain_push(chain, key), it.value)) + return false; + + chain_pop(chain); + } + + return true; +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 5, + .attrs = { + {"spacing", false, &conf_verify_int}, + {"left-spacing", false, &conf_verify_int}, + {"right-spacing", false, &conf_verify_int}, + {"content", true, &verify_content}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/i3.h b/modules/i3.h deleted file mode 100644 index d6be4e5..0000000 --- a/modules/i3.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "../module.h" -#include "../particle.h" - -/* Maps workspace name to a content particle. */ -struct i3_workspaces { - const char *name; - struct particle *content; -}; - -struct module *module_i3( - struct i3_workspaces workspaces[], size_t workspace_count, - int left_spacing, int right_spacing); diff --git a/modules/label.c b/modules/label.c index 750082f..ab7b2d6 100644 --- a/modules/label.c +++ b/modules/label.c @@ -1,10 +1,10 @@ -#include "label.h" - #include #include #include +#include "../config.h" + struct private { struct particle *label; }; @@ -32,8 +32,8 @@ run(struct module_run_context *ctx) return 0; } -struct module * -module_label(struct particle *label) +static struct module * +label_new(struct particle *label) { struct private *m = malloc(sizeof(*m)); m->label = label; @@ -45,3 +45,20 @@ module_label(struct particle *label) mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *c = yml_get_value(node, "content"); + return label_new(conf_to_particle(c, parent_font)); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 2, + .attrs = { + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/label.h b/modules/label.h deleted file mode 100644 index 7f04a10..0000000 --- a/modules/label.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "../module.h" -#include "../particle.h" - -struct module *module_label(struct particle *label); diff --git a/modules/mpd.c b/modules/mpd.c index 58583f6..c833e5d 100644 --- a/modules/mpd.c +++ b/modules/mpd.c @@ -1,5 +1,3 @@ -#include "mpd.h" - #include #include #include @@ -18,6 +16,7 @@ #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../bar.h" +#include "../config.h" enum state { STATE_OFFLINE = 1000, @@ -451,8 +450,8 @@ refresh_in(struct module *mod, long milli_seconds) return r == 0; } -struct module * -module_mpd(const char *host, uint16_t port, struct particle *label) +static struct module * +mpd_new(const char *host, uint16_t port, struct particle *label) { struct private *priv = malloc(sizeof(*priv)); priv->host = strdup(host); @@ -478,3 +477,28 @@ module_mpd(const char *host, uint16_t port, struct particle *label) mod->refresh_in = &refresh_in; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *host = yml_get_value(node, "host"); + const struct yml_node *port = yml_get_value(node, "port"); + const struct yml_node *c = yml_get_value(node, "content"); + + return mpd_new( + yml_value_as_string(host), + port != NULL ? yml_value_as_int(port) : 0, + conf_to_particle(c, parent_font)); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 4, + .attrs = { + {"host", true, &conf_verify_string}, + {"port", false, &conf_verify_int}, + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/mpd.h b/modules/mpd.h deleted file mode 100644 index 3a69b7c..0000000 --- a/modules/mpd.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include - -#include "../module.h" -#include "../particle.h" - -struct module *module_mpd( - const char *host, uint16_t port, struct particle *label); diff --git a/modules/network.c b/modules/network.c index 9e747c1..598a3eb 100644 --- a/modules/network.c +++ b/modules/network.c @@ -1,5 +1,3 @@ -#include "network.h" - #include #include #include @@ -17,8 +15,9 @@ #define LOG_MODULE "network" #define LOG_ENABLE_DBG 0 #include "../log.h" -#include "../module.h" #include "../bar.h" +#include "../config.h" +#include "../module.h" #include "../tllist.h" struct af_addr { @@ -141,6 +140,8 @@ netlink_connect(void) .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR, }; + LOG_WARN("nl_pid_value = 0x%08x", addr.nl_pid); + if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) == -1) { LOG_ERRNO("failed to bind netlink socket"); close(sock); @@ -509,8 +510,8 @@ run(struct module_run_context *ctx) return 0; } -struct module * -module_network(const char *iface, struct particle *label) +static struct module * +network_new(const char *iface, struct particle *label) { struct private *priv = malloc(sizeof(*priv)); priv->iface = strdup(iface); @@ -531,3 +532,24 @@ module_network(const char *iface, struct particle *label) mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *name = yml_get_value(node, "name"); + const struct yml_node *content = yml_get_value(node, "content"); + + return network_new( + yml_value_as_string(name), conf_to_particle(content, parent_font)); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 3, + .attrs = { + {"name", true, &conf_verify_string}, + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/network.h b/modules/network.h deleted file mode 100644 index 6ec57bd..0000000 --- a/modules/network.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "../module.h" -#include "../particle.h" - -struct module *module_network(const char *iface, struct particle *label); diff --git a/modules/removables.c b/modules/removables.c index 263206f..caa6100 100644 --- a/modules/removables.c +++ b/modules/removables.c @@ -1,5 +1,3 @@ -#include "removables.h" - #include #include #include @@ -18,8 +16,9 @@ #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../bar.h" -#include "../tllist.h" +#include "../config.h" #include "../particles/dynlist.h" +#include "../tllist.h" typedef tll(char *) mount_point_list_t; @@ -544,8 +543,8 @@ run(struct module_run_context *ctx) return 0; } -struct module * -module_removables(struct particle *label, int left_spacing, int right_spacing) +static struct module * +removables_new(struct particle *label, int left_spacing, int right_spacing) { struct private *priv = malloc(sizeof(*priv)); priv->label = label; @@ -560,3 +559,33 @@ module_removables(struct particle *label, int left_spacing, int right_spacing) mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *content = yml_get_value(node, "content"); + const struct yml_node *spacing = yml_get_value(node, "spacing"); + const struct yml_node *left_spacing = yml_get_value(node, "left-spacing"); + const struct yml_node *right_spacing = yml_get_value(node, "right-spacing"); + + int left = spacing != NULL ? yml_value_as_int(spacing) : + left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; + int right = spacing != NULL ? yml_value_as_int(spacing) : + right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; + + return removables_new( + conf_to_particle(content, parent_font), left, right); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 5, + .attrs = { + {"spacing", false, &conf_verify_int}, + {"left-spacing", false, &conf_verify_int}, + {"right-spacing", false, &conf_verify_int}, + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/removables.h b/modules/removables.h deleted file mode 100644 index ff94a18..0000000 --- a/modules/removables.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "../module.h" -#include "../particle.h" - -struct module *module_removables( - struct particle *label, int left_spacing, int right_spacing); diff --git a/modules/xkb.c b/modules/xkb.c index ca99058..3b1d2d7 100644 --- a/modules/xkb.c +++ b/modules/xkb.c @@ -1,5 +1,3 @@ -#include "xkb.h" - #include #include #include @@ -13,6 +11,7 @@ #define LOG_MODULE "xkb" #include "../log.h" #include "../bar.h" +#include "../config.h" #include "../xcb.h" struct layout { @@ -436,8 +435,8 @@ run(struct module_run_context *ctx) return ret; } -struct module * -module_xkb(struct particle *label) +static struct module * +xkb_new(struct particle *label) { struct private *m = malloc(sizeof(*m)); m->label = label; @@ -452,3 +451,20 @@ module_xkb(struct particle *label) mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *c = yml_get_value(node, "content"); + return xkb_new(conf_to_particle(c, parent_font)); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 2, + .attrs = { + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/xkb.h b/modules/xkb.h deleted file mode 100644 index d8ec03d..0000000 --- a/modules/xkb.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include "../module.h" -#include "../particle.h" - -struct module *module_xkb(struct particle *label); diff --git a/modules/xwindow.c b/modules/xwindow.c index f699192..167d1fa 100644 --- a/modules/xwindow.c +++ b/modules/xwindow.c @@ -1,5 +1,3 @@ -#include "xwindow.h" - #include #include #include @@ -17,6 +15,7 @@ #define LOG_MODULE "xkb" #include "../log.h" #include "../bar.h" +#include "../config.h" #include "../xcb.h" struct private { @@ -301,8 +300,8 @@ destroy(struct module *mod) module_default_destroy(mod); } -struct module * -module_xwindow(struct particle *label) +static struct module * +xwindow_new(struct particle *label) { struct private *m = calloc(1, sizeof(*m)); m->label = label; @@ -314,3 +313,20 @@ module_xwindow(struct particle *label) mod->content = &content; return mod; } + +static struct module * +from_conf(const struct yml_node *node, const struct font *parent_font) +{ + const struct yml_node *c = yml_get_value(node, "content"); + return xwindow_new(conf_to_particle(c, parent_font)); +} + +const struct module_info module_info = { + .from_conf = &from_conf, + .attr_count = 2, + .attrs = { + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + {NULL, false, NULL}, + }, +}; diff --git a/modules/xwindow.h b/modules/xwindow.h deleted file mode 100644 index e178ff8..0000000 --- a/modules/xwindow.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "../module.h" -#include "../particle.h" - -struct module *module_xwindow(struct particle *label); diff --git a/plugin.c b/plugin.c new file mode 100644 index 0000000..7f59e55 --- /dev/null +++ b/plugin.c @@ -0,0 +1,77 @@ +#include "plugin.h" + +#include +#include + +#define LOG_MODULE "plugin" +#define LOG_ENABLE_DBG 0 +#include "log.h" +#include "config.h" +#include "tllist.h" + +struct plugin { + char *name; + void *lib; + const void *sym; +}; + +static tll(struct plugin) plugins = tll_init(); + +static void +free_plugin(struct plugin plug) +{ + dlerror(); + dlclose(plug.lib); + + const char *dl_error = dlerror(); + if (dl_error != NULL) + LOG_ERR("%s: dlclose(): %s", plug.name, dl_error); + + free(plug.name); +} + +static void __attribute__((destructor)) +fini(void) +{ + tll_free_and_free(plugins, free_plugin); +} + +const struct module_info * +plugin_load_module(const char *name) +{ + char path[128]; + snprintf(path, sizeof(path), "lib%s.so", name); + + /* Have we already loaded it? */ + tll_foreach(plugins, plug) { + if (strcmp(plug->item.name, name) == 0) { + LOG_DBG("%s already loaded: %p", name, plug->item.lib); + assert(plug->item.sym != NULL); + return plug->item.sym; + } + } + + /* Not loaded - do it now */ + void *lib = dlopen(path, RTLD_LOCAL | RTLD_NOW); + LOG_DBG("%s: dlopened to %p", name, lib); + + if (lib == NULL) { + LOG_ERR("%s: dlopen: %s", name, dlerror()); + return NULL; + } + + tll_push_back(plugins, ((struct plugin){strdup(name), lib})); + struct plugin *plug = &tll_back(plugins); + + dlerror(); /* Clear previous error */ + plug->sym = dlsym(lib, "module_info"); + + const char *dlsym_error = dlerror(); + if (dlsym_error != NULL) { + LOG_ERR("%s: dlsym: %s", name, dlsym_error); + return NULL; + } + + assert(plug->sym != NULL); + return plug->sym; +} diff --git a/plugin.h b/plugin.h new file mode 100644 index 0000000..6d829e2 --- /dev/null +++ b/plugin.h @@ -0,0 +1,5 @@ +#pragma once + +#include "module.h" + +const struct module_info *plugin_load_module(const char *name);