diff --git a/include/AModule.hpp b/include/AModule.hpp index 479755b7..0960b116 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -17,6 +17,7 @@ class AModule : public IModule { operator Gtk::Widget &() override; auto doAction(const std::string &name) -> void override; + /// Emitting on this dispatcher triggers a update() call Glib::Dispatcher dp; protected: diff --git a/include/modules/cffi.hpp b/include/modules/cffi.hpp index 43545a4f..ac3b3de7 100644 --- a/include/modules/cffi.hpp +++ b/include/modules/cffi.hpp @@ -22,21 +22,21 @@ class CFFI : public AModule { CFFI(const std::string&, const std::string&, const Json::Value&); virtual ~CFFI(); - // virtual auto update() -> void override; - // virtual operator Gtk::Widget&() override; - virtual auto refresh(int signal) -> void override; virtual auto doAction(const std::string& name) -> void override; + virtual auto update() -> void override; private: /// void* cffi_instance_ = nullptr; - typedef void*(InitFn)(GtkContainer*, const struct wbcffi_config_entry* config_entries, + typedef void*(InitFn)(GtkContainer* root_widget, const void (*trigger_update)(void*), + void* trigger_update_arg, const struct wbcffi_config_entry* config_entries, size_t config_entries_len); typedef void(DenitFn)(void* instance); typedef void(RefreshFn)(void* instance, int signal); typedef void(DoActionFn)(void* instance, const char* name); + typedef void(UpdateFn)(void* instance); // FFI hooks struct { @@ -44,6 +44,7 @@ class CFFI : public AModule { std::function deinit = nullptr; std::function refresh = [](void*, int) {}; std::function doAction = [](void*, const char*) {}; + std::function update = [](void*) {}; } hooks_; }; diff --git a/resources/custom_modules/cffi_example/README.md b/resources/custom_modules/cffi_example/README.md index 71e6ce83..88396c19 100644 --- a/resources/custom_modules/cffi_example/README.md +++ b/resources/custom_modules/cffi_example/README.md @@ -7,6 +7,9 @@ Most language can implement the required functions and constants (C, C++, Rust, Go, Python, ...), meaning you can develop custom modules using your language of choice, as long as there's GTK bindings. +Symbols to implement are documented in the +[waybar_cffi_module.h](waybar_cffi_module.h) file. + # Usage ## Building this module diff --git a/resources/custom_modules/cffi_example/main.c b/resources/custom_modules/cffi_example/main.c index 24644afe..c959c9a8 100644 --- a/resources/custom_modules/cffi_example/main.c +++ b/resources/custom_modules/cffi_example/main.c @@ -19,7 +19,8 @@ void onclicked(GtkButton* button) { // You must const size_t wbcffi_version = 1; -void* wbcffi_init(GtkContainer* root, const struct wbcffi_config_entry* config_entries, +void* wbcffi_init(GtkContainer* root_widget, void (*trigger_update)(void*), + void* trigger_update_arg, const struct wbcffi_config_entry* config_entries, size_t config_entries_len) { printf("cffi_example: init config:\n"); for (size_t i = 0; i < config_entries_len; i++) { @@ -28,7 +29,7 @@ void* wbcffi_init(GtkContainer* root, const struct wbcffi_config_entry* config_e // Allocate the instance object Instance* inst = malloc(sizeof(Instance)); - inst->root = root; + inst->root = root_widget; // Add a container for displaying the next widgets inst->container = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5)); @@ -51,13 +52,18 @@ void* wbcffi_init(GtkContainer* root, const struct wbcffi_config_entry* config_e printf("cffi_example inst=%p: init success ! (%d total instances)\n", inst, ++instance_count); return inst; } + void wbcffi_deinit(void* instance) { printf("cffi_example inst=%p: free memory\n", instance); free(instance); } + +void wbcffi_update(void* instance) { printf("cffi_example inst=%p: Update request\n", instance); } + void wbcffi_refresh(void* instance, int signal) { printf("cffi_example inst=%p: Received refresh signal %d\n", instance, signal); } + void wbcffi_doaction(void* instance, const char* name) { printf("cffi_example inst=%p: doAction(%s)\n", instance, name); } \ No newline at end of file diff --git a/resources/custom_modules/cffi_example/waybar_cffi_module.h b/resources/custom_modules/cffi_example/waybar_cffi_module.h index af2fe581..2696b670 100644 --- a/resources/custom_modules/cffi_example/waybar_cffi_module.h +++ b/resources/custom_modules/cffi_example/waybar_cffi_module.h @@ -23,12 +23,16 @@ struct wbcffi_config_entry { /// MANDATORY CFFI function /// /// @param root_widget Root GTK widget instantiated by Waybar +/// @param trigger_update Call this function with trigger_update_arg as argument to trigger +/// wbcffi_update() on the next GTK main event loop iteration +/// @param trigger_update_arg Argument for trigger_update call /// @param config_entries Flat representation of the module JSON config. The data only available /// during wbcffi_init call. /// @param config_entries_len Number of entries in `config_entries` /// /// @return A untyped pointer to module data, NULL if the module failed to load. -void* wbcffi_init(GtkContainer* root_widget, const struct wbcffi_config_entry* config_entries, +void* wbcffi_init(GtkContainer* root_widget, const void (*trigger_update)(void*), + void* trigger_update_arg, const struct wbcffi_config_entry* config_entries, size_t config_entries_len); /// Module deinit/delete function, called when Waybar is closed or when the module is removed @@ -38,8 +42,15 @@ void* wbcffi_init(GtkContainer* root_widget, const struct wbcffi_config_entry* c /// @param instance Module instance data (as returned by `wbcffi_init`) void wbcffi_deinit(void* instance); -/// When Waybar receives a POSIX signal, it forwards the signal to each module, calling this -/// function +/// Called from the GTK main event loop, to update the UI +/// +/// Optional CFFI function +/// +/// @param instance Module instance data (as returned by `wbcffi_init`) +/// @param action_name Action name +void wbcffi_update(void* instance); + +/// Called when Waybar receives a POSIX signal and forwards it to each module /// /// Optional CFFI function /// @@ -47,12 +58,14 @@ void wbcffi_deinit(void* instance); /// @param signal Signal ID void wbcffi_refresh(void* instance, int signal); +/// Called on module action (see +/// https://github.com/Alexays/Waybar/wiki/Configuration#module-actions-config) /// /// Optional CFFI function /// /// @param instance Module instance data (as returned by `wbcffi_init`) -/// @param name Action name -void wbcffi_doaction(void* instance, const char* name); +/// @param action_name Action name +void wbcffi_doaction(void* instance, const char* action_name); #ifdef __cplusplus } diff --git a/src/modules/cffi.cpp b/src/modules/cffi.cpp index d0cd765f..e7a28edd 100644 --- a/src/modules/cffi.cpp +++ b/src/modules/cffi.cpp @@ -39,6 +39,9 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()}; } // Optional functions + if (auto fn = reinterpret_cast(dlsym(handle, "wbcffi_update"))) { + hooks_.update = fn; + } if (auto fn = reinterpret_cast(dlsym(handle, "wbcffi_refresh"))) { hooks_.refresh = fn; } @@ -70,8 +73,10 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co } // Call init - cffi_instance_ = hooks_.init(dynamic_cast(&event_box_)->gobj(), - config_entries.data(), config_entries.size()); + cffi_instance_ = hooks_.init( + dynamic_cast(&event_box_)->gobj(), + [](void* dp) { ((Glib::Dispatcher*)dp)->emit(); }, &dp, config_entries.data(), + config_entries.size()); // Handle init failures if (cffi_instance_ == nullptr) { @@ -85,6 +90,14 @@ CFFI::~CFFI() { } } +auto CFFI::update() -> void { + assert(cffi_instance_ != nullptr); + hooks_.update(cffi_instance_); + + // Execute the on-update command set in config + AModule::update(); +} + auto CFFI::refresh(int signal) -> void { assert(cffi_instance_ != nullptr); hooks_.refresh(cffi_instance_, signal); @@ -93,15 +106,7 @@ auto CFFI::refresh(int signal) -> void { auto CFFI::doAction(const std::string& name) -> void { assert(cffi_instance_ != nullptr); if (!name.empty()) { - // TODO: Make a decision - // Calling AModule::doAction and hooks_.doAction will execute the action twice if it is - // configured in AModule::eventActionMap_ and implemented in the CFFI module. - // - // Should we block AModule::doAction() if the action is implemented in hooks_.doAction() ? - // (doAction could return true if the action has been processed) - // hooks_.doAction(cffi_instance_, name.c_str()); - AModule::doAction(name); } }