DistroShelf is a Rust-based GTK4/Libadwaita GUI for managing Distrobox containers. Built with Meson, it provides container lifecycle management, package installation, and application export functionality.
Central reactive store pattern (src/store/root_store.rs):
- Single GObject holding all app state: containers, tasks, images, settings
- UI binds directly to
RootStoreproperties via GObject data binding - State updates trigger automatic UI refresh through property notifications
- Contains
Query<T>instances for async data with built-in loading/error states - Example:
containers_query: Query<Vec<Container>>exposed as bindable property
ALL shell commands MUST use CommandRunner abstraction (src/fakers/command_runner.rs):
// CORRECT - works in both native and Flatpak
let cmd = Command::new("distrobox-list");
let output = runner.output(cmd).await?;
// WRONG - breaks in Flatpak
let output = std::process::Command::new("distrobox-list").output()?;Why: Flatpak apps cannot directly exec host commands. FlatpakCommandRunner automatically wraps commands with flatpak-spawn --host (see src/backends/flatpak.rs::map_flatpak_spawn_host).
Implementations:
RealCommandRunner: Direct execution (native builds)FlatpakCommandRunner: Wraps withflatpak-spawn --hostNullCommandRunner: Returns mock responses for testing/previews
Standard gtk-rs pattern used throughout (src/container.rs, src/window.rs, etc.):
mod imp {
#[derive(Properties)]
#[properties(wrapper_type = super::MyWidget)]
pub struct MyWidget {
#[property(get, set)]
name: RefCell<String>,
}
}
glib::wrapper! {
pub struct MyWidget(ObjectSubclass<imp::MyWidget>);
}UI widgets use GTK composite templates:
#[derive(gtk::CompositeTemplate)]
#[template(file = "window.ui")]
pub struct DistroShelfWindow {
#[template_child]
pub sidebar_list_view: TemplateChild<gtk::ListView>,
}
// Connect callbacks in imp module:
#[gtk::template_callbacks]
impl WelcomeView {
#[template_callback]
fn continue_to_terminal_page(&self, _: >k::Button) { /* ... */ }
}Widget .ui files live alongside their Rust implementations in src/widgets/. Global UI resources (help overlay, etc.) remain in data/gtk/.
Wraps async operations with reactive state (src/query/mod.rs):
let query = Query::new("containers", || async { fetch_containers().await })
.with_timeout(Duration::from_secs(5))
.with_retry_strategy(|n| if n < 3 { Some(Duration::from_secs(n as u64)) } else { None });
query.refetch(); // Triggers fetch, updates is-loading/data/error properties
query.connect_success(|data| { /* UI update */ });Properties: is-loading, data, error, last-fetched-at
Tracks command execution with output streaming (src/distrobox_task.rs):
let task = DistroboxTask::new("my-container", "Upgrade", |task| async move {
let child = runner.spawn(Command::new("distrobox-upgrade"))?;
task.handle_child_output(child).await?; // Streams output to task.vte_terminal()
Ok(())
});
// Status: "pending" -> "executing" -> "successful"/"failed"
// Displayed in TaskManagerDialog with live outputType-safe wrapper over gio::ListStore (src/gtk_utils/typed_list_store.rs):
let store = TypedListStore::<Container>::new();
for container in store.iter() { /* No downcasting needed */ }Use reconcile_list_by_key to diff-update lists without full rebuild:
reconcile_list_by_key(&store, &new_containers, |c| c.name(), &["status", "image"]);
// Updates existing items, adds new, removes old - preserves object identityAlways use attribute syntax for weak/strong references:
btn.connect_clicked(clone!(
#[weak(rename_to=this)]
self,
#[strong]
data,
move |_| { this.do_something(&data); }
));App automatically detects Flatpak environment and configures CommandRunner:
- Native: Uses
RealCommandRunner - Flatpak: Uses
FlatpakCommandRunner(wraps all commands withflatpak-spawn --host) - See
src/backends/flatpak.rsand application initialization insrc/application.rs
ContainerRuntime trait (src/backends/container_runtime.rs) abstracts Podman/Docker:
- Auto-detects available runtime at startup
- Provides unified interface for images, events, container status
RootStore::container_runtimeis aQuery<Rc<dyn ContainerRuntime>>
Shell script in src/backends/distrobox/POSIX_FIND_AND_CONCAT_DESKTOP_FILES.sh finds and encodes desktop files from containers for app export. Uses hex-encoding to avoid shell escaping issues.
Call #tool:runSubagent with gnome-doc-librarian to fetch up-to-date documentation for GNOME libraries and components.