Expand description
§runlatch-core
The data model, provider abstraction, built-in providers, and registry that power runlatch, a modular Linux autostart manager.
Linux autostart is spread across systemd units, XDG .desktop files, and DE-specific
session configs. This crate unifies them behind one trait, AutostartProvider, aggregated
by a Registry. New backends (OpenRC, KDE, GNOME, …) are added by implementing the trait —
no changes to the core.
§Quick start
Enumerate every autostart entry on the machine across all built-in providers:
use runlatch_core::Registry;
let registry = Registry::with_defaults();
let result = registry.all_entries().await;
for entry in &result.entries {
let mark = if entry.enabled { "●" } else { "○" };
println!("{mark} {}:{} {}", entry.source, entry.id, entry.command);
}
// Aggregation never fails as a whole: a provider that errored is reported here
// while every other provider's entries are still returned above.
for err in &result.errors {
eprintln!("warning: provider '{}' failed: {:#}", err.source, err.error);
}The crate is runtime-agnostic — it only ever .awaits, so the binary brings its own runtime
(see Architecture).
§Writing a provider
A backend is any type implementing AutostartProvider. It must be Send + Sync so the
registry can hold it as a Box<dyn AutostartProvider> and share it across tasks. Implement the
seven methods, then hand an instance to Registry::new:
use anyhow::Result;
use async_trait::async_trait;
use runlatch_core::{AutostartEntry, AutostartProvider, Registry, Scope};
struct MyProvider;
#[async_trait]
impl AutostartProvider for MyProvider {
/// Stable id, also the left side of a `source:id` address.
fn id(&self) -> &'static str { "my-provider" }
fn scope(&self) -> Scope { Scope::User }
/// Real runtime detection. Return `false` (never panic) when the backend
/// isn't present — the registry then silently skips this provider.
async fn is_available(&self) -> bool { true }
async fn entries(&self) -> Result<Vec<AutostartEntry>> { Ok(vec![]) }
async fn enable(&self, _id: &str) -> Result<()> { Ok(()) }
async fn disable(&self, _id: &str) -> Result<()> { Ok(()) }
async fn add(&self, _entry: &AutostartEntry) -> Result<()> { Ok(()) }
async fn remove(&self, _id: &str) -> Result<()> { Ok(()) }
}
// Mix your provider in with the built-ins, or run it standalone.
let registry = Registry::new(vec![Box::new(MyProvider)]);
let _ = registry.all_entries().await;Each AutostartEntry you return is tagged with your id as its source, so callers can
address it unambiguously as my-provider:<entry-id>.
§Architecture
Async on purpose. The I/O methods are async because probing and querying some backends
(notably systemd over D-Bus) can block for a noticeable time. Keeping the core async and
runtime-agnostic means it only ever .awaits — it never spins up a runtime or block_ons — so a
GUI front-end can drive it without pinning its render thread on a slow bus call.
Aggregation never fails as a whole. Registry::all_entries collects a failing provider’s
error into AggregateResult::errors while still returning every other provider’s good results.
One broken backend never blanks the listing.
Order-preserving .desktop editing. The XDG provider toggles entries by setting/removing the
Hidden key via DesktopFile, which edits a single key while leaving comments, ordering, and
unrelated keys byte-for-byte intact.
§License
Licensed under the MIT license.
§Module map
model— the provider-agnosticAutostartEntryandScope.provider— theAutostartProvidertrait and why it isasync.providers— the built-inXdgAutostartProvider(user and system) andSystemdProvider(user and system).registry— the aggregatingRegistry.desktop_file— an order-preserving.desktopreader/writer.
Re-exports§
pub use model::AutostartEntry;pub use model::Scope;pub use provider::AutostartProvider;pub use providers::SystemdProvider;pub use providers::XdgAutostartProvider;pub use registry::AggregateResult;pub use registry::ProviderError;pub use registry::Registry;
Modules§
- desktop_
file - A small, order-preserving reader/writer for freedesktop Desktop Entry files.
- model
- Provider-agnostic data model shared across all autostart providers.
- provider
- The provider abstraction.
- providers
- Built-in
AutostartProviderimplementations. - registry
- The provider registry: the single entry point callers use to work with every available autostart backend at once.