Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions cosmic-applet-battery/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ struct CosmicBatteryApplet {
battery_percent: f64,
on_battery: bool,
gpus: HashMap<PathBuf, GPUData>,
update_trigger: Option<UnboundedSender<()>>,
time_remaining: Duration,
max_kbd_brightness: Option<i32>,
kbd_brightness: Option<i32>,
Expand Down Expand Up @@ -180,6 +181,7 @@ enum Message {
SetChargingLimit(chain::Toggler, bool),
KeyboardBacklight(KeyboardBacklightUpdate),
UpowerDevice(DeviceDbusEvent),
GpuInit(UnboundedSender<()>),
GpuOn(PathBuf, String, Option<Vec<Entry>>),
GpuOff(PathBuf),
ToggleGpuApps(PathBuf),
Expand Down Expand Up @@ -338,6 +340,9 @@ impl cosmic::Application for CosmicBatteryApplet {
if let Some(tx) = self.power_profile_sender.as_ref() {
let _ = tx.send(PowerProfileRequest::Get);
}
if let Some(tx) = self.update_trigger.as_ref() {
let _ = tx.send(());
}
let mut tasks = vec![get_popup(popup_settings)];
// Try again every time a popup is opened
if self.charging_limit.is_none() {
Expand Down Expand Up @@ -424,6 +429,9 @@ impl cosmic::Application for CosmicBatteryApplet {
tokio::spawn(cosmic::process::spawn(cmd));
}
},
Message::GpuInit(tx) => {
self.update_trigger = Some(tx);
}
Message::GpuOn(path, name, app_list) => {
let toggled = self
.gpus
Expand Down Expand Up @@ -483,7 +491,7 @@ impl cosmic::Application for CosmicBatteryApplet {
.on_press_down(Message::TogglePopup)
.into();

if !self.gpus.is_empty() {
let content = if !self.gpus.is_empty() {
let dot = container(vertical_space().height(Length::Fixed(0.0)))
.padding(2.0)
.class(cosmic::style::Container::Custom(Box::new(|theme| {
Expand Down Expand Up @@ -511,7 +519,9 @@ impl cosmic::Application for CosmicBatteryApplet {
}
} else {
btn
}
};

self.core.applet.autosize_window(content).into()
}

fn view_window(&self, _id: window::Id) -> Element<Message> {
Expand Down Expand Up @@ -843,6 +853,7 @@ impl cosmic::Application for CosmicBatteryApplet {
PowerProfileUpdate::Error(e) => Message::Errored(e), // TODO: handle error
}),
dgpu_subscription(0).map(|event| match event {
GpuUpdate::Init(tx) => Message::GpuInit(tx),
GpuUpdate::On(path, name, list) => Message::GpuOn(path, name, list),
GpuUpdate::Off(path) => Message::GpuOff(path),
}),
Expand Down
97 changes: 72 additions & 25 deletions cosmic-applet-battery/src/dgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ use cosmic::{
iced::{self, Subscription},
iced_futures::stream,
};
use drm::control::Device as ControlDevice;
use drm::control::{
Device as ControlDevice,
connector::{Info as ConnectorInfo, Interface},
};
use futures::{FutureExt, SinkExt};
use tokio::{
io::unix::AsyncFd,
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
task::spawn_blocking,
time::{self, Interval},
};
Expand Down Expand Up @@ -56,6 +60,7 @@ impl Debug for GpuMonitor {
struct Gpu {
path: PathBuf,
name: String,
boot_vga: bool,
primary: bool,
enabled: bool,
driver: Option<OsString>,
Expand Down Expand Up @@ -146,26 +151,32 @@ pub struct RunningApp {
executable_name: String,
}

fn connectors(path: &impl AsRef<Path>) -> Option<impl Iterator<Item = ConnectorInfo>> {
struct Device(std::fs::File);
impl AsFd for Device {
fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> {
self.0.as_fd()
}
}
impl drm::Device for Device {}
impl ControlDevice for Device {}

let device = Device(std::fs::File::open(path).ok()?);
let resources = device.resource_handles().ok()?;

Some(
resources
.connectors
.into_iter()
.filter_map(move |conn| device.get_connector(conn, false).ok()),
)
}

impl Gpu {
async fn connected_outputs(&self) -> Option<Vec<Entry>> {
let path = self.path.clone();
spawn_blocking(move || {
struct Device(std::fs::File);
impl AsFd for Device {
fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> {
self.0.as_fd()
}
}
impl drm::Device for Device {}
impl ControlDevice for Device {}

let device = Device(std::fs::File::open(path).ok()?);
let resources = device.resource_handles().ok()?;

let outputs = resources
.connectors
.into_iter()
.filter_map(|conn| device.get_connector(conn, false).ok())
let outputs = connectors(&path)?
.filter(|info| info.state() == drm::control::connector::State::Connected)
.map(|info| Entry {
name: format!(
Expand All @@ -177,6 +188,7 @@ impl Gpu {
secondary: String::new(),
})
.collect();

// TODO read and parse edid with libdisplay-info and display output manufacture/model

Some(outputs)
Expand Down Expand Up @@ -308,7 +320,7 @@ fn all_gpus<S: AsRef<str>>(seat: S) -> io::Result<Vec<Gpu>> {
let mut enumerator = udev::Enumerator::new()?;
enumerator.match_subsystem("drm")?;
enumerator.match_sysname("card[0-9]*")?;
Ok(enumerator
let mut gpus = enumerator
.scan_devices()?
.filter(|device| {
device
Expand Down Expand Up @@ -369,13 +381,34 @@ fn all_gpus<S: AsRef<str>>(seat: S) -> io::Result<Vec<Gpu>> {
Some(Gpu {
path,
name,
primary: boot_vga,
boot_vga,
primary: false,
enabled: false,
driver,
interval,
})
})
.collect())
.collect::<Vec<_>>();

if let Some(primary_idx) = gpus
.iter()
.position(|gpu| {
connectors(&gpu.path).is_some_and(|mut conns| {
conns.any(|info| {
let i = info.interface();
i == Interface::EmbeddedDisplayPort
|| i == Interface::LVDS
|| i == Interface::DSI
})
})
})
.or_else(|| gpus.iter().position(|gpu| gpu.boot_vga))
.or_else(|| (gpus.len() == 1).then_some(0))
{
gpus[primary_idx].primary = true;
}

Ok(gpus)
}

pub fn dgpu_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
Expand All @@ -396,12 +429,13 @@ pub fn dgpu_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
#[derive(Debug)]
pub enum State {
Ready,
Waiting(GpuMonitor),
Waiting(GpuMonitor, UnboundedReceiver<()>),
Finished,
}

#[derive(Debug)]
pub enum GpuUpdate {
Init(UnboundedSender<()>),
Off(PathBuf),
On(PathBuf, String, Option<Vec<Entry>>),
}
Expand All @@ -412,10 +446,17 @@ async fn start_listening(
) -> State {
match state {
State::Ready => match GpuMonitor::new().await {
Some(monitor) => State::Waiting(monitor),
Some(monitor) => {
let (tx, rx) = mpsc::unbounded_channel();
if output.send(GpuUpdate::Init(tx)).await.is_err() {
State::Finished
} else {
State::Waiting(monitor, rx)
}
}
None => State::Finished,
},
State::Waiting(mut monitor) => {
State::Waiting(mut monitor, mut trigger) => {
let select_all = futures::future::select_all(
monitor
.gpus
Expand Down Expand Up @@ -472,6 +513,7 @@ async fn start_listening(
monitor.gpus.push(Gpu {
path: path.to_path_buf(),
name,
boot_vga: false,
primary: false,
enabled: false,
driver,
Expand Down Expand Up @@ -503,7 +545,7 @@ async fn start_listening(
i = select_all => {
let gpu = &mut monitor.gpus[i];
if gpu.path == monitor.primary_gpu {
return State::Waiting(monitor);
return State::Waiting(monitor, trigger);
}

trace!("Polling gpu {}", gpu.path.display());
Expand All @@ -529,9 +571,14 @@ async fn start_listening(
return State::Finished;
}
}
_ = trigger.recv() => {
for gpu in &mut monitor.gpus {
gpu.interval.reset_immediately();
}
}
};

State::Waiting(monitor)
State::Waiting(monitor, trigger)
}
State::Finished => iced::futures::future::pending().await,
}
Expand Down
Loading