A toolkit-agnostic widget framework for building desktop shells and system panels in Rust. AWF provides system services (brightness, network, audio, etc.) with adapters for popular GUI toolkits.
AWF separates system service logic from UI toolkit code, allowing you to:
- Write system services once (brightness, network, battery, etc.)
- Use them with any async runtime (tokio, compio, smol, async-std)
- Integrate with any GUI toolkit via adapters
| Crate | Purpose | Dependencies |
|---|---|---|
awf |
Service traits and implementations | async-watch, async-fs, futures-timer |
awf-iced |
Iced toolkit adapter | iced |
awf-relm4 |
Relm4/GTK4 toolkit adapter | relm4 |
- Runtime Agnostic: Core services use only
futuresand runtime-agnostic crates - Stream-Based: Services expose
futures::Stream<Item = Event>for reactive updates - Cold Start: Subscribers immediately receive current state, then updates
- Graceful Degradation: Services emit
Unavailable/Errorevents instead of panicking
use awf::services::brightness::{BrightnessService, BrightnessEvent};
use awf::WritableService;
use iced::{Subscription, Task};
struct App {
service: Arc<BrightnessService>,
brightness: Option<f64>,
}
#[derive(Debug, Clone)]
enum Message {
Brightness(BrightnessEvent),
SetBrightness(f64),
}
impl App {
fn subscription(&self) -> Subscription<Message> {
awf_iced::subscribe(&*self.service, Message::Brightness)
}
fn update(&mut self, msg: Message) -> Task<Message> {
match msg {
Message::Brightness(BrightnessEvent::Changed { percentage, .. }) => {
self.brightness = Some(percentage);
}
Message::SetBrightness(val) => {
return Task::perform(
self.service.send(BrightnessCommand::Set(val)),
|_| Message::Brightness(BrightnessEvent::Changed {
percentage: val, raw: 0, max: 0
})
);
}
_ => {}
}
Task::none()
}
}use awf::services::brightness::{BrightnessService, BrightnessEvent};
use relm4::ComponentSender;
fn init(
_init: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let service = Arc::new(BrightnessService::new());
service.spawn();
// Bridge service events to Relm4 messages
awf_relm4::subscribe(&*service, sender.clone(), Msg::Brightness);
// ...
}| Service | Module | Backend |
|---|---|---|
| Brightness | services::brightness |
sysfs (/sys/class/backlight) |
| Service | Backend |
|---|---|
| Network | NetworkManager (D-Bus) through nmrs |
| Audio | PipeWire/PulseAudio |
| Battery | UPower (D-Bus) |
| Notifications | Freedesktop notifications |
Implement the Service trait for your own system monitors:
use awf::{Service, ServiceError};
use futures::Stream;
use std::pin::Pin;
pub struct MyService {
// ...
}
#[derive(Debug, Clone)]
pub enum MyEvent {
Data(String),
Unavailable,
Error(ServiceError),
}
impl Service for MyService {
type Event = MyEvent;
fn subscribe(&self) -> Pin<Box<dyn Stream<Item = Self::Event> + Send>> {
let rx = self.rx.clone();
Box::pin(futures::stream::unfold(rx, |mut rx| async move {
let val = rx.recv().await.ok()?;
Some((val, rx))
}))
}
}cargo build --workspacecargo test -p awfLicensed under MIT license (LICENSE)
Inspired by: