Skip to content

Commit

Permalink
Send update dispatcher though FFI
Browse files Browse the repository at this point in the history
  • Loading branch information
CromFr committed Oct 17, 2023
1 parent 732aed2 commit a119cee
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 18 deletions.
1 change: 1 addition & 0 deletions include/AModule.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 5 additions & 1 deletion include/modules/cffi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,27 @@ class CFFI : public AModule {

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 {
std::function<InitFn> init = nullptr;
std::function<DenitFn> deinit = nullptr;
std::function<RefreshFn> refresh = [](void*, int) {};
std::function<DoActionFn> doAction = [](void*, const char*) {};
std::function<UpdateFn> update = [](void*) {};
} hooks_;
};

Expand Down
3 changes: 3 additions & 0 deletions resources/custom_modules/cffi_example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions resources/custom_modules/cffi_example/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand All @@ -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));
Expand All @@ -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);
}
23 changes: 18 additions & 5 deletions resources/custom_modules/cffi_example/waybar_cffi_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,21 +42,30 @@ 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
///
/// @param instance Module instance data (as returned by `wbcffi_init`)
/// @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
}
Expand Down
25 changes: 15 additions & 10 deletions src/modules/cffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<UpdateFn*>(dlsym(handle, "wbcffi_update"))) {
hooks_.update = fn;
}
if (auto fn = reinterpret_cast<RefreshFn*>(dlsym(handle, "wbcffi_refresh"))) {
hooks_.refresh = fn;
}
Expand Down Expand Up @@ -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<Gtk::Container*>(&event_box_)->gobj(),
config_entries.data(), config_entries.size());
cffi_instance_ = hooks_.init(
dynamic_cast<Gtk::Container*>(&event_box_)->gobj(),
[](void* dp) { ((Glib::Dispatcher*)dp)->emit(); }, &dp, config_entries.data(),
config_entries.size());

// Handle init failures
if (cffi_instance_ == nullptr) {
Expand All @@ -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);
Expand All @@ -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);
}
}

Expand Down

0 comments on commit a119cee

Please sign in to comment.