battery: Add gpu indicator and process list
This commit is contained in:
parent
17073628af
commit
8472f1f358
8 changed files with 798 additions and 131 deletions
|
|
@ -2,6 +2,7 @@ use crate::backlight::{
|
|||
screen_backlight_subscription, ScreenBacklightRequest, ScreenBacklightUpdate,
|
||||
};
|
||||
use crate::config;
|
||||
use crate::dgpu::{dgpu_subscription, App, GpuUpdate};
|
||||
use crate::fl;
|
||||
use crate::power_daemon::{
|
||||
power_profile_subscription, Power, PowerProfileRequest, PowerProfileUpdate,
|
||||
|
|
@ -10,6 +11,7 @@ use crate::upower_device::{device_subscription, DeviceDbusEvent};
|
|||
use crate::upower_kbdbacklight::{
|
||||
kbd_backlight_subscription, KeyboardBacklightRequest, KeyboardBacklightUpdate,
|
||||
};
|
||||
use cosmic::applet::cosmic_panel_config::PanelAnchor;
|
||||
use cosmic::applet::token::subscription::{
|
||||
activation_token_subscription, TokenRequest, TokenUpdate,
|
||||
};
|
||||
|
|
@ -21,14 +23,19 @@ use cosmic::iced::{
|
|||
widget::{column, container, row, slider, text},
|
||||
window, Alignment, Length, Subscription,
|
||||
};
|
||||
use cosmic::iced_core::alignment::Vertical;
|
||||
use cosmic::iced_core::{Background, Color};
|
||||
use cosmic::iced_runtime::core::layout::Limits;
|
||||
use cosmic::iced_style::application;
|
||||
use cosmic::widget::{divider, horizontal_space, icon};
|
||||
use cosmic::iced_widget::{Column, Row};
|
||||
use cosmic::widget::{divider, horizontal_space, icon, scrollable, vertical_space};
|
||||
use cosmic::Command;
|
||||
use cosmic::{Element, Theme};
|
||||
use cosmic_time::{anim, chain, id, once_cell::sync::Lazy, Instant, Timeline};
|
||||
|
||||
use log::error;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
|
|
@ -49,11 +56,18 @@ fn format_duration(duration: Duration) -> String {
|
|||
}
|
||||
|
||||
pub fn run() -> cosmic::iced::Result {
|
||||
cosmic::applet::run::<CosmicBatteryApplet>(false, ())
|
||||
cosmic::applet::run::<CosmicBatteryApplet>(true, ())
|
||||
}
|
||||
|
||||
static MAX_CHARGE: Lazy<id::Toggler> = Lazy::new(id::Toggler::unique);
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct GPUData {
|
||||
name: String,
|
||||
toggled: bool,
|
||||
app_list: Option<Vec<App>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct CosmicBatteryApplet {
|
||||
core: cosmic::app::Core,
|
||||
|
|
@ -62,6 +76,7 @@ struct CosmicBatteryApplet {
|
|||
charging_limit: bool,
|
||||
battery_percent: f64,
|
||||
on_battery: bool,
|
||||
gpus: HashMap<PathBuf, GPUData>,
|
||||
time_remaining: Duration,
|
||||
kbd_brightness: f64,
|
||||
screen_brightness: f64,
|
||||
|
|
@ -144,6 +159,9 @@ enum Message {
|
|||
UpdateScreenBrightness(f64),
|
||||
InitKbdBacklight(UnboundedSender<KeyboardBacklightRequest>, f64),
|
||||
InitScreenBacklight(UnboundedSender<ScreenBacklightRequest>, f64),
|
||||
GpuOn(PathBuf, String, Option<Vec<App>>),
|
||||
GpuOff(PathBuf),
|
||||
ToggleGpuApps(PathBuf),
|
||||
Errored(String),
|
||||
InitProfile(UnboundedSender<PowerProfileRequest>, Power),
|
||||
Profile(Power),
|
||||
|
|
@ -319,16 +337,67 @@ impl cosmic::Application for CosmicBatteryApplet {
|
|||
cosmic::process::spawn(cmd);
|
||||
}
|
||||
},
|
||||
Message::GpuOn(path, name, app_list) => {
|
||||
let toggled = self
|
||||
.gpus
|
||||
.get(&path)
|
||||
.map(|data| data.toggled)
|
||||
.unwrap_or_default();
|
||||
self.gpus.insert(
|
||||
path,
|
||||
GPUData {
|
||||
name,
|
||||
app_list,
|
||||
toggled,
|
||||
},
|
||||
);
|
||||
}
|
||||
Message::GpuOff(path) => {
|
||||
self.gpus.remove(&path);
|
||||
}
|
||||
Message::ToggleGpuApps(path) => {
|
||||
if let Some(data) = self.gpus.get_mut(&path) {
|
||||
data.toggled = !data.toggled;
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
self.core
|
||||
let btn = self
|
||||
.core
|
||||
.applet
|
||||
.icon_button(&self.icon_name)
|
||||
.on_press(Message::TogglePopup)
|
||||
.into()
|
||||
.into();
|
||||
|
||||
if !self.gpus.is_empty() {
|
||||
let dot = container(vertical_space(Length::Fixed(0.0)))
|
||||
.padding(2.0)
|
||||
.style(<Theme as container::StyleSheet>::Style::Custom(Box::new(
|
||||
|theme| container::Appearance {
|
||||
text_color: Some(Color::TRANSPARENT),
|
||||
background: Some(Background::Color(theme.cosmic().accent_color().into())),
|
||||
border_radius: 2.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
icon_color: Some(Color::TRANSPARENT),
|
||||
},
|
||||
)))
|
||||
.into();
|
||||
|
||||
match self.core.applet.anchor {
|
||||
PanelAnchor::Left | PanelAnchor::Right => Column::with_children(vec![btn, dot])
|
||||
.align_items(Alignment::Center)
|
||||
.into(),
|
||||
PanelAnchor::Top | PanelAnchor::Bottom => Row::with_children(vec![btn, dot])
|
||||
.align_items(Alignment::Center)
|
||||
.into(),
|
||||
}
|
||||
} else {
|
||||
btn
|
||||
}
|
||||
}
|
||||
|
||||
fn view_window(&self, _id: window::Id) -> Element<Message> {
|
||||
|
|
@ -344,133 +413,241 @@ impl cosmic::Application for CosmicBatteryApplet {
|
|||
)
|
||||
})
|
||||
.size(10);
|
||||
|
||||
let mut content = vec![
|
||||
padded_control(
|
||||
row![
|
||||
icon::from_name(&*self.icon_name).size(24).symbolic(true),
|
||||
column![name, description]
|
||||
]
|
||||
.spacing(8)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.into(),
|
||||
padded_control(divider::horizontal::default()).into(),
|
||||
menu_button(
|
||||
row![
|
||||
column![
|
||||
text(fl!("battery")).size(14),
|
||||
text(fl!("battery-desc")).size(10)
|
||||
]
|
||||
.width(Length::Fill),
|
||||
if matches!(self.power_profile, Power::Battery) {
|
||||
container(
|
||||
icon::from_name("emblem-ok-symbolic")
|
||||
.size(12)
|
||||
.symbolic(true),
|
||||
)
|
||||
} else {
|
||||
container(horizontal_space(1.0))
|
||||
}
|
||||
]
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.on_press(Message::SelectProfile(Power::Battery))
|
||||
.into(),
|
||||
menu_button(
|
||||
row![
|
||||
column![
|
||||
text(fl!("balanced")).size(14),
|
||||
text(fl!("balanced-desc")).size(10)
|
||||
]
|
||||
.width(Length::Fill),
|
||||
if matches!(self.power_profile, Power::Balanced) {
|
||||
container(
|
||||
icon::from_name("emblem-ok-symbolic")
|
||||
.size(12)
|
||||
.symbolic(true),
|
||||
)
|
||||
} else {
|
||||
container(horizontal_space(1.0))
|
||||
}
|
||||
]
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.on_press(Message::SelectProfile(Power::Balanced))
|
||||
.into(),
|
||||
menu_button(
|
||||
row![
|
||||
column![
|
||||
text(fl!("performance")).size(14),
|
||||
text(fl!("performance-desc")).size(10)
|
||||
]
|
||||
.width(Length::Fill),
|
||||
if matches!(self.power_profile, Power::Performance) {
|
||||
container(
|
||||
icon::from_name("emblem-ok-symbolic")
|
||||
.size(12)
|
||||
.symbolic(true),
|
||||
)
|
||||
} else {
|
||||
container(horizontal_space(1.0))
|
||||
}
|
||||
]
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.on_press(Message::SelectProfile(Power::Performance))
|
||||
.into(),
|
||||
padded_control(divider::horizontal::default()).into(),
|
||||
padded_control(
|
||||
anim!(
|
||||
//toggler
|
||||
MAX_CHARGE,
|
||||
&self.timeline,
|
||||
fl!("max-charge"),
|
||||
self.charging_limit,
|
||||
Message::SetChargingLimit,
|
||||
)
|
||||
.text_size(14)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.into(),
|
||||
padded_control(divider::horizontal::default()).into(),
|
||||
padded_control(
|
||||
row![
|
||||
icon::from_name(self.display_icon_name.as_str())
|
||||
.size(24)
|
||||
.symbolic(true),
|
||||
slider(
|
||||
1..=100,
|
||||
(self.screen_brightness * 100.0) as i32,
|
||||
Message::SetScreenBrightness
|
||||
),
|
||||
text(format!("{:.0}%", self.screen_brightness * 100.0))
|
||||
.size(16)
|
||||
.width(Length::Fixed(40.0))
|
||||
.horizontal_alignment(Horizontal::Right)
|
||||
]
|
||||
.spacing(12),
|
||||
)
|
||||
.into(),
|
||||
padded_control(
|
||||
row![
|
||||
icon::from_name("keyboard-brightness-symbolic")
|
||||
.size(24)
|
||||
.symbolic(true),
|
||||
slider(
|
||||
0..=100,
|
||||
(self.kbd_brightness * 100.0) as i32,
|
||||
Message::SetKbdBrightness
|
||||
),
|
||||
text(format!("{:.0}%", self.kbd_brightness * 100.0))
|
||||
.size(16)
|
||||
.width(Length::Fixed(40.0))
|
||||
.horizontal_alignment(Horizontal::Right)
|
||||
]
|
||||
.spacing(12),
|
||||
)
|
||||
.into(),
|
||||
padded_control(divider::horizontal::default()).into(),
|
||||
];
|
||||
|
||||
if !self.gpus.is_empty() {
|
||||
content.push(
|
||||
padded_control(
|
||||
row![
|
||||
text(fl!("dgpu-running"))
|
||||
.size(16)
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(Horizontal::Left),
|
||||
container(vertical_space(Length::Fixed(0.0)))
|
||||
.padding(4)
|
||||
.style(<Theme as container::StyleSheet>::Style::Custom(Box::new(
|
||||
|theme| container::Appearance {
|
||||
text_color: Some(Color::TRANSPARENT),
|
||||
background: Some(Background::Color(
|
||||
theme.cosmic().accent_color().into(),
|
||||
)),
|
||||
border_radius: 4.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
icon_color: Some(Color::TRANSPARENT),
|
||||
},
|
||||
))),
|
||||
]
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
content.push(padded_control(divider::horizontal::default()).into());
|
||||
}
|
||||
|
||||
for (key, gpu) in &self.gpus {
|
||||
if gpu.app_list.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
content.push(
|
||||
menu_button(row![
|
||||
text(fl!(
|
||||
"dgpu-applications",
|
||||
gpu_name = if self.gpus.len() == 1 {
|
||||
String::new()
|
||||
} else {
|
||||
format!("\"{}\"", gpu.name)
|
||||
}
|
||||
))
|
||||
.size(14)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(24.0))
|
||||
.vertical_alignment(Vertical::Center),
|
||||
container(
|
||||
icon::from_name(if gpu.toggled {
|
||||
"go-down-symbolic"
|
||||
} else {
|
||||
"go-up-symbolic"
|
||||
})
|
||||
.size(14)
|
||||
.symbolic(true)
|
||||
)
|
||||
.align_x(Horizontal::Center)
|
||||
.align_y(Vertical::Center)
|
||||
.width(Length::Fixed(24.0))
|
||||
.height(Length::Fixed(24.0)),
|
||||
])
|
||||
.on_press(Message::ToggleGpuApps(key.clone()))
|
||||
.into(),
|
||||
);
|
||||
|
||||
if gpu.toggled {
|
||||
let app_list = gpu.app_list.as_ref().unwrap();
|
||||
let mut list_apps = Vec::with_capacity(app_list.len());
|
||||
for app in app_list {
|
||||
list_apps.push(
|
||||
padded_control(
|
||||
row![
|
||||
if let Some(icon) = &app.icon {
|
||||
container(icon::from_name(&**icon).size(12).symbolic(true))
|
||||
} else {
|
||||
container(horizontal_space(12.0))
|
||||
},
|
||||
column![text(&app.name).size(14), text(&app.secondary).size(10)]
|
||||
.width(Length::Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
content.push(
|
||||
scrollable(Column::with_children(list_apps))
|
||||
.height(Length::Fixed(300.0))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
content.push(padded_control(divider::horizontal::default()).into());
|
||||
}
|
||||
|
||||
content.push(
|
||||
menu_button(text(fl!("power-settings")).size(14).width(Length::Fill))
|
||||
.on_press(Message::OpenSettings)
|
||||
.into(),
|
||||
);
|
||||
|
||||
self.core
|
||||
.applet
|
||||
.popup_container(
|
||||
column![
|
||||
padded_control(
|
||||
row![
|
||||
icon::from_name(&*self.icon_name).size(24).symbolic(true),
|
||||
column![name, description]
|
||||
]
|
||||
.spacing(8)
|
||||
.align_items(Alignment::Center)
|
||||
),
|
||||
padded_control(divider::horizontal::default()),
|
||||
menu_button(
|
||||
row![
|
||||
column![
|
||||
text(fl!("battery")).size(14),
|
||||
text(fl!("battery-desc")).size(10)
|
||||
]
|
||||
.width(Length::Fill),
|
||||
if matches!(self.power_profile, Power::Battery) {
|
||||
container(
|
||||
icon::from_name("emblem-ok-symbolic")
|
||||
.size(12)
|
||||
.symbolic(true),
|
||||
)
|
||||
} else {
|
||||
container(horizontal_space(1.0))
|
||||
}
|
||||
]
|
||||
.align_items(Alignment::Center)
|
||||
)
|
||||
.on_press(Message::SelectProfile(Power::Battery)),
|
||||
menu_button(
|
||||
row![
|
||||
column![
|
||||
text(fl!("balanced")).size(14),
|
||||
text(fl!("balanced-desc")).size(10)
|
||||
]
|
||||
.width(Length::Fill),
|
||||
if matches!(self.power_profile, Power::Balanced) {
|
||||
container(
|
||||
icon::from_name("emblem-ok-symbolic")
|
||||
.size(12)
|
||||
.symbolic(true),
|
||||
)
|
||||
} else {
|
||||
container(horizontal_space(1.0))
|
||||
}
|
||||
]
|
||||
.align_items(Alignment::Center)
|
||||
)
|
||||
.on_press(Message::SelectProfile(Power::Balanced)),
|
||||
menu_button(
|
||||
row![
|
||||
column![
|
||||
text(fl!("performance")).size(14),
|
||||
text(fl!("performance-desc")).size(10)
|
||||
]
|
||||
.width(Length::Fill),
|
||||
if matches!(self.power_profile, Power::Performance) {
|
||||
container(
|
||||
icon::from_name("emblem-ok-symbolic")
|
||||
.size(12)
|
||||
.symbolic(true),
|
||||
)
|
||||
} else {
|
||||
container(horizontal_space(1.0))
|
||||
}
|
||||
]
|
||||
.align_items(Alignment::Center)
|
||||
)
|
||||
.on_press(Message::SelectProfile(Power::Performance)),
|
||||
padded_control(divider::horizontal::default()),
|
||||
padded_control(
|
||||
anim!(
|
||||
//toggler
|
||||
MAX_CHARGE,
|
||||
&self.timeline,
|
||||
fl!("max-charge"),
|
||||
self.charging_limit,
|
||||
Message::SetChargingLimit,
|
||||
)
|
||||
.text_size(14)
|
||||
.width(Length::Fill)
|
||||
),
|
||||
padded_control(divider::horizontal::default()),
|
||||
padded_control(
|
||||
row![
|
||||
icon::from_name(self.display_icon_name.as_str())
|
||||
.size(24)
|
||||
.symbolic(true),
|
||||
slider(
|
||||
1..=100,
|
||||
(self.screen_brightness * 100.0) as i32,
|
||||
Message::SetScreenBrightness
|
||||
),
|
||||
text(format!("{:.0}%", self.screen_brightness * 100.0))
|
||||
.size(16)
|
||||
.width(Length::Fixed(40.0))
|
||||
.horizontal_alignment(Horizontal::Right)
|
||||
]
|
||||
.spacing(12)
|
||||
),
|
||||
padded_control(
|
||||
row![
|
||||
icon::from_name("keyboard-brightness-symbolic")
|
||||
.size(24)
|
||||
.symbolic(true),
|
||||
slider(
|
||||
0..=100,
|
||||
(self.kbd_brightness * 100.0) as i32,
|
||||
Message::SetKbdBrightness
|
||||
),
|
||||
text(format!("{:.0}%", self.kbd_brightness * 100.0))
|
||||
.size(16)
|
||||
.width(Length::Fixed(40.0))
|
||||
.horizontal_alignment(Horizontal::Right)
|
||||
]
|
||||
.spacing(12)
|
||||
),
|
||||
padded_control(divider::horizontal::default()),
|
||||
menu_button(text(fl!("power-settings")).size(14).width(Length::Fill))
|
||||
.on_press(Message::OpenSettings)
|
||||
]
|
||||
.padding([8, 0]),
|
||||
)
|
||||
.popup_container(Column::with_children(content).padding([8, 0]))
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
@ -500,6 +677,10 @@ impl cosmic::Application for CosmicBatteryApplet {
|
|||
PowerProfileUpdate::Init(tx, p) => Message::InitProfile(p, tx),
|
||||
PowerProfileUpdate::Error(e) => Message::Errored(e), // TODO: handle error
|
||||
}),
|
||||
dgpu_subscription(0).map(|event| match event {
|
||||
GpuUpdate::On(path, name, list) => Message::GpuOn(path, name, list),
|
||||
GpuUpdate::Off(path) => Message::GpuOff(path),
|
||||
}),
|
||||
self.timeline
|
||||
.as_subscription()
|
||||
.map(|(_, now)| Message::Frame(now)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue