Improve GUI

This commit is contained in:
Adrian Woźniak 2022-08-24 17:00:30 +02:00
parent 8d8f4932c1
commit a795200245
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
16 changed files with 259 additions and 110 deletions

67
Cargo.lock generated
View File

@ -160,14 +160,14 @@ dependencies = [
"glutin 0.29.1",
"gumdrop",
"image",
"log",
"nix 0.25.0",
"parking_lot 0.12.1",
"pretty_env_logger",
"serde",
"thiserror",
"tokio",
"toml",
"tracing",
"tracing-subscriber",
"vulkano",
"vulkano-shaders",
"vulkano-win",
@ -213,11 +213,11 @@ version = "0.1.0"
dependencies = [
"amdgpu",
"futures",
"log",
"pretty_env_logger",
"ron 0.7.1",
"serde",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
@ -249,6 +249,15 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "arboard"
version = "2.1.1"
@ -2509,6 +2518,15 @@ dependencies = [
"libc",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "shared_library"
version = "0.1.9"
@ -2698,6 +2716,15 @@ dependencies = [
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"once_cell",
]
[[package]]
name = "threadpool"
version = "1.8.1"
@ -2840,6 +2867,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b"
dependencies = [
"ansi_term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
@ -2906,6 +2959,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vec_map"
version = "0.8.2"

View File

@ -127,6 +127,7 @@ impl OutputType {
Some(match s {
"DP" => Self::DisplayPort,
"eDP" => Self::MiniDisplayPort,
"DVI" => Self::Dvi,
"HDMI" => Self::Hdmi,
_ => return None,
})
@ -174,6 +175,7 @@ pub struct Output {
pub status: Status,
#[serde(rename = "M")]
pub modes: Vec<OutputMode>,
pub display_power_managment: bool,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]

View File

@ -37,8 +37,8 @@ toml = { version = "0.5" }
thiserror = { version = "1.0" }
gumdrop = { version = "0.8" }
log = { version = "0.4" }
pretty_env_logger = { version = "0.4" }
tracing = { version = "0.1.36" }
tracing-subscriber = { version = "0.3.15" }
egui = { version = "0.18", optional = true, features = [] }
epaint = { version = "0.18", features = [], optional = true }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -41,6 +41,14 @@ impl FanService {
pub struct FanServices(pub Vec<FanService>);
impl std::ops::Deref for FanServices {
type Target = Vec<FanService>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FanServices {
pub fn list_changed(&self, other: &[Pid]) -> bool {
if self.0.len() != other.len() {
@ -108,8 +116,8 @@ impl StatefulConfig {
// 80x80
let image = {
let bytes = include_bytes!("../assets/icons/ports2.jpg");
image::load_from_memory_with_format(bytes, ImageFormat::Jpeg).unwrap()
let bytes = include_bytes!("../assets/icons/ports2.png");
image::load_from_memory_with_format(bytes, ImageFormat::Png).unwrap()
};
let ctx = ui.ctx();
@ -139,8 +147,8 @@ impl StatefulConfig {
pub struct AmdGui {
pub page: Page,
pid_files: FanServices,
outputs: BTreeMap<String, Vec<Output>>,
pid_files: SocketState<FanServices>,
outputs: SocketState<BTreeMap<String, Vec<Output>>>,
cooling_performance: CoolingPerformance,
change_fan_settings: ChangeFanSettings,
outputs_settings: OutputsSettings,
@ -160,13 +168,13 @@ impl AmdGui {
pub fn new_with_config(config: FanConfig) -> Self {
Self {
page: Default::default(),
pid_files: FanServices::from(vec![]),
outputs: Default::default(),
pid_files: SocketState::NotAvailable,
outputs: SocketState::NotAvailable,
cooling_performance: CoolingPerformance::new(100, config.clone()),
change_fan_settings: ChangeFanSettings::new(config.clone()),
outputs_settings: OutputsSettings::default(),
config: StatefulConfig::new(config),
reload_pid_list_delay: RELOAD_PID_LIST_DELAY,
reload_pid_list_delay: 0,
}
}
@ -175,65 +183,91 @@ impl AmdGui {
match self.page {
Page::Config => {
self.change_fan_settings
.draw(ui, &mut self.pid_files, &mut self.config);
if let SocketState::Connected(pid_files) = &mut self.pid_files {
self.change_fan_settings
.draw(ui, pid_files, &mut self.config);
} else {
ui.label("Not available");
}
}
Page::Monitoring => {
self.cooling_performance.draw(ui, &self.pid_files);
if let SocketState::Connected(pid_files) = &mut self.pid_files {
self.cooling_performance.draw(ui, pid_files);
} else {
ui.label("Not available");
}
}
Page::Settings => {}
Page::Outputs => {
self.outputs_settings
.draw(ui, &mut self.config, &self.outputs);
if let SocketState::Connected(outputs) = &self.outputs {
self.outputs_settings.draw(ui, &mut self.config, outputs);
} else {
ui.label("Not available");
}
}
}
}
pub fn tick(&mut self) {
self.cooling_performance.tick();
if self.pid_files.0.is_empty() || self.reload_pid_list_delay.checked_sub(1).is_none() {
self.reload_pid_list_delay = RELOAD_PID_LIST_DELAY;
let can_decrease = self.reload_pid_list_delay > 0;
{
use amdgpu::pidfile::helper_cmd::{send_command, Command, Response};
if can_decrease {
self.reload_pid_list_delay -= 1;
return;
}
match send_command(Command::FanServices) {
Ok(Response::Services(services)) if self.pid_files.list_changed(&services) => {
self.pid_files = FanServices::from(services);
}
Ok(Response::Services(_services)) => {
// SKIP
}
Ok(res) => {
log::warn!("Unexpected response {:?} while loading fan services", res);
}
Err(e) => {
log::warn!("Failed to load amd fan services pid list. {:?}", e);
}
self.reload_pid_list_delay = RELOAD_PID_LIST_DELAY;
{
use amdgpu::pidfile::helper_cmd::{send_command, Command, Response};
match send_command(Command::FanServices) {
Ok(Response::Services(services))
if self
.pid_files
.connected()
.map(|c| c.list_changed(&services))
.unwrap_or(true) =>
{
self.pid_files = SocketState::Connected(FanServices::from(services));
}
Ok(Response::Services(_services)) => {
// SKIP
}
Ok(res) => {
tracing::warn!("Unexpected response {:?} while loading fan services", res);
}
Err(e) => {
self.pid_files = SocketState::NotAvailable;
tracing::warn!("Failed to load amd fan services pid list. {:?}", e);
}
}
}
{
use amdgpu::pidfile::ports::{send_command, Command, Response};
{
use amdgpu::pidfile::ports::{send_command, Command, Response};
match send_command(Command::Ports) {
Ok(Response::NoOp) => {}
Ok(Response::Ports(outputs)) => {
let mut names = outputs.iter().fold(
Vec::with_capacity(outputs.len()),
|mut set, output| {
set.push(output.card.clone());
set
},
);
names.sort();
match send_command(Command::Ports) {
Ok(Response::NoOp) => {}
Ok(Response::Ports(outputs)) => {
let mut names = outputs.iter().fold(
Vec::with_capacity(outputs.len()),
|mut set, output| {
set.push(output.card.clone());
set
},
);
names.sort();
let mut tree = BTreeMap::new();
names.into_iter().for_each(|name| {
tree.insert(name, Vec::with_capacity(6));
});
let mut tree = BTreeMap::new();
names.into_iter().for_each(|name| {
tree.insert(name, Vec::with_capacity(6));
});
self.outputs = outputs.into_iter().fold(tree, |mut agg, output| {
self.outputs = SocketState::Connected(outputs.into_iter().fold(
tree,
|mut agg, output| {
let v = agg
.entry(output.card.clone())
.or_insert_with(|| Vec::with_capacity(6));
@ -253,15 +287,31 @@ impl AmdGui {
))
});
agg
});
}
Err(e) => {
log::warn!("Failed to load amd fan services pid list. {:?}", e);
},
));
}
Err(e) => {
if matches!(self.page, Page::Outputs) {
self.page = Page::Config;
}
self.outputs = SocketState::NotAvailable;
tracing::warn!("Failed to load amd fan services pid list. {:?}", e);
}
}
} else {
self.reload_pid_list_delay -= 1;
}
}
}
pub enum SocketState<Content> {
NotAvailable,
Connected(Content),
}
impl<C> SocketState<C> {
pub fn connected(&self) -> Option<&C> {
match self {
Self::Connected(c) => Some(c),
_ => None,
}
}
}

View File

@ -39,7 +39,7 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, mut receiver: UnboundedReceiver<bool
loop {
if receiver.recv().await.is_some() {
if let Err(e) = proxy.send_event(()) {
log::error!("{:?}", e);
tracing::error!("{:?}", e);
}
}
}

View File

@ -53,7 +53,7 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, mut receiver: UnboundedReceiver<bool
loop {
if receiver.recv().await.is_some() {
if let Err(e) = proxy.send_event(()) {
log::error!("{:?}", e);
tracing::error!("{:?}", e);
}
}
}

View File

@ -23,14 +23,7 @@ use crate::AmdGui;
pub fn create_ui(amd_gui: Arc<Mutex<AmdGui>>, ctx: &egui::Context) {
egui::containers::TopBottomPanel::new(TopBottomSide::Top, "menu").show(ctx, |ui| {
let mut child = ui.child_ui(ui.available_rect_before_wrap(), Layout::left_to_right());
if child
.add(
egui::Button::new("Outputs"), /* .text_style(TextStyle::Heading) */
)
.clicked_by(PointerButton::Primary)
{
amd_gui.lock().page = Page::Outputs;
}
if child
.add(
egui::Button::new("Config"), /* .text_style(TextStyle::Heading) */
@ -47,6 +40,14 @@ pub fn create_ui(amd_gui: Arc<Mutex<AmdGui>>, ctx: &egui::Context) {
{
amd_gui.lock().page = Page::Monitoring;
}
if child
.add(
egui::Button::new("Outputs"), /* .text_style(TextStyle::Heading) */
)
.clicked_by(PointerButton::Primary)
{
amd_gui.lock().page = Page::Outputs;
}
if child
.add(
egui::Button::new("Settings"), /* .text_style(TextStyle::Heading) */

View File

@ -24,7 +24,7 @@ use vulkano::{swapchain, sync};
use vulkano_win::VkSurfaceBuild;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Fullscreen, Window, WindowBuilder};
use winit::window::{Window, WindowBuilder};
use crate::app::AmdGui;
use crate::backend::create_ui;
@ -66,14 +66,19 @@ impl<F: GpuFuture> AsMut<dyn GpuFuture> for FrameEndFuture<F> {
pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>) {
let required_extensions = vulkano_win::required_extensions();
let instance = Instance::new(InstanceCreateInfo {
application_name: Some("amdguid".into()),
enabled_extensions: required_extensions,
..Default::default()
})
.unwrap();
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
let physical = {
let mut v = PhysicalDevice::enumerate(&instance).collect::<Vec<_>>();
v.sort_by(|a, b| a.api_version().cmp(&b.api_version()));
v.remove(0)
};
println!(
tracing::info!(
"Using device: {} (type: {:?})",
physical.properties().device_name,
physical.properties().device_type,
@ -82,7 +87,7 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
let event_loop = EventLoop::new();
let surface = WindowBuilder::new()
.with_title("AMD GUID")
.with_fullscreen(Some(Fullscreen::Borderless(None)))
// .with_fullscreen(Some(Fullscreen::Borderless(None)))
.build_vk_surface(&event_loop, instance.clone())
.unwrap();

View File

@ -16,7 +16,7 @@ async fn main() {
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "DEBUG");
}
pretty_env_logger::init();
tracing_subscriber::fmt::init();
let config = Arc::new(Mutex::new(
amdgpu_config::fan::load_config(amdgpu_config::fan::DEFAULT_FAN_CONFIG_PATH)
.expect("No FAN config"),
@ -35,7 +35,7 @@ fn schedule_tick(amd_gui: std::sync::Arc<parking_lot::Mutex<AmdGui>>) -> Unbound
loop {
amd_gui.lock().tick();
if let Err(e) = sender.send(true) {
log::error!("Failed to propagate tick update. {:?}", e);
tracing::error!("Failed to propagate tick update. {:?}", e);
}
tokio::time::sleep(tokio::time::Duration::from_millis(166)).await;
}

View File

@ -158,7 +158,7 @@ impl ChangeFanSettings {
let c: &amdgpu_config::fan::Config = &*config;
let content = match toml::to_string(c) {
Err(e) => {
log::error!("Config file serialization failed. {:?}", e);
tracing::error!("Config file serialization failed. {:?}", e);
return;
}
Ok(content) => content,

View File

@ -23,35 +23,10 @@ impl OutputsSettings {
ui.horizontal_top(|ui| {
outputs.iter().for_each(|(name, outputs)| {
ui.vertical(|ui| {
ui.label(format!("name {name}"));
ui.label(format!("Card {name}"));
ui.horizontal_top(|ui| {
outputs.iter().for_each(|output| {
ui.vertical(|ui| {
ui.add(OutputWidget::new(output, state));
ui.label(format!("port_number {}", output.port_number));
ui.label(format!("port_type {:?}", output.port_type));
ui.label(format!("card {}", output.card));
ui.label(format!(
"port_name {}",
output.port_name.as_deref().unwrap_or_default()
));
let state = WidgetText::RichText(
RichText::new(match output.status {
Status::Connected => "Connected",
Status::Disconnected => "Disconnected",
})
.color(
match output.status {
Status::Connected => Color32::DARK_GREEN,
Status::Disconnected => Color32::GRAY,
},
),
);
ui.label(state);
});
Self::render_single(ui, state, output);
});
});
});
@ -59,4 +34,49 @@ impl OutputsSettings {
});
});
}
fn render_single(ui: &mut Ui, state: &mut StatefulConfig, output: &Output) {
ui.vertical(|ui| {
ui.add(OutputWidget::new(output, state));
ui.label(format!("Port type {:?}", output.port_type));
ui.label(format!("Port number {}", output.port_number));
if let Some(name) = output.port_name.as_deref() {
ui.label(format!("Port name {}", name));
}
ui.label(WidgetText::RichText(
RichText::new(match output.status {
Status::Connected => "Connected",
Status::Disconnected => "Disconnected",
})
.color(match output.status {
Status::Connected => Color32::GREEN,
Status::Disconnected => Color32::GRAY,
})
.code()
.strong()
.monospace(),
));
ui.label("Display Power Management");
if ui
.button(WidgetText::RichText(
RichText::new(match output.display_power_managment {
true => "On",
false => "Off",
})
.color(match output.display_power_managment {
true => Color32::GREEN,
false => Color32::GRAY,
})
.monospace()
.code()
.strong(),
))
.clicked()
{
//
}
});
}
}

View File

@ -23,11 +23,11 @@ impl<'l> egui::Widget for ReloadSection<'l> {
}) {
Ok(response) => {
service.reload = ChangeState::Success;
log::info!("{:?}", response)
tracing::info!("{:?}", response)
}
Err(e) => {
service.reload = ChangeState::Failure(format!("{:?}", e));
log::error!("Failed to reload config. {:?}", e)
tracing::error!("Failed to reload config. {:?}", e)
}
}
}

View File

@ -11,5 +11,5 @@ futures = { version = "0.3", features = [] }
ron = { version = "0.7.1" }
serde = { version = "1.0.137", features = ["derive"] }
log = { version = "0.4" }
pretty_env_logger = { version = "0.4" }
tracing = { version = "0.1.36" }
tracing-subscriber = { version = "0.3.15" }

View File

@ -12,6 +12,7 @@ fn parse_output(entry: DirEntry) -> Option<Output> {
if ty.is_dir() {
return None;
}
tracing::error!("{:?}", entry.path());
let file_name = entry.file_name();
let path = file_name.to_str()?;
let mut it = path
@ -33,11 +34,20 @@ fn parse_output(entry: DirEntry) -> Option<Output> {
let card = it.next()?.strip_prefix("card")?.to_string();
let port_type = it.next()?;
let dpms = std::fs::read_to_string(entry.path().join("dpms"))
.unwrap_or_else(|e| {
tracing::error!("{}", e);
"Off".into()
})
.to_lowercase();
tracing::info!("Display Power Management System is {:?}", dpms);
let mut output = Output {
card,
ty: OutputType::parse_str(&port_type),
port_type,
modes,
display_power_managment: dpms.trim() == "on",
..Default::default()
};
let mut it = it.rev();
@ -90,7 +100,7 @@ async fn service(state: Arc<Mutex<Vec<Output>>>) {
.expect("Creating pid file for ports failed")
};
if let Err(e) = std::fs::set_permissions(&sock_path, Permissions::from_mode(0o777)) {
log::error!("Failed to change gui helper socket file mode. {:?}", e);
tracing::error!("Failed to change gui helper socket file mode. {:?}", e);
}
while let Ok((stream, _addr)) = listener.accept() {
@ -106,11 +116,11 @@ fn handle_connection(stream: UnixStream, state: Arc<Mutex<Vec<Output>>>) {
_ => return service.kill(),
};
log::info!("Incoming {:?}", command);
tracing::info!("Incoming {:?}", command);
let cmd = match ron::from_str::<Command>(command.trim()) {
Ok(cmd) => cmd,
Err(e) => {
log::warn!("Invalid message {:?}. {:?}", command, e);
tracing::warn!("Invalid message {:?}. {:?}", command, e);
return service.kill();
}
};
@ -128,6 +138,8 @@ fn handle_command(mut service: Service, cmd: Command, state: Arc<Mutex<Vec<Outpu
}
fn main() {
tracing_subscriber::fmt::init();
let executor = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()