waybar_cffi/
lib.rs

1//! A crate that allows Waybar modules to be built in Rust.
2//!
3//! Waybar modules are written using its experimental CFFI module interface, and
4//! implement their UI using [Gtk 3][`gtk`]. Modules are built as a shared
5//! library that is loaded by Waybar at runtime.
6//!
7//! This version of the bindings was built against Waybar version
8#![doc = concat!(include_str!("../WAYBAR_VERSION"), ".")]
9//!
10//! ## Example
11//!
12//! Implementing a module is done by implementing the [`Module`] trait, and then
13//! calling [`waybar_module`] to export the required symbols from the shared
14//! library:
15//!
16//! ```
17#![doc = include_str!("../examples/hello-world.rs")]
18//! ```
19//!
20//! This example is also available in `examples/hello-world.rs`, and can be built and run with:
21//!
22//! ```bash
23//! $ cargo build --example hello-world
24//! $ waybar -c examples/hello-world.json
25//! ```
26//!
27//! ## Gtk
28//!
29//! Waybar still uses Gtk 3 for its UI, so modules are required to also use it.
30//!
31//! This crate re-exports the [`gtk`] crate, and it is strongly suggested that
32//! you use this re-export, rather than including `gtk` as a dependency in your
33//! own crate.
34//!
35//! ### Async
36//!
37//! If you need async functionality, you should use
38//! [`glib::MainContext`][gtk::glib::MainContext] as the reactor via methods
39//! like [`MainContext::spawn_local`][gtk::glib::MainContext::spawn_local],
40//! rather than trying to start up a new async runtime. This will ensure that
41//! your module integrates properly with the main Waybar event loop.
42
43#[doc(hidden)]
44pub mod config;
45mod error;
46mod info;
47
48#[doc(hidden)]
49pub use crate::error::Error;
50pub use crate::info::InitInfo;
51#[doc(hidden)]
52pub use serde;
53#[doc(hidden)]
54pub use waybar_cffi_sys as sys;
55pub use waybar_cffi_sys::gtk;
56
57use serde::de::DeserializeOwned;
58use waybar_cffi_sys::{
59    libc::{c_void, size_t},
60    wbcffi_config_entry, wbcffi_init_info,
61};
62
63/// A Waybar CFFI module.
64///
65/// In most cases, only [`Module::init`] needs to be specified, and the default
66/// implementations of the other methods can be used unchanged.
67#[allow(unused_variables)]
68pub trait Module {
69    /// The configuration type.
70    ///
71    /// The JSONC configuration for the module will be deserialised using
72    /// [`serde`] into a value of this type.
73    type Config: DeserializeOwned;
74
75    /// Called when the module is being initialised.
76    ///
77    /// Generally, you'll want to create your UI using [`gtk`] here, attaching
78    /// it to the container returned by [`InitInfo::get_root_widget`].
79    fn init(info: &InitInfo, config: Self::Config) -> Self;
80
81    /// Called when the module should be updated.
82    fn update(&mut self) {}
83
84    /// Called when the module should be refreshed in response to a signal.
85    fn refresh(&mut self, signal: i32) {}
86
87    /// Called when an action is called on the module.
88    fn do_action(&mut self, action: &str) {}
89}
90
91/// Defines and exports the C functions required for the module to be used by
92/// Waybar.
93///
94/// This macro must be invoked exactly once in a Waybar module crate.
95#[macro_export]
96macro_rules! waybar_module {
97    ($ty:ty) => {
98        #[allow(non_upper_case_globals)]
99        #[unsafe(no_mangle)]
100        pub static wbcffi_version: $crate::sys::libc::size_t = 1;
101
102        #[allow(clippy::not_unsafe_ptr_arg_deref)]
103        #[unsafe(no_mangle)]
104        pub extern "C" fn wbcffi_init(
105            init_info: *const $crate::sys::wbcffi_init_info,
106            config_entries: *const $crate::sys::wbcffi_config_entry,
107            config_entries_len: $crate::sys::libc::size_t,
108        ) -> *mut $crate::sys::libc::c_void {
109            $crate::init::<$ty>(
110                stringify!($ty),
111                init_info,
112                config_entries,
113                config_entries_len,
114            )
115        }
116
117        #[allow(clippy::not_unsafe_ptr_arg_deref)]
118        #[unsafe(no_mangle)]
119        pub extern "C" fn wbcffi_deinit(instance: *mut $crate::sys::libc::c_void) {
120            let ptr = instance as *mut $ty;
121            let module = unsafe { Box::from_raw(ptr) };
122
123            // Intentionally not converted back to raw, as we want the module to be dropped.
124        }
125
126        #[allow(clippy::not_unsafe_ptr_arg_deref)]
127        #[unsafe(no_mangle)]
128        pub extern "C" fn wbcffi_update(instance: *mut $crate::sys::libc::c_void) {
129            let ptr = instance as *mut $ty;
130            let mut module = unsafe { Box::from_raw(ptr) };
131
132            module.update();
133
134            let _ = Box::into_raw(module);
135        }
136
137        #[allow(clippy::not_unsafe_ptr_arg_deref)]
138        #[unsafe(no_mangle)]
139        pub extern "C" fn wbcffi_refresh(
140            instance: *mut $crate::sys::libc::c_void,
141            signal: $crate::sys::libc::c_int,
142        ) {
143            let ptr = instance as *mut $ty;
144            let mut module = unsafe { Box::from_raw(ptr) };
145
146            module.refresh(signal as i32);
147
148            let _ = Box::into_raw(module);
149        }
150
151        #[allow(clippy::not_unsafe_ptr_arg_deref)]
152        #[unsafe(no_mangle)]
153        pub extern "C" fn wbcffi_doaction(
154            instance: *mut $crate::sys::libc::c_void,
155            action_name: *const $crate::sys::libc::c_char,
156        ) {
157            let ptr = instance as *mut $ty;
158            let mut module = unsafe { Box::from_raw(ptr) };
159
160            let action = unsafe { std::ffi::CStr::from_ptr(action_name) }
161                .to_str()
162                .expect("action name");
163            module.do_action(action);
164
165            let _ = Box::into_raw(module);
166        }
167    };
168}
169
170#[doc(hidden)]
171pub fn init<T: Module>(
172    name: &'static str,
173    init_info: *const wbcffi_init_info,
174    config_entries: *const wbcffi_config_entry,
175    config_entries_len: size_t,
176) -> *mut c_void {
177    // A tiny wrapper to avoid having to do too much from within the macro
178    // definition.
179    match do_init::<T>(init_info, config_entries, config_entries_len) {
180        Ok(module) => Box::into_raw(Box::new(module)) as *mut c_void,
181        Err(e) => {
182            eprintln!("{name} init error: {e}");
183            std::ptr::null_mut()
184        }
185    }
186}
187
188fn do_init<T: Module>(
189    init_info: *const wbcffi_init_info,
190    config_entries: *const wbcffi_config_entry,
191    config_entries_len: size_t,
192) -> Result<T, Error> {
193    // This has to happen somewhere. Here's as good as anywhere, and at least we
194    // know it's early.
195    gtk::init().map_err(Error::Gtk)?;
196
197    let info = InitInfo::new(init_info)?;
198    let config = config::parse(config_entries, config_entries_len)?;
199
200    Ok(T::init(&info, config))
201}