Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C ABI plugin system #2573

Merged
merged 5 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
1 change: 1 addition & 0 deletions include/factory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
#include "modules/cava.hpp"
#endif
#include "bar.hpp"
#include "modules/cffi.hpp"
#include "modules/custom.hpp"
#include "modules/image.hpp"
#include "modules/temperature.hpp"
Expand Down
60 changes: 60 additions & 0 deletions include/modules/cffi.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#pragma once

#include <string>

#include "AModule.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"

namespace waybar::modules {

namespace ffi {
extern "C" {
typedef struct wbcffi_module wbcffi_module;

typedef struct {
wbcffi_module* obj;
const char* waybar_version;
GtkContainer* (*get_root_widget)(wbcffi_module*);
void (*queue_update)(wbcffi_module*);
} wbcffi_init_info;

struct wbcffi_config_entry {
const char* key;
const char* value;
};
}
} // namespace ffi

class CFFI : public AModule {
public:
CFFI(const std::string&, const std::string&, const Json::Value&);
virtual ~CFFI();

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)(const ffi::wbcffi_init_info* init_info,
const ffi::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_;
};

} // namespace waybar::modules
37 changes: 37 additions & 0 deletions man/waybar-cffi.5.scd
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
waybar-cffi(5)
# NAME

waybar - cffi module

# DESCRIPTION

The *cffi* module gives full control of a GTK widget to a third-party dynamic library, to create more complex modules using different programming languages.

# CONFIGURATION

Addressed by *cffi/<name>*

*module_path*: ++
typeof: string ++
The path to the dynamic library to load to control the widget.

Some additional configuration may be required depending on the cffi dynamic library being used.


# EXAMPLES

## C example:

An example module written in C can be found at https://github.com/Alexays/Waybar/resources/custom_modules/cffi_example/

Waybar config to enable the module:
```
"cffi/c_example": {
"module_path": ".config/waybar/cffi/wb_cffi_example.so"
}
```


# STYLE

The classes and IDs are managed by the cffi dynamic library.
3 changes: 3 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ if is_linux
add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp')
src_files += files(
'src/modules/battery.cpp',
'src/modules/cffi.cpp',
'src/modules/cpu.cpp',
'src/modules/cpu_frequency/common.cpp',
'src/modules/cpu_frequency/linux.cpp',
Expand All @@ -202,6 +203,7 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd
add_project_arguments('-DHAVE_CPU_BSD', language: 'cpp')
add_project_arguments('-DHAVE_MEMORY_BSD', language: 'cpp')
src_files += files(
'src/modules/cffi.cpp',
'src/modules/cpu.cpp',
'src/modules/cpu_frequency/bsd.cpp',
'src/modules/cpu_frequency/common.cpp',
Expand Down Expand Up @@ -440,6 +442,7 @@ if scdoc.found()
'waybar-backlight-slider.5.scd',
'waybar-battery.5.scd',
'waybar-cava.5.scd',
'waybar-cffi.5.scd',
'waybar-clock.5.scd',
'waybar-cpu.5.scd',
'waybar-custom.5.scd',
Expand Down
1 change: 1 addition & 0 deletions resources/custom_modules/cffi_example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.cache/
38 changes: 38 additions & 0 deletions resources/custom_modules/cffi_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# C FFI module

A C FFI module is a dynamic library that exposes standard C functions and
constants, that Waybar can load and execute to create custom advanced widgets.

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

```bash
meson setup build
meson compile -C build
```

## Load the module

Edit your waybar config:
```json
{
// ...
"modules-center": [
// ...
"cffi/c_example"
],
// ...
"cffi/c_example": {
// Path to the compiled dynamic library file
"module_path": "resources/custom_modules/cffi_example/build/wb_cffi_example.so"
}
}
```
70 changes: 70 additions & 0 deletions resources/custom_modules/cffi_example/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

#include "waybar_cffi_module.h"

typedef struct {
wbcffi_module* waybar_module;
GtkBox* container;
GtkButton* button;
} ExampleMod;

// This static variable is shared between all instances of this module
static int instance_count = 0;

void onclicked(GtkButton* button) {
char text[256];
snprintf(text, 256, "Dice throw result: %d", rand() % 6 + 1);
gtk_button_set_label(button, text);
}

// You must
const size_t wbcffi_version = 1;

void* wbcffi_init(const wbcffi_init_info* init_info, const 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++) {
printf(" %s = %s\n", config_entries[i].key, config_entries[i].value);
}

// Allocate the instance object
ExampleMod* inst = malloc(sizeof(ExampleMod));
inst->waybar_module = init_info->obj;

GtkContainer* root = init_info->get_root_widget(init_info->obj);

// Add a container for displaying the next widgets
inst->container = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5));
gtk_container_add(GTK_CONTAINER(root), GTK_WIDGET(inst->container));

// Add a label
GtkLabel* label = GTK_LABEL(gtk_label_new("[Example C FFI Module:"));
gtk_container_add(GTK_CONTAINER(inst->container), GTK_WIDGET(label));

// Add a button
inst->button = GTK_BUTTON(gtk_button_new_with_label("click me !"));
g_signal_connect(inst->button, "clicked", G_CALLBACK(onclicked), NULL);
gtk_container_add(GTK_CONTAINER(inst->container), GTK_WIDGET(inst->button));

// Add a label
label = GTK_LABEL(gtk_label_new("]"));
gtk_container_add(GTK_CONTAINER(inst->container), GTK_WIDGET(label));

// Return instance object
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);
}
13 changes: 13 additions & 0 deletions resources/custom_modules/cffi_example/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
project(
'waybar_cffi_example', 'c',
version: '0.1.0',
license: 'MIT',
)

shared_library('wb_cffi_example',
['main.c'],
dependencies: [
dependency('gtk+-3.0', version : ['>=3.22.0'])
],
name_prefix: ''
)
89 changes: 89 additions & 0 deletions resources/custom_modules/cffi_example/waybar_cffi_module.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#pragma once

#include <gtk/gtk.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/// Waybar ABI version. 1 is the latest version
extern const size_t wbcffi_version;

/// Private Waybar CFFI module
typedef struct wbcffi_module wbcffi_module;

/// Waybar module information
typedef struct {
/// Waybar CFFI object pointer
wbcffi_module* obj;

/// Waybar version string
const char* waybar_version;

/// Returns the waybar widget allocated for this module
/// @param obj Waybar CFFI object pointer
GtkContainer* (*get_root_widget)(wbcffi_module* obj);

/// Queues a request for calling wbcffi_update() on the next GTK main event
/// loop iteration
/// @param obj Waybar CFFI object pointer
void (*queue_update)(wbcffi_module*);
} wbcffi_init_info;

/// Config key-value pair
typedef struct {
/// Entry key
const char* key;
/// Entry value as string. JSON object and arrays are serialized.
const char* value;
} wbcffi_config_entry;

/// Module init/new function, called on module instantiation
///
/// MANDATORY CFFI function
///
/// @param init_info Waybar module information
/// @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(const wbcffi_init_info* init_info, const 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
///
/// MANDATORY CFFI function
///
/// @param instance Module instance data (as returned by `wbcffi_init`)
void wbcffi_deinit(void* instance);

/// 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 action_name Action name
void wbcffi_doaction(void* instance, const char* action_name);

#ifdef __cplusplus
}
#endif
3 changes: 3 additions & 0 deletions src/factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]);
}
if (ref.compare(0, 5, "cffi/") == 0 && ref.size() > 5) {
return new waybar::modules::CFFI(ref.substr(5), id, config_[name]);
}
} catch (const std::exception& e) {
auto err = fmt::format("Disabling module \"{}\", {}", name, e.what());
throw std::runtime_error(err);
Expand Down
Loading