Thanks for helping improve Waybar! This guide covers the essentials.
meson setup build
ninja -C build
./build/waybar # run your build directlyEnable all optional modules while developing:
meson setup build -Dexperimental=trueWaybar follows Google's C++ style guide. Format your changes before committing:
clang-format -i <files>CI runs clang-format and a full build on Linux and FreeBSD — please make sure
both pass.
Module documentation lives in man/ as scdoc man pages, not in the
wiki. Editing a man page and merging to master regenerates the matching wiki
page automatically (see .github/wiki). When you add a module,
add its man page and a line in .github/wiki/mapping.json.
- Branch from
masterand keep each PR focused on one change. - Describe what changed and why; link any related issues.
- Add or update the man page for every user-facing option you introduce.
- Build and test against the module(s) you touched.
Have fun :)
- Standard: C++20.
- Build system: Meson (
meson.build). Project version is defined there. - Compiler flags: Added via
add_project_arguments()in Meson. Feature flags useHAVE_*/WANT_*prefixes (e.g.-DHAVE_NIRI,-DHAVE_HYPRLAND,-DHAVE_LIBUDEV).
- Tool:
.clang-formatis checked in. Never bypass it. - Style: Google base style.
- Indent: 2 spaces. No tabs.
- Column limit: 100.
- Braces: K&R (opening brace on the same line).
- Declaration alignment: Disabled (
AlignConsecutiveDeclarations: false). - Pointer/reference alignment: Left (
const Json::Value& config,int* ptr, notint *ptr).
- Match the primary exported class exactly:
AAppIconLabel.hpp,workspaces.cpp,backlight_backend.hpp. - Corresponding header and source should live in predictable paths:
include/<module/path>.hppsrc/<module/path>.cpp
- Classes / Structs:
PascalCase.- Abstract base classes are prefixed with
A(e.g.,AModule,ALabel,AIconLabel,AAppIconLabel).
- Abstract base classes are prefixed with
- Enums / Enum classes:
PascalCasename.- Enumerators:
UPPER_SNAKE_CASE(e.g.,SCROLL_DIR::NONE,KillSignalAction::RELOAD,ChangeType::Increase).
- Enumerators:
- Concepts / Type aliases:
PascalCase.
- Member variables:
snake_case_with a trailing underscore.- Examples:
config_,bar_,label_,app_icon_size_,distance_scrolled_y_,on_updated_cb_.
- Examples:
- Function parameters & locals:
snake_case(no trailing underscore).- Examples:
workspace_data,should_refresh,app_identifier,preferred_device.
- Examples:
- Static / constexpr constants:
UPPER_SNAKE_CASEor descriptivekPascalCase.- Examples:
MODULE_CLASS,EPOLL_MAX_EVENTS,kExecFailureExitCode.
- Examples:
- Free functions:
snake_case.- Examples:
sanitize_string(),rewrite_string(),get_total_memory(),best_device().
- Examples:
- Class methods:
lowerCamelCase.- Examples:
update(),tooltipEnabled(),handleScroll(),getScrollDir(),resolveFormat(),setBrightness().
- Examples:
- Virtual overrides: Mark with
override(andfinalwhere applicable). Header signatures often use a trailing return type:auto update() -> void override; auto refresh(int should_refresh) -> void;
- All lowercase, nested by module path:
namespace waybar { } namespace waybar::modules::niri { } namespace waybar::util { }
- Close every namespace with a comment:
} // namespace waybar::modules::niri
- Use
#pragma oncein all project headers. - Include order in
.cppfiles:- Corresponding header first.
- Blank line.
- External library headers (
<fmt/...>,<spdlog/...>,<gtkmm/...>,<json/json.h>). - Standard library headers (
<algorithm>,<vector>,<memory>). - Blank line.
- Other project headers (
"util/...","modules/...").
- Do not use
using namespacein headers. In.cppfiles it is acceptable for narrow scopes (e.g.,using namespace std::literals::chrono_literals;). - Headers that expose standard-library types in their public interface (e.g.
std::chrono::millisecondsas a return type orstd::vector<T>as a member) must#includethe corresponding standard header directly. Do not rely on transitive includes from other headers.
- All UI modules ultimately derive from
AModule(and oftenALabelorAIconLabel). - Accept configuration in constructors:
MyModule(const Json::Value& config, const std::string& name, const std::string& id, ...);
- Use
Glib::Dispatcher(viawaybar::SafeSignal) to marshal work to the GTK main thread. - Use
sigc::signalfor normal GTK++ signals. - If a scope must not be interrupted by
pthread_cancel, guard it withwaybar::util::CancellationGuard.
- Modules that talk to a compositor often implement a small
EventHandlerinterface (onEvent(...)) and delegate to a singleton backend (e.g.,gIPC).
- Prefer
std::unique_ptrwith custom deleters over rawnew/deletefor C-API resources (seeScopedFd,UdevDeleter,UdevDeviceDeleter,ScopeGuard).
- Every module receives
const Json::Value& config(usually as the first constructor argument). - Always validate node type before reading:
if (config_["sort-by-id"].isBool()) { ... } if (config.isMember("window-rewrite-default") && config["window-rewrite-default"].isString()) { ... }
- Use
waybar::util::JsonParserif you need to pre-process JSON with non-standard escape sequences.
- Use
fmt::format/fmt::joinfor all string composition. - Use
fmt::dynamic_format_arg_store<fmt::format_context>when building arguments dynamically. - Custom
fmt::formatterspecializations are allowed for domain types (e.g.,Glib::ustring, project enums). - Sanitize arbitrary text before inserting into Pango markup with
waybar::util::sanitize_string. - Use
waybar::util::rewriteStringfor user-configurable regex rewrites. - Truncate UTF-8 safely with
waybar::util::utf8_truncate/utf8_width.
- Use
spdlogfor all logging:spdlog::error("Context: {}", e.what());spdlog::warn("Deprecated key '{}', prefer '{}'", old, replacement);spdlog::debug("State changed to {}", value);
- Throw
std::runtime_error(or similar) for fatal initialization failures that should bubble up tomain().
- Prefer gtkmm-3.0 types (
Gtk::Button,Gtk::Label,Gdk::Pixbuf,Glib::RefPtr,Glib::ustring) over raw C GTK APIs. - Access the default icon theme through thread-safe wrappers if off the main thread (
DefaultGtkIconThemeWrapper). - Tooltips and labels should respect the module
tooltiptoggle (seetooltipEnabled()inAModule).
- Isolate platform-specific code in dedicated files (e.g.,
linux.cpp,bsd.cpp). - Use preprocessor guards for platform differences (
#if defined(__FreeBSD__),#if defined(HAVE_LIBNL)). - Keep the common interface in a shared header or base class.
- GTK is strictly single-threaded. Never emit raw
sigc::signalfrom background threads. - Use
waybar::SafeSignal<T...>to marshal events from worker threads to the GTK main loop. - When a module manages background threads, use
std::mutex,std::recursive_mutex, or atomic variables to protect shared state, and ensure the destructor joins or synchronizes with those threads before destroying resources.
- Do not use
strcpy,strcat, orsprintfinto fixed-size buffers (e.g.char buf[PATH_MAX]). Preferstd::string,std::vector<char>, orstd::arraywith bounds-safe operations. - When passing a
std::vector<char>buffer to a C API that expects a mutablechar*string, always ensure the buffer is null-terminated and clamp the written length tosize() - 1. Never usestd::copyfrom an unbounded source into a fixed-size buffer.
- Singletons or objects with process-wide lifetime must not store references (
&) or pointers to objects with shorter lifetime (e.g., configuration trees, GTK widgets, or bar instances) unless they are explicitly notified of destruction. Prefer storing configuration by value (Json::Value,std::string, etc.) if the singleton outlives the config loader.