adwaita
C+ bindings for libadwaita 1, GNOME's companion library to GTK 4: adaptive widgets, the GNOME HIG look, boxed-list preferences, in-app toasts, and runtime light/dark recoloring. Nine modules cover the surface: application, window, header, view, toast, rows, viewstack, style, and widgets. Import the adwaita/adwaita umbrella for the flat Adw* type surface, or pull in a narrower module directly (import "adwaita/rows" as rows;).
This is a thin layer on top of gtk, not a replacement. Every Adwaita object is a GObject and the key types subclass their GTK counterparts (AdwApplication ⊂ GtkApplication, AdwApplicationWindow ⊂ GtkApplicationWindow), so the bindings reuse all of gtk through raw pointers and add only the Adw* widgets. You mix the two freely: a gtk::Button inside an adwaita::HeaderBar, a gtk::Box inside an adwaita::Clamp. Adwaita stays a separate package because libadwaita is its own optional, opinionated shared library; a pure-GTK app on a non-GNOME desktop should not be forced to link it.
Ownership
Same model as gtk: widget wrappers are opaque borrowed handles — the parent container sinks the floating reference when the child is added, so there is no drop. Only Application owns a full reference, released with unref() after run(). Two transfers of ownership to remember: ToastOverlay::add_toast takes ownership of the toast (do not reuse the wrapper afterwards), and the AdwApplication owns its windows.
Strings and raw pointers
The C ABI wants NUL-terminated *u8, so string arguments are passed with the #str_ptr("…\0") intrinsic. Adwaita containers hold GTK widgets, so child slots take a raw *u8 — call .raw() on a wrapper to hand its object to a parent (tv.add_top_bar(header.raw())). Pass 0 as *u8 where an argument is optional, such as an empty subtitle.
A first window
import "adwaita/adwaita" as adw;
fn on_activate(app: *u8, _d: *u8) {
let win = adw::Window::for_application(app);
win.set_default_size(420 as i32, 360 as i32);
let tv = adw::ToolbarView::new();
let header = adw::HeaderBar::new();
header.set_title_widget(
adw::WindowTitle::new(#str_ptr("Demo\0"), #str_ptr("\0")).raw());
tv.add_top_bar(header.raw());
let group = adw::PreferencesGroup::new();
let row = adw::SwitchRow::new();
row.set_title(#str_ptr("Enabled\0"));
row.set_active(1 as i32);
group.add(row.raw());
let clamp = adw::Clamp::new();
clamp.set_child(group.raw());
tv.set_content(clamp.raw());
win.set_content(tv.raw());
win.present();
}
fn main() -> i32 {
let app = adw::Application::new(#str_ptr("org.example.Demo\0"));
app.connect_activate(on_activate);
let status = app.run();
app.unref();
return status;
}
Application initializes libadwaita (it loads the Adwaita stylesheet and sets up the style manager) and delegates the main loop to its GtkApplication base. The window's single child slot is set_content (not GTK's set_child), and it typically holds a ToolbarView — the modern scaffold that stacks top bars, content, and bottom bars.
Boxed-list preferences
The rows module is Adwaita's signature settings UI: ActionRow, EntryRow, SwitchRow, ComboRow, and ExpanderRow, organized through the PreferencesGroup → PreferencesPage → PreferencesWindow hierarchy. Row titles run through the shared AdwPreferencesRow base, so every row exposes set_title. Toggle and selection signals are closure-free — they connect a named handler function, in keeping with C+ having no closures:
import "adwaita/rows" as rows;
fn on_toggle(row: *u8, _ps: *u8) {
// fired on "notify::active"
}
fn build(group: rows::PreferencesGroup) {
let sw = rows::SwitchRow::new();
sw.set_title(#str_ptr("Sync on launch\0"));
sw.connect_active_changed(on_toggle);
group.add(sw.raw());
}
Color scheme
StyleManager::default_manager() is the application-wide manager (a borrowed singleton, no unref). Set the preference with a color_scheme_* value — force_* overrides the system setting, prefer_* follows it when possible — and read is_dark() (or watch "notify::dark" via connect_dark_changed):
import "adwaita/style" as style;
let mgr = style::StyleManager::default_manager();
mgr.set_color_scheme(style::color_scheme_prefer_dark());
Platform notes
libadwaita is Linux/GNOME only. Install it before building (Debian/Ubuntu: sudo apt install libadwaita-1-dev); libadwaita-1 lands on the link line and the GTK stack comes transitively through the gtk dependency. See targets for the platform matrix. The remaining surface — toast (transient snackbars over a ToastOverlay), viewstack (ViewStack + ViewSwitcher paged content), view (Clamp, Bin, StatusPage), and widgets (Avatar, ButtonContent, SplitButton) — follows the same raw-pointer and .raw() conventions shown above.
For the closure-free callback mechanics and C-ABI message passing, see FFI. To compose UI declaratively in C+'s own syntax, see facet and builder blocks; to drive an app from an external agent, see the agent surface.