C+
Packages · View as Markdown

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 PreferencesGroupPreferencesPagePreferencesWindow 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.