2025-10-08 08:19:35 +02:00
|
|
|
// Copyright 2024 System76 <info@system76.com>
|
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
|
|
|
|
pub mod device {
|
|
|
|
|
use futures::{FutureExt, Stream, StreamExt};
|
|
|
|
|
use iced_futures::Subscription;
|
2026-05-04 18:02:46 +02:00
|
|
|
use std::fmt::Debug;
|
|
|
|
|
use std::hash::Hash;
|
2025-10-08 08:19:35 +02:00
|
|
|
use upower_dbus::{BatteryType, DeviceProxy, UPowerProxy};
|
|
|
|
|
|
|
|
|
|
pub fn device_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
|
|
|
|
id: I,
|
|
|
|
|
) -> iced_futures::Subscription<DeviceDbusEvent> {
|
2026-03-17 16:56:55 -04:00
|
|
|
Subscription::run_with(id, |_| {
|
2025-10-08 08:19:35 +02:00
|
|
|
async move {
|
|
|
|
|
match events().await {
|
|
|
|
|
Ok(stream) => stream,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
log::error!("Error getting events from upower device: {}", err);
|
|
|
|
|
futures::future::pending().await
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-17 16:56:55 -04:00
|
|
|
.flatten_stream()
|
|
|
|
|
})
|
2025-10-08 08:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn display_device() -> zbus::Result<(UPowerProxy<'static>, DeviceProxy<'static>)> {
|
|
|
|
|
let connection = zbus::Connection::system().await?;
|
|
|
|
|
let upower: UPowerProxy<'_> = UPowerProxy::new(&connection).await?;
|
|
|
|
|
let device_path = upower.get_display_device().await?;
|
|
|
|
|
Ok((upower, device_path))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn events() -> zbus::Result<impl Stream<Item = DeviceDbusEvent>> {
|
|
|
|
|
let (upower, device) = display_device().await?;
|
|
|
|
|
let devices = upower.enumerate_devices().await?;
|
|
|
|
|
let mut has_battery = false;
|
|
|
|
|
for device in devices {
|
|
|
|
|
let Ok(d) = DeviceProxy::builder(upower.inner().connection()).path(device) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let Ok(d) = d.build().await else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
if d.type_().await == Ok(BatteryType::Battery)
|
|
|
|
|
&& d.power_supply().await.unwrap_or_default()
|
|
|
|
|
{
|
|
|
|
|
has_battery = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let initial = futures::stream::iter(if has_battery {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(DeviceDbusEvent::NoBattery)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let stream = futures::stream_select!(
|
|
|
|
|
upower.receive_on_battery_changed().await.map(|_| ()),
|
|
|
|
|
device.receive_percentage_changed().await.map(|_| ()),
|
|
|
|
|
device.receive_time_to_empty_changed().await.map(|_| ()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(initial.chain(stream.map(move |_| {
|
|
|
|
|
DeviceDbusEvent::Update {
|
|
|
|
|
on_battery: upower
|
|
|
|
|
.cached_on_battery()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.unwrap_or_default(),
|
|
|
|
|
percent: device
|
|
|
|
|
.cached_percentage()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.unwrap_or_default(),
|
|
|
|
|
time_to_empty: device
|
|
|
|
|
.cached_time_to_empty()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.unwrap_or_default(),
|
|
|
|
|
}
|
|
|
|
|
})))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum DeviceDbusEvent {
|
|
|
|
|
NoBattery,
|
|
|
|
|
Update {
|
|
|
|
|
on_battery: bool,
|
|
|
|
|
percent: f64,
|
|
|
|
|
time_to_empty: i64,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub mod kbdbacklight {
|
|
|
|
|
// TODO Test how this handles upower starting after applet does, if it ever is
|
|
|
|
|
// started or restarted.
|
|
|
|
|
|
|
|
|
|
use futures::{FutureExt, Stream, StreamExt};
|
|
|
|
|
use iced_futures::Subscription;
|
2026-05-04 18:02:46 +02:00
|
|
|
use std::fmt::Debug;
|
|
|
|
|
use std::hash::Hash;
|
2025-10-08 08:19:35 +02:00
|
|
|
use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
|
|
|
|
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
|
|
|
|
use upower_dbus::{BrightnessChanged, KbdBacklightProxy};
|
|
|
|
|
|
|
|
|
|
pub fn kbd_backlight_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
|
|
|
|
id: I,
|
|
|
|
|
) -> iced_futures::Subscription<KeyboardBacklightUpdate> {
|
2026-03-17 16:56:55 -04:00
|
|
|
Subscription::run_with(id, |_| {
|
2025-10-08 08:19:35 +02:00
|
|
|
async move {
|
|
|
|
|
match events().await {
|
|
|
|
|
Ok(stream) => stream,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
log::error!("Error listening to KbdBacklight: {}", err);
|
|
|
|
|
futures::future::pending().await
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-17 16:56:55 -04:00
|
|
|
.flatten_stream()
|
|
|
|
|
})
|
2025-10-08 08:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Event {
|
|
|
|
|
BrightnessChanged(BrightnessChanged),
|
|
|
|
|
Request(KeyboardBacklightRequest),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn events() -> zbus::Result<impl Stream<Item = KeyboardBacklightUpdate>> {
|
|
|
|
|
let conn = zbus::Connection::system().await?;
|
|
|
|
|
let kbd_proxy = KbdBacklightProxy::builder(&conn).build().await?;
|
|
|
|
|
let (tx, rx) = unbounded_channel();
|
|
|
|
|
|
|
|
|
|
let max_brightness = kbd_proxy.get_max_brightness().await?;
|
|
|
|
|
let brightness = kbd_proxy.get_brightness().await?;
|
|
|
|
|
|
|
|
|
|
let brightness_changed_stream = kbd_proxy.receive_brightness_changed().await?;
|
|
|
|
|
|
|
|
|
|
let initial = futures::stream::iter([
|
|
|
|
|
KeyboardBacklightUpdate::Sender(tx),
|
|
|
|
|
KeyboardBacklightUpdate::MaxBrightness(max_brightness),
|
|
|
|
|
KeyboardBacklightUpdate::Brightness(brightness),
|
|
|
|
|
]);
|
|
|
|
|
let stream = futures::stream::select(
|
|
|
|
|
UnboundedReceiverStream::new(rx).map(Event::Request),
|
|
|
|
|
brightness_changed_stream.map(Event::BrightnessChanged),
|
|
|
|
|
);
|
|
|
|
|
Ok(initial.chain(stream.filter_map(move |event| {
|
|
|
|
|
let kbd_proxy = kbd_proxy.clone();
|
|
|
|
|
async move {
|
|
|
|
|
match event {
|
|
|
|
|
Event::BrightnessChanged(changed) => {
|
|
|
|
|
if let Ok(args) = changed.args() {
|
|
|
|
|
Some(KeyboardBacklightUpdate::Brightness(*args.value()))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Event::Request(req) => match req {
|
|
|
|
|
KeyboardBacklightRequest::Get => {
|
|
|
|
|
if let Ok(brightness) = kbd_proxy.get_brightness().await {
|
|
|
|
|
Some(KeyboardBacklightUpdate::Brightness(brightness))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
KeyboardBacklightRequest::Set(value) => {
|
|
|
|
|
let _ = kbd_proxy.set_brightness(value).await;
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum KeyboardBacklightUpdate {
|
|
|
|
|
Sender(UnboundedSender<KeyboardBacklightRequest>),
|
|
|
|
|
Brightness(i32),
|
|
|
|
|
MaxBrightness(i32),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum KeyboardBacklightRequest {
|
|
|
|
|
Get,
|
|
|
|
|
Set(i32),
|
|
|
|
|
}
|
|
|
|
|
}
|