Update dependencies

This commit is contained in:
Adrian Woźniak 2022-08-17 21:03:26 +02:00
parent b86302534f
commit 2b1872ae35
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
54 changed files with 2201 additions and 1196 deletions

1803
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,20 @@
use std::io;
use std::path::PathBuf;
use std::time::{Duration, Instant};
use amdgpu_config::fan::{MatrixPoint, DEFAULT_FAN_CONFIG_PATH};
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode};
use crossterm::execute;
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use gumdrop::Options;
use std::path::PathBuf;
use std::time::Instant;
use std::{io, time::Duration};
use tui::backend::Backend;
use tui::backend::{Backend, CrosstermBackend};
use tui::layout::*;
use tui::style::{Color, Modifier, Style};
use tui::symbols::Marker;
use tui::{backend::CrosstermBackend, layout::*, widgets::*, Frame, Terminal};
use tui::widgets::*;
use tui::{Frame, Terminal};
#[derive(Options)]
struct Opts {

View File

@ -1,7 +1,6 @@
use gumdrop::Options;
use amdgpu::utils::hw_mons;
use amdgpu_config::fan::Config;
use gumdrop::Options;
use crate::command::Fan;
use crate::{AmdFanError, FanMode};

View File

@ -1,5 +1,3 @@
use gumdrop::Options;
use amdgpu::hw_mon::HwMon;
use amdgpu::utils::{linear_map, load_temp_inputs};
use amdgpu::{
@ -7,6 +5,7 @@ use amdgpu::{
PULSE_WIDTH_MODULATION_MIN, PULSE_WIDTH_MODULATION_MODE,
};
use amdgpu_config::fan::Config;
use gumdrop::Options;
use crate::{change_mode, service};
@ -90,7 +89,8 @@ impl Fan {
v.into_iter().map(|hw| Self::wrap(hw, config)).collect()
}
/// Change fan speed to given value if it's between minimal and maximal value
/// Change fan speed to given value if it's between minimal and maximal
/// value
pub fn set_speed(&mut self, speed: f64) -> crate::Result<()> {
let min = self.pwm_min() as f64;
let max = self.pwm_max() as f64;
@ -99,15 +99,16 @@ impl Fan {
Ok(())
}
/// Change gpu fan speed management to manual (amdfand will manage speed) instead of
/// GPU embedded manager
/// Change gpu fan speed management to manual (amdfand will manage speed)
/// instead of GPU embedded manager
pub fn write_manual(&self) -> crate::Result<()> {
self.hw_mon_write(MODULATION_ENABLED_FILE, 1)
.map_err(FanError::ManualSpeedFailed)?;
Ok(())
}
/// Change gpu fan speed management to automatic, speed will be managed by GPU embedded manager
/// Change gpu fan speed management to automatic, speed will be managed by
/// GPU embedded manager
pub fn write_automatic(&self) -> crate::Result<()> {
self.hw_mon_write("pwm1_enable", 2)
.map_err(FanError::AutomaticSpeedFailed)?;
@ -132,8 +133,8 @@ impl Fan {
}
/// Get maximal GPU temperature from all inputs.
/// This is not recommended since GPU can heat differently in different parts and usually only
/// temp1 should be taken for consideration.
/// This is not recommended since GPU can heat differently in different
/// parts and usually only temp1 should be taken for consideration.
pub fn max_gpu_temp(&self) -> crate::Result<f64> {
if let Some(input) = self.temp_input.as_ref() {
let value = self.read_gpu_temp(&input.as_string())?;

View File

@ -1,12 +1,9 @@
extern crate log;
use gumdrop::Options;
use amdgpu::{
lock_file::PidLock,
utils::{ensure_config_dir, hw_mons},
};
use amdgpu::lock_file::PidLock;
use amdgpu::utils::{ensure_config_dir, hw_mons};
use amdgpu_config::fan::{load_config, Config, DEFAULT_FAN_CONFIG_PATH};
use gumdrop::Options;
use crate::command::FanCommand;
use crate::error::AmdFanError;

View File

@ -1,13 +1,13 @@
use gumdrop::Options;
use amdgpu::utils::hw_mons;
use amdgpu::{config_reloaded, is_reload_required, listen_unix_signal};
use amdgpu_config::fan::Config;
use gumdrop::Options;
use crate::command::Fan;
use crate::AmdFanError;
/// Start service which will change fan speed according to config and GPU temperature
/// Start service which will change fan speed according to config and GPU
/// temperature
pub fn run(mut config: Config) -> crate::Result<()> {
listen_unix_signal();

View File

@ -28,7 +28,8 @@ impl MatrixPoint {
pub struct Config {
#[serde(skip)]
path: String,
/// One of temperature inputs /sys/class/drm/card{X}/device/hwmon/hwmon{Y}/temp{Z}_input
/// One of temperature inputs
/// /sys/class/drm/card{X}/device/hwmon/hwmon{Y}/temp{Z}_input
/// If nothing is provided higher reading will be taken (this is not good!)
temp_input: Option<TempInput>,
log_level: LogLevel,
@ -242,9 +243,8 @@ pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
#[cfg(test)]
mod parse_config {
use serde::Deserialize;
use amdgpu::{AmdGpuError, Card, TempInput};
use serde::Deserialize;
#[derive(Deserialize, PartialEq, Eq, Debug)]
pub struct Foo {

View File

@ -1,7 +1,6 @@
use serde::{Deserialize, Serialize};
use amdgpu::utils::ensure_config;
use amdgpu::LogLevel;
use serde::{Deserialize, Serialize};
pub static DEFAULT_MONITOR_CONFIG_PATH: &str = "/etc/amdfand/monitor.toml";

View File

@ -1,10 +1,12 @@
#[cfg(feature = "gui-helper")]
use crate::helper_cmd::GuiHelperError;
use crate::lock_file::LockFileError;
use crate::ports::PortsError;
use pidlock::PidlockError;
use std::fmt::{Debug, Display, Formatter};
use pidlock::PidlockError;
use crate::lock_file::LockFileError;
#[cfg(feature = "gui-helper")]
use crate::pidfile::helper_cmd::GuiHelperError;
use crate::pidfile::ports::PortsError;
pub struct IoFailure {
pub io: std::io::Error,
pub path: std::path::PathBuf,

View File

@ -1,16 +1,14 @@
use serde::{Deserialize, Serialize};
pub use card::*;
pub use error::*;
use serde::{Deserialize, Serialize};
pub use temp_input::*;
mod card;
mod error;
#[cfg(feature = "gui-helper")]
pub mod helper_cmd;
pub mod hw_mon;
pub mod lock_file;
pub mod ports;
pub mod pidfile;
mod temp_input;
pub mod utils;
@ -28,7 +26,9 @@ pub static PULSE_WIDTH_MODULATION_MAX: &str = "pwm1_max";
/// pulse width modulation fan level (0-255)
pub static PULSE_WIDTH_MODULATION: &str = "pwm1";
/// pulse width modulation fan control method (0: no fan speed control, 1: manual fan speed control using pwm interface, 2: automatic fan speed control)
/// pulse width modulation fan control method (0: no fan speed control, 1:
/// manual fan speed control using pwm interface, 2: automatic fan speed
/// control)
pub static PULSE_WIDTH_MODULATION_MODE: &str = "pwm1_enable";
// static PULSE_WIDTH_MODULATION_DISABLED: &str = "0";

View File

@ -1,11 +1,13 @@
//! Create lock file and prevent running 2 identical services.
//! NOTE: For 2 amdfand services you may just give 2 different names
use crate::helper_cmd::Pid;
use crate::IoFailure;
use nix::libc;
use std::path::Path;
use nix::libc;
use crate::pidfile::Pid;
use crate::IoFailure;
#[derive(Debug, thiserror::Error)]
pub enum LockFileError {
#[error("Failed to read {path}. {err:?}")]
@ -56,7 +58,8 @@ impl PidLock {
/// Create new lock file. File will be created if:
/// * pid file does not exists
/// * pid file exists but process is dead
/// * old pid and current pid have different names (lock file exists after reboot and PID was taken by other process)
/// * old pid and current pid have different names (lock file exists after
/// reboot and PID was taken by other process)
pub fn acquire(&mut self) -> Result<(), crate::error::AmdGpuError> {
log::debug!("PID LOCK acquiring {}", self.pid_path);
let pid = self.process_pid();

View File

@ -1,9 +1,10 @@
//! AMD GUI helper communication toolkit
use std::io::{Read, Write};
use std::ops::Deref;
use std::os::unix::net::UnixStream;
use crate::pidfile::{Pid, PidResponse};
#[derive(Debug, thiserror::Error)]
pub enum GuiHelperError {
#[error("GUI Helper socket file not found. Is service running?")]
@ -14,18 +15,6 @@ pub enum GuiHelperError {
Serialize(#[from] ron::Error),
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct Pid(pub i32);
impl Deref for Pid {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum Command {
ReloadConfig { pid: Pid },
@ -41,6 +30,12 @@ pub enum Response {
ConfigFileSaveFailed(String),
}
impl PidResponse for Response {
fn kill_response() -> Self {
Self::NoOp
}
}
pub fn sock_file() -> std::path::PathBuf {
std::path::Path::new("/tmp").join("amdgui-helper.sock")
}

133
amdgpu/src/pidfile/mod.rs Normal file
View File

@ -0,0 +1,133 @@
use std::fmt::Debug;
use std::io::{Read, Write};
use std::marker::PhantomData;
use std::net::Shutdown;
use std::ops::Deref;
use std::os::unix::net::UnixStream;
use serde::de::DeserializeOwned;
use serde::Serialize;
pub mod helper_cmd;
pub mod ports;
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct Pid(pub i32);
impl Deref for Pid {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub fn handle_connection<HandleCmd, Cmd, Res>(stream: UnixStream, handle_command: HandleCmd)
where
HandleCmd: FnOnce(Service<Res>, Cmd) + Copy,
Cmd: DeserializeOwned + Serialize + Debug,
Res: DeserializeOwned + Serialize + Debug + PidResponse,
{
let mut service = Service::<Res>::new(stream);
let command = match service.read_command() {
Some(s) => s,
_ => return service.kill(),
};
log::info!("Incoming {:?}", command);
let cmd = match ron::from_str::<Cmd>(command.trim()) {
Ok(cmd) => cmd,
Err(e) => {
log::warn!("Invalid message {:?}. {:?}", command, e);
return service.kill();
}
};
handle_command(service, cmd);
}
pub trait PidResponse: Sized {
fn kill_response() -> Self;
}
pub struct Service<Response>(UnixStream, PhantomData<Response>)
where
Response: serde::Serialize + Debug + PidResponse;
impl<Response> Service<Response>
where
Response: serde::Serialize + Debug + PidResponse,
{
pub fn new(file: UnixStream) -> Self {
Self(file, Default::default())
}
/// Serialize and send command
pub fn write_response(&mut self, res: Response) {
write_response(&mut self.0, res)
}
/// Read from `.sock` file new line separated commands
pub fn read_command(&mut self) -> Option<String> {
read_command(&mut self.0)
}
/// Close connection with no operation response
pub fn kill(mut self) {
self.write_response(Response::kill_response());
self.close();
}
pub fn close(self) {
let _ = self.0.shutdown(Shutdown::Both);
}
}
/// Serialize and send command
pub fn write_response<Response>(file: &mut UnixStream, res: Response)
where
Response: serde::Serialize + Debug,
{
match ron::to_string(&res) {
Ok(buffer) => match file.write_all(buffer.as_bytes()) {
Ok(_) => {
log::info!("Response successfully written")
}
Err(e) => log::warn!("Failed to write response. {:?}", e),
},
Err(e) => {
log::warn!("Failed to serialize response {:?}. {:?}", res, e)
}
}
}
/// Read from `.sock` file new line separated commands
pub fn read_command(file: &mut UnixStream) -> Option<String> {
let mut command = String::with_capacity(100);
log::info!("Reading stream...");
read_line(file, &mut command);
if command.is_empty() {
return None;
}
Some(command)
}
pub fn read_line(stream: &mut UnixStream, command: &mut String) {
let mut buffer = [0];
while stream.read_exact(&mut buffer).is_ok() {
if buffer[0] == b'\n' {
break;
}
match std::str::from_utf8(&buffer) {
Ok(s) => {
command.push_str(s);
}
Err(e) => {
log::error!("Failed to read from client. {:?}", e);
let _ = stream.shutdown(Shutdown::Both);
continue;
}
}
}
}

View File

@ -1,12 +1,13 @@
//! AMD GUI helper communication toolkit
use std::io::{Read, Write};
use std::ops::Deref;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::pidfile::PidResponse;
#[derive(Debug, thiserror::Error)]
pub enum PortsError {
#[error("AMD GPU ports socket file not found. Is service running?")]
@ -74,18 +75,6 @@ impl Output {
}
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct Pid(pub i32);
impl Deref for Pid {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum Command {
Ports,
@ -97,6 +86,12 @@ pub enum Response {
NoOp,
}
impl PidResponse for Response {
fn kill_response() -> Self {
Self::NoOp
}
}
pub fn sock_file() -> PathBuf {
std::path::Path::new("/tmp").join("amdgpu-ports.sock")
}

View File

@ -1,6 +1,7 @@
use crate::AmdGpuError;
use serde::Serializer;
use crate::AmdGpuError;
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub struct TempInput(pub u16);

View File

@ -1,23 +1,26 @@
//! Special daemon with root privileges. Since GUI should not have (and sometimes can't have) root
//! privileges and service processes are designed to be as small as possible this is proxy.
//! Special daemon with root privileges. Since GUI should not have (and
//! sometimes can't have) root privileges and service processes are designed to
//! be as small as possible this is proxy.
//!
//! It is responsible for:
//! * Loading all amdfand processes. In order to do this process needs to be killed with signal 0 to check if it still is alive
//! * Loading all amdfand processes. In order to do this process needs to be
//! killed with signal 0 to check if it still is alive
//! * Reload amdfand process with signal SIGHUP
//! * Save changed config file
//!
//! It is using `/tmp/amdgui-helper.sock` file and `ron` serialization for communication.
//! After each operation connection is terminated so each command needs new connection.
//! It is using `/tmp/amdgui-helper.sock` file and `ron` serialization for
//! communication. After each operation connection is terminated so each command
//! needs new connection.
#![allow(clippy::non_octal_unix_permissions)]
use amdgpu::helper_cmd::{Command, Pid, Response};
use amdgpu::IoFailure;
use std::ffi::OsStr;
use std::fs::Permissions;
use std::io::{Read, Write};
use std::net::Shutdown;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::{UnixListener, UnixStream};
use std::os::unix::net::UnixListener;
use amdgpu::pidfile::helper_cmd::{Command, Response};
use amdgpu::pidfile::{handle_connection, Pid};
use amdgpu::IoFailure;
#[derive(Debug, thiserror::Error)]
pub enum Error {
@ -38,7 +41,7 @@ fn main() -> Result<()> {
let mut lock = amdgpu::lock_file::PidLock::new("amdgui", String::from("helper"))?;
lock.acquire()?;
let sock_path = amdgpu::helper_cmd::sock_file();
let sock_path = amdgpu::pidfile::helper_cmd::sock_file();
let listener = {
let _ = std::fs::remove_file(&sock_path);
@ -52,86 +55,27 @@ fn main() -> Result<()> {
}
while let Ok((stream, _addr)) = listener.accept() {
handle_connection(stream);
handle_connection::<_, Command, Response>(stream, handle_command);
}
lock.release()?;
Ok(())
}
pub struct Service(UnixStream);
pub type Service = amdgpu::pidfile::Service<Response>;
impl Service {
/// Serialize and send command
pub fn write_response(&mut self, res: amdgpu::helper_cmd::Response) {
match ron::to_string(&res) {
Ok(buffer) => match self.0.write_all(buffer.as_bytes()) {
Ok(_) => {
log::info!("Response successfully written")
}
Err(e) => log::warn!("Failed to write response. {:?}", e),
},
Err(e) => {
log::warn!("Failed to serialize response {:?}. {:?}", res, e)
}
}
}
/// Read from `.sock` file new line separated commands
pub fn read_command(&mut self) -> Option<String> {
let mut command = String::with_capacity(100);
log::info!("Reading stream...");
read_line(&mut self.0, &mut command);
if command.is_empty() {
return None;
}
Some(command)
}
/// Close connection with no operation response
pub fn kill(mut self) {
self.write_response(Response::NoOp);
self.close();
}
pub fn close(self) {
let _ = self.0.shutdown(Shutdown::Both);
}
}
fn handle_connection(stream: UnixStream) {
let mut service = Service(stream);
let command = match service.read_command() {
Some(s) => s,
_ => return service.kill(),
};
log::info!("Incoming {:?}", command);
let cmd = match ron::from_str::<amdgpu::helper_cmd::Command>(command.trim()) {
Ok(cmd) => cmd,
Err(e) => {
log::warn!("Invalid message {:?}. {:?}", command, e);
return service.kill();
}
};
handle_command(service, cmd);
}
fn handle_command(mut service: Service, cmd: Command) {
fn handle_command(service: Service, cmd: Command) {
match cmd {
Command::ReloadConfig { pid } => {
log::info!("Reloading config file for pid {:?}", pid);
handle_reload_config(service, pid);
}
Command::FanServices => handle_fan_services(service),
Command::SaveFanConfig { path, content } => {
handle_save_fan_config(&mut service, path, content)
}
Command::SaveFanConfig { path, content } => handle_save_fan_config(service, path, content),
}
}
fn handle_save_fan_config(service: &mut Service, path: String, content: String) {
fn handle_save_fan_config(mut service: Service, path: String, content: String) {
match std::fs::write(path, content) {
Err(e) => service.write_response(Response::ConfigFileSaveFailed(format!("{:?}", e))),
Ok(..) => service.write_response(Response::ConfigFileSaved),
@ -145,25 +89,6 @@ fn handle_fan_services(mut service: Service) {
service.write_response(Response::Services(services));
}
fn read_line(stream: &mut UnixStream, command: &mut String) {
let mut buffer = [0];
while stream.read_exact(&mut buffer).is_ok() {
if buffer[0] == b'\n' {
break;
}
match std::str::from_utf8(&buffer) {
Ok(s) => {
command.push_str(s);
}
Err(e) => {
log::error!("Failed to read from client. {:?}", e);
let _ = stream.shutdown(Shutdown::Both);
continue;
}
}
}
}
fn handle_reload_config(service: Service, pid: Pid) {
unsafe {
nix::libc::kill(pid.0, nix::sys::signal::Signal::SIGHUP as i32);

View File

@ -40,28 +40,31 @@ gumdrop = { version = "0.8" }
log = { version = "0.4" }
pretty_env_logger = { version = "0.4" }
egui = { version = "0.15", optional = true }
epaint = { version = "0.15", features = ["serialize"], optional = true }
epi = { version = "0.15", optional = true }
winit = { version = "0.25", optional = true }
egui-winit = { version = "0.15", optional = true }
egui = { version = "0.18", optional = true, features = [] }
epaint = { version = "0.18", features = [], optional = true }
epi = { version = "0.17.0", optional = true }
winit = { version = "0.26", optional = true }
egui-winit = { version = "0.18", optional = true }
# vulkan
egui_vulkano = { version = "0.4", optional = true }
vulkano-win = { version = "0.25", optional = true }
vulkano = { version = "0.25", optional = true }
vulkano-shaders = { version = "0.25", optional = true }
egui_vulkano = { version = "0.8.0", optional = true }
vulkano-win = { version = "0.29.0", optional = true }
vulkano = { version = "0.29.0", optional = true }
vulkano-shaders = { version = "0.29.0", optional = true }
bytemuck = { version = "*" }
# xorg glium
glium = { version = "0.30", optional = true }
egui_glium = { version = "0.15", optional = true }
glium = { version = "0.32.1", optional = true }
egui_glium = { version = "0.18.0", optional = true }
# xorg glow
glutin = { version = "0.27", optional = true }
glutin = { version = "0.29", optional = true }
glow = { version = "0.11", optional = true }
egui_glow = { version = "0.15", optional = true }
egui_glow = { version = "0.18", optional = true }
tokio = { version = "1.15", features = ["full"] }
parking_lot = { version = "0.11" }
parking_lot = { version = "0.12" }
nix = { version = "0.24" }
nix = { version = "0.25" }
image = { version = "0.24.2" }
[dev-dependencies]
amdgpu = { path = "../amdgpu", version = "1.0", features = ["gui-helper"] }

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -1,10 +1,14 @@
use crate::widgets::{ChangeFanSettings, CoolingPerformance};
use amdgpu::helper_cmd::Pid;
use egui::{CtxRef, Ui};
use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;
use amdgpu::pidfile::ports::Output;
use amdgpu::pidfile::Pid;
use egui::Ui;
use epi::Frame;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
use crate::widgets::outputs_settings::OutputsSettings;
use crate::widgets::{ChangeFanSettings, CoolingPerformance};
pub enum ChangeState {
New,
@ -61,6 +65,7 @@ impl From<Vec<Pid>> for FanServices {
pub enum Page {
Config,
Monitoring,
Outputs,
Settings,
}
@ -85,14 +90,16 @@ pub struct StatefulConfig {
pub struct AmdGui {
pub page: Page,
pid_files: FanServices,
outputs: BTreeMap<String, Vec<Output>>,
cooling_performance: CoolingPerformance,
change_fan_settings: ChangeFanSettings,
outputs_settings: OutputsSettings,
config: StatefulConfig,
reload_pid_list_delay: u8,
}
impl epi::App for AmdGui {
fn update(&mut self, _ctx: &CtxRef, _frame: &mut Frame<'_>) {}
fn update(&mut self, _ctx: &epi::egui::Context, _frame: &Frame) {}
fn name(&self) -> &str {
"AMD GUI"
@ -104,8 +111,10 @@ impl AmdGui {
Self {
page: Default::default(),
pid_files: FanServices::from(vec![]),
outputs: Default::default(),
cooling_performance: CoolingPerformance::new(100, config.clone()),
change_fan_settings: ChangeFanSettings::new(config.clone()),
outputs_settings: OutputsSettings::default(),
config: StatefulConfig {
config,
state: ChangeState::New,
@ -124,6 +133,10 @@ impl AmdGui {
self.cooling_performance.draw(ui, &self.pid_files);
}
Page::Settings => {}
Page::Outputs => {
self.outputs_settings
.draw(ui, &mut self.config, &self.outputs);
}
}
}
@ -131,13 +144,15 @@ impl AmdGui {
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;
match amdgpu::helper_cmd::send_command(amdgpu::helper_cmd::Command::FanServices) {
Ok(amdgpu::helper_cmd::Response::Services(services))
if self.pid_files.list_changed(&services) =>
{
use amdgpu::pidfile::helper_cmd::{send_command, Command, Response};
match send_command(Command::FanServices) {
Ok(Response::Services(services)) if self.pid_files.list_changed(&services) => {
self.pid_files = FanServices::from(services);
}
Ok(amdgpu::helper_cmd::Response::Services(_services)) => {
Ok(Response::Services(_services)) => {
// SKIP
}
Ok(res) => {
@ -147,6 +162,55 @@ impl AmdGui {
log::warn!("Failed to load amd fan services pid list. {:?}", e);
}
}
}
{
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(format!("{}", output.card));
set
},
);
names.sort();
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| {
let v = agg
.entry(output.card.clone())
.or_insert_with(|| Vec::with_capacity(6));
v.push(output);
v.sort_by(|a, b| {
format!(
"{}{}{}",
a.port_type,
a.port_name.as_deref().unwrap_or_default(),
a.port_number,
)
.cmp(&format!(
"{}{}{}",
b.port_type,
b.port_name.as_deref().unwrap_or_default(),
b.port_number,
))
});
agg
});
}
Err(e) => {
log::warn!("Failed to load amd fan services pid list. {:?}", e);
}
}
}
} else {
self.reload_pid_list_delay -= 1;
}

View File

@ -1,10 +1,11 @@
use parking_lot::Mutex;
use std::sync::Arc;
use glium::glutin;
use parking_lot::Mutex;
use tokio::sync::mpsc::UnboundedReceiver;
use crate::app::AmdGui;
use crate::backend::create_ui;
use glium::glutin;
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
let window_builder = glutin::window::WindowBuilder::new()

View File

@ -108,7 +108,9 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, mut receiver: UnboundedReceiver<bool
egui.on_event(&event);
gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
gl_window.window().request_redraw(); // TODO: ask egui if the
// events warrants a
// repaint instead
}
glutin::event::Event::LoopDestroyed => {
egui.destroy(&gl);

View File

@ -5,39 +5,52 @@ pub mod glow_backend;
#[cfg(feature = "wayland")]
pub mod wayland_backend;
use std::sync::Arc;
use egui::panel::TopBottomSide;
use egui::{Layout, PointerButton};
use epaint::TextStyle;
#[cfg(feature = "xorg-glium")]
pub use glium_backend::run_app;
#[cfg(feature = "xorg-glow")]
pub use glow_backend::run_app;
use parking_lot::Mutex;
use std::sync::Arc;
#[cfg(feature = "wayland")]
pub use wayland_backend::run_app;
#[cfg(feature = "xorg-glow")]
pub use glow_backend::run_app;
use crate::app::Page;
use crate::AmdGui;
#[cfg(feature = "xorg-glium")]
pub use glium_backend::run_app;
pub fn create_ui(amd_gui: Arc<Mutex<AmdGui>>, ctx: &egui::CtxRef) {
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("Config").text_style(TextStyle::Heading))
.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) */
)
.clicked_by(PointerButton::Primary)
{
amd_gui.lock().page = Page::Config;
}
if child
.add(egui::Button::new("Monitoring").text_style(TextStyle::Heading))
.add(
egui::Button::new("Monitoring"), /* .text_style(TextStyle::Heading) */
)
.clicked_by(PointerButton::Primary)
{
amd_gui.lock().page = Page::Monitoring;
}
if child
.add(egui::Button::new("Settings").text_style(TextStyle::Heading))
.add(
egui::Button::new("Settings"), /* .text_style(TextStyle::Heading) */
)
.clicked_by(PointerButton::Primary)
{
amd_gui.lock().page = Page::Settings;
@ -54,6 +67,9 @@ pub fn create_ui(amd_gui: Arc<Mutex<AmdGui>>, ctx: &egui::CtxRef) {
Page::Monitoring => {
gui.ui(ui);
}
Page::Outputs => {
gui.ui(ui);
}
Page::Settings => {
ctx.settings_ui(ui);
}

View File

@ -1,112 +1,156 @@
use crate::app::AmdGui;
use crate::backend::create_ui;
use parking_lot::Mutex;
use std::convert::{TryFrom, TryInto};
use std::sync::Arc;
use bytemuck::{Pod, Zeroable};
use egui_vulkano::UpdateTexturesResult;
use parking_lot::Mutex;
use tokio::sync::mpsc::UnboundedReceiver;
use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer};
use vulkano::command_buffer::{
AutoCommandBufferBuilder, CommandBufferUsage, DynamicState, SubpassContents,
};
use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer, TypedBufferAccess};
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, SubpassContents};
use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType};
use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo};
use vulkano::format::Format;
use vulkano::image::view::ImageView;
use vulkano::image::{ImageUsage, SwapchainImage};
use vulkano::render_pass::{Framebuffer, FramebufferAbstract, RenderPass, Subpass};
use vulkano::swapchain::{AcquireError, ColorSpace, Swapchain, SwapchainCreationError};
use vulkano::sync::{FlushError, GpuFuture};
use vulkano::{swapchain, sync, Version};
use vulkano::image::{ImageAccess, ImageUsage, SwapchainImage};
use vulkano::instance::{Instance, InstanceCreateInfo};
use vulkano::pipeline::graphics::input_assembly::InputAssemblyState;
use vulkano::pipeline::graphics::vertex_input::BuffersDefinition;
use vulkano::pipeline::graphics::viewport::{Viewport, ViewportState};
use vulkano::pipeline::GraphicsPipeline;
use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass};
use vulkano::swapchain::{AcquireError, Swapchain, SwapchainCreateInfo, SwapchainCreationError};
use vulkano::sync::{FenceSignalFuture, FlushError, GpuFuture};
use vulkano::{swapchain, sync};
use vulkano_win::VkSurfaceBuild;
use winit::dpi::PhysicalSize;
use winit::event::{Event, WindowEvent};
use winit::event_loop::ControlFlow;
use winit::window::Window;
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Fullscreen, Window, WindowBuilder};
pub mod vs {
vulkano_shaders::shader! {
ty: "vertex",
src: "
#version 450
layout(location = 0) in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
"
}
}
use crate::app::AmdGui;
use crate::backend::create_ui;
pub mod fs {
vulkano_shaders::shader! {
ty: "fragment",
src: "
#version 450
layout(location = 0) out vec4 f_color;
void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0);
}
"
}
}
#[derive(Default, Debug, Clone)]
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
struct Vertex {
position: [f32; 2],
color: [f32; 4],
}
pub enum FrameEndFuture<F: GpuFuture + 'static> {
FenceSignalFuture(FenceSignalFuture<F>),
BoxedFuture(Box<dyn GpuFuture>),
}
impl<F: GpuFuture> FrameEndFuture<F> {
pub fn now(device: Arc<Device>) -> Self {
Self::BoxedFuture(sync::now(device).boxed())
}
pub fn get(self) -> Box<dyn GpuFuture> {
match self {
FrameEndFuture::FenceSignalFuture(f) => f.boxed(),
FrameEndFuture::BoxedFuture(f) => f,
}
}
}
impl<F: GpuFuture> AsMut<dyn GpuFuture> for FrameEndFuture<F> {
fn as_mut(&mut self) -> &mut (dyn GpuFuture + 'static) {
match self {
FrameEndFuture::FenceSignalFuture(f) => f,
FrameEndFuture::BoxedFuture(f) => f,
}
}
}
pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>) {
let required_extensions = vulkano_win::required_extensions();
let instance =
vulkano::instance::Instance::new(None, Version::V1_0, &required_extensions, None).unwrap();
let physical = vulkano::device::physical::PhysicalDevice::enumerate(&instance)
.next()
let instance = Instance::new(InstanceCreateInfo {
enabled_extensions: required_extensions,
..Default::default()
})
.unwrap();
let event_loop = winit::event_loop::EventLoop::new();
let surface = winit::window::WindowBuilder::new()
.with_inner_size(PhysicalSize::new(1024, 768))
.with_title("AMD GUI")
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
println!(
"Using device: {} (type: {:?})",
physical.properties().device_name,
physical.properties().device_type,
);
let event_loop = EventLoop::new();
let surface = WindowBuilder::new()
.with_title("egui_vulkano demo")
.with_fullscreen(Some(Fullscreen::Borderless(None)))
.build_vk_surface(&event_loop, instance.clone())
.unwrap();
// vulkan
let queue_family = physical
.queue_families()
.find(|&q| q.supports_graphics() && surface.is_supported(q).unwrap_or(false))
let device_extensions = DeviceExtensions {
khr_swapchain: true,
..DeviceExtensions::none()
};
let (physical_device, queue_family) = PhysicalDevice::enumerate(&instance)
.filter(|&p| p.supported_extensions().is_superset_of(&device_extensions))
.filter_map(|p| {
p.queue_families()
.find(|&q| q.supports_graphics() && q.supports_surface(&surface).unwrap_or(false))
.map(|q| (p, q))
})
.min_by_key(|(p, _)| match p.properties().device_type {
PhysicalDeviceType::DiscreteGpu => 0,
PhysicalDeviceType::IntegratedGpu => 1,
PhysicalDeviceType::VirtualGpu => 2,
PhysicalDeviceType::Cpu => 3,
PhysicalDeviceType::Other => 4,
})
.unwrap();
let device_ext = vulkano::device::DeviceExtensions {
khr_swapchain: true,
..vulkano::device::DeviceExtensions::none()
};
let (device, mut queues) = vulkano::device::Device::new(
physical,
physical.supported_features(),
&device_ext,
[(queue_family, 0.5)].iter().cloned(),
let (device, mut queues) = Device::new(
physical_device,
DeviceCreateInfo {
enabled_extensions: physical_device
.required_extensions()
.union(&device_extensions),
queue_create_infos: vec![QueueCreateInfo::family(queue_family)],
..Default::default()
},
)
.unwrap();
let queue = queues.next().unwrap();
let (mut swapchain, images) = {
let caps = surface.capabilities(physical).unwrap();
let alpha = caps.supported_composite_alpha.iter().next().unwrap();
let caps = physical_device
.surface_capabilities(&surface, Default::default())
.unwrap();
let composite_alpha = caps.supported_composite_alpha.iter().next().unwrap();
assert!(&caps
.supported_formats
.contains(&(Format::B8G8R8A8Srgb, ColorSpace::SrgbNonLinear)));
let format = Format::B8G8R8A8Srgb;
let dimensions: [u32; 2] = surface.window().inner_size().into();
let image_format = Some(Format::B8G8R8A8_SRGB);
let image_extent: [u32; 2] = surface.window().inner_size().into();
Swapchain::start(device.clone(), surface.clone())
.num_images(caps.min_image_count)
.format(format)
.dimensions(dimensions)
.usage(ImageUsage::color_attachment())
.sharing_mode(&queue)
.composite_alpha(alpha)
.build()
Swapchain::new(
device.clone(),
surface.clone(),
SwapchainCreateInfo {
min_image_count: caps.min_image_count,
image_format,
image_extent,
image_usage: ImageUsage::color_attachment(),
composite_alpha,
..Default::default()
},
)
.unwrap()
};
#[derive(Default, Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
struct Vertex {
position: [f32; 2],
}
vulkano::impl_vertex!(Vertex, position);
let vertex_buffer = {
@ -131,17 +175,46 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
.unwrap()
};
let vs = vs::Shader::load(device.clone()).unwrap();
let fs = fs::Shader::load(device.clone()).unwrap();
mod vs {
vulkano_shaders::shader! {
ty: "vertex",
src: "
#version 450
let render_pass = Arc::new(
vulkano::ordered_passes_renderpass!(
layout(location = 0) in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
"
}
}
mod fs {
vulkano_shaders::shader! {
ty: "fragment",
src: "
#version 450
layout(location = 0) out vec4 f_color;
void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0);
}
"
}
}
let vs = vs::load(device.clone()).unwrap();
let fs = fs::load(device.clone()).unwrap();
let render_pass = vulkano::ordered_passes_renderpass!(
device.clone(),
attachments: {
color: {
load: Clear,
store: Store,
format: swapchain.format(),
format: swapchain.image_format(),
samples: 1,
}
},
@ -150,40 +223,34 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
{ color: [color], depth_stencil: {}, input: [] } // Create a second renderpass to draw egui
]
)
.unwrap(),
);
.unwrap();
let pipeline = Arc::new(
vulkano::pipeline::GraphicsPipeline::start()
.vertex_input_single_buffer::<Vertex>()
.vertex_shader(vs.main_entry_point(), ())
.triangle_list()
.viewports_dynamic_scissors_irrelevant(1)
.fragment_shader(fs.main_entry_point(), ())
.render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
let pipeline = GraphicsPipeline::start()
.vertex_input_state(BuffersDefinition::new().vertex::<Vertex>())
.vertex_shader(vs.entry_point("main").unwrap(), ())
.input_assembly_state(InputAssemblyState::new())
.viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant())
.fragment_shader(fs.entry_point("main").unwrap(), ())
.render_pass(Subpass::from(render_pass.clone().into(), 0).unwrap())
.build(device.clone())
.unwrap(),
);
.unwrap();
let mut dynamic_state = DynamicState {
line_width: None,
viewports: None,
scissors: None,
compare_mask: None,
write_mask: None,
reference: None,
let mut viewport = Viewport {
origin: [0.0, 0.0],
dimensions: [0.0, 0.0],
depth_range: 0.0..1.0,
};
let mut framebuffers =
window_size_dependent_setup(&images, render_pass.clone(), &mut dynamic_state);
let mut framebuffers = window_size_dependent_setup(&images, render_pass.clone(), &mut viewport);
let mut recreate_swap_chain = false;
let mut recreate_swapchain = false;
let mut previous_frame_end = Some(sync::now(device.clone()).boxed());
let mut previous_frame_end = Some(FrameEndFuture::now(device.clone()));
//Set up everything need to draw the gui
let window = surface.window();
let mut egui_ctx = egui::CtxRef::default();
let mut egui_winit = egui_winit::State::new(window);
let egui_ctx = egui::Context::default();
let mut egui_winit = egui_winit::State::new(4096, window);
let mut egui_painter = egui_vulkano::Painter::new(
device.clone(),
@ -192,6 +259,11 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
)
.unwrap();
//Set up some window to look at for the test
// let mut my_texture = egui_ctx.load_texture("my_texture",
// ColorImage::example());
event_loop.run(move |event, _, control_flow| {
match event {
Event::WindowEvent {
@ -204,7 +276,7 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
event: WindowEvent::Resized(_),
..
} => {
recreate_swap_chain = true;
recreate_swapchain = true;
}
Event::WindowEvent { event, .. } => {
let egui_consumed_event = egui_winit.on_event(&egui_ctx, &event);
@ -212,39 +284,47 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
// do your own event handling here
};
}
Event::UserEvent(_) | Event::RedrawEventsCleared => {
previous_frame_end.as_mut().unwrap().cleanup_finished();
Event::RedrawEventsCleared => {
previous_frame_end
.as_mut()
.unwrap()
.as_mut()
.cleanup_finished();
if recreate_swap_chain {
if recreate_swapchain {
let dimensions: [u32; 2] = surface.window().inner_size().into();
let (new_swap_chain, new_images) =
match swapchain.recreate().dimensions(dimensions).build() {
let (new_swapchain, new_images) =
match swapchain.recreate(SwapchainCreateInfo {
image_extent: surface.window().inner_size().into(),
..swapchain.create_info()
}) {
Ok(r) => r,
Err(SwapchainCreationError::UnsupportedDimensions) => return,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {:?}", e),
};
swapchain = new_swap_chain;
swapchain = new_swapchain;
framebuffers = window_size_dependent_setup(
&new_images,
render_pass.clone(),
&mut dynamic_state,
&mut viewport,
);
recreate_swap_chain = false;
viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32];
recreate_swapchain = false;
}
let (image_num, suboptimal, acquire_future) =
match swapchain::acquire_next_image(swapchain.clone(), None) {
Ok(r) => r,
Err(AcquireError::OutOfDate) => {
recreate_swap_chain = true;
recreate_swapchain = true;
return;
}
Err(e) => panic!("Failed to acquire next image: {:?}", e),
};
if suboptimal {
recreate_swap_chain = true;
recreate_swapchain = true;
}
let clear_values = vec![[0.0, 0.0, 1.0, 1.0].into()];
@ -255,6 +335,20 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
)
.unwrap();
egui_ctx.begin_frame(egui_winit.take_egui_input(surface.window()));
create_ui(amd_gui.clone(), &egui_ctx);
let egui_output = egui_ctx.end_frame();
let platform_output = egui_output.platform_output;
egui_winit.handle_platform_output(surface.window(), &egui_ctx, platform_output);
let result = egui_painter
.update_textures(egui_output.textures_delta, &mut builder)
.expect("egui texture error");
let wait_for_last_frame = result == UpdateTexturesResult::Changed;
// Do your usual rendering
builder
.begin_render_pass(
@ -263,29 +357,23 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
clear_values,
)
.unwrap()
.draw(
pipeline.clone(),
&dynamic_state,
vertex_buffer.clone(),
(),
(),
)
.set_viewport(0, [viewport.clone()])
.bind_pipeline_graphics(pipeline.clone())
.bind_vertex_buffers(0, vertex_buffer.clone())
.draw(vertex_buffer.len().try_into().unwrap(), 1, 0, 0)
.unwrap(); // Don't end the render pass yet
egui_ctx.begin_frame(egui_winit.take_egui_input(surface.window()));
// Build your gui
create_ui(amd_gui.clone(), &egui_ctx);
let (egui_output, clipped_shapes) = egui_ctx.end_frame();
egui_winit.handle_output(surface.window(), &egui_ctx, egui_output);
// Automatically start the next render subpass and draw the gui
let size = surface.window().inner_size();
let sf: f32 = surface.window().scale_factor() as f32;
egui_painter
.draw(
&mut builder,
&dynamic_state,
[size.width as f32, size.height as f32],
[(size.width as f32) / sf, (size.height as f32) / sf],
&egui_ctx,
clipped_shapes,
egui_output.shapes,
)
.unwrap();
@ -294,9 +382,16 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
let command_buffer = builder.build().unwrap();
if wait_for_last_frame {
if let Some(FrameEndFuture::FenceSignalFuture(ref mut f)) = previous_frame_end {
f.wait(None).unwrap();
}
}
let future = previous_frame_end
.take()
.unwrap()
.get()
.join(acquire_future)
.then_execute(queue.clone(), command_buffer)
.unwrap()
@ -305,15 +400,15 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
match future {
Ok(future) => {
previous_frame_end = Some(future.boxed());
previous_frame_end = Some(FrameEndFuture::FenceSignalFuture(future));
}
Err(FlushError::OutOfDate) => {
recreate_swap_chain = true;
previous_frame_end = Some(sync::now(device.clone()).boxed());
recreate_swapchain = true;
previous_frame_end = Some(FrameEndFuture::now(device.clone()));
}
Err(e) => {
println!("Failed to flush future: {:?}", e);
previous_frame_end = Some(sync::now(device.clone()).boxed());
previous_frame_end = Some(FrameEndFuture::now(device.clone()));
}
}
}
@ -325,28 +420,23 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
fn window_size_dependent_setup(
images: &[Arc<SwapchainImage<Window>>],
render_pass: Arc<RenderPass>,
dynamic_state: &mut DynamicState,
) -> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
let dimensions = images[0].dimensions();
let viewport = vulkano::pipeline::viewport::Viewport {
origin: [0.0, 0.0],
dimensions: [dimensions[0] as f32, dimensions[1] as f32],
depth_range: 0.0..1.0,
};
dynamic_state.viewports = Some(vec![viewport]);
viewport: &mut Viewport,
) -> Vec<Arc<Framebuffer>> {
let dimensions = images[0].dimensions().width_height();
viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32];
images
.iter()
.map(|image| {
let view = ImageView::new(image.clone()).unwrap();
Arc::new(
Framebuffer::start(render_pass.clone())
.add(view)
let view = ImageView::new_default(image.clone()).unwrap();
Framebuffer::new(
render_pass.clone(),
FramebufferCreateInfo {
attachments: vec![view],
..Default::default()
},
)
.unwrap()
.build()
.unwrap(),
) as Arc<dyn FramebufferAbstract + Send + Sync>
})
.collect::<Vec<_>>()
}

View File

@ -2,10 +2,9 @@
use std::ops::RangeInclusive;
pub use arrows::*;
use egui::Pos2;
use epaint::{Color32, Shape, Stroke};
pub use arrows::*;
pub use h_line::*;
pub use line::*;
pub use marker_shape::*;
@ -129,7 +128,8 @@ impl ToString for LineStyle {
// ----------------------------------------------------------------------------
/// Describes a function y = f(x) with an optional range for x and a number of points.
/// Describes a function y = f(x) with an optional range for x and a number of
/// points.
pub struct ExplicitGenerator {
function: Box<dyn Fn(f64) -> f64>,
x_range: RangeInclusive<f64>,
@ -138,8 +138,8 @@ pub struct ExplicitGenerator {
// ----------------------------------------------------------------------------
/// Returns the x-coordinate of a possible intersection between a line segment from `p1` to `p2` and
/// a horizontal line at the given y-coordinate.
/// Returns the x-coordinate of a possible intersection between a line segment
/// from `p1` to `p2` and a horizontal line at the given y-coordinate.
#[inline(always)]
pub fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option<f32> {
((p1.y > y && p2.y < y) || (p1.y < y && p2.y > y))

View File

@ -1,9 +1,11 @@
use std::ops::RangeInclusive;
use egui::Ui;
use epaint::{Color32, Shape};
use crate::items::plot_item::PlotItem;
use crate::items::values::Values;
use crate::transform::{Bounds, ScreenTransform};
use egui::Ui;
use epaint::{Color32, Shape};
use std::ops::RangeInclusive;
/// A set of arrows.
pub struct Arrows {

View File

@ -1,11 +1,13 @@
use std::ops::RangeInclusive;
use egui::Ui;
use epaint::{Color32, Shape, Stroke};
use crate::items::plot_item::PlotItem;
use crate::items::value::Value;
use crate::items::values::Values;
use crate::items::LineStyle;
use crate::transform::{Bounds, ScreenTransform};
use egui::Ui;
use epaint::{Color32, Shape, Stroke};
use std::ops::RangeInclusive;
/// A horizontal line in a plot, filling the full width
#[derive(Clone, Debug, PartialEq)]
@ -28,7 +30,8 @@ impl HLine {
}
}
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will
/// be auto-assigned.
#[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.stroke.color = color.into();

View File

@ -1,12 +1,14 @@
use std::ops::RangeInclusive;
use egui::{pos2, NumExt, Ui};
use epaint::{Color32, Mesh, Rgba, Shape, Stroke};
use crate::items;
use crate::items::plot_item::PlotItem;
use crate::items::value::Value;
use crate::items::values::Values;
use crate::items::{LineStyle, DEFAULT_FILL_ALPHA};
use crate::transform::{Bounds, ScreenTransform};
use egui::{pos2, NumExt, Ui};
use epaint::{Color32, Mesh, Rgba, Shape, Stroke};
use std::ops::RangeInclusive;
impl PlotItem for Line {
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
@ -120,7 +122,8 @@ impl Line {
}
}
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will
/// be auto-assigned.
#[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.stroke.color = color.into();

View File

@ -1,10 +1,12 @@
use std::ops::RangeInclusive;
use egui::{pos2, Image, Rect, Ui, Vec2};
use epaint::{Color32, Shape, Stroke, TextureId};
use crate::items::plot_item::PlotItem;
use crate::items::value::Value;
use crate::items::values::Values;
use crate::transform::{Bounds, ScreenTransform};
use egui::{pos2, Image, Rect, Ui, Vec2};
use epaint::{Color32, Shape, Stroke, TextureId};
use std::ops::RangeInclusive;
/// An image in the plot.
pub struct PlotImage {
@ -65,8 +67,8 @@ impl PlotImage {
///
/// This name will show up in the plot legend, if legends are turned on.
///
/// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend.
/// Multiple plot items may share the same name, in which case they will
/// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn name(mut self, name: impl ToString) -> Self {

View File

@ -1,8 +1,10 @@
use crate::items::Values;
use crate::transform::{Bounds, ScreenTransform};
use std::ops::RangeInclusive;
use egui::Ui;
use epaint::{Color32, Shape};
use std::ops::RangeInclusive;
use crate::items::Values;
use crate::transform::{Bounds, ScreenTransform};
/// Trait shared by things that can be drawn in the plot.
pub trait PlotItem {

View File

@ -1,17 +1,20 @@
use std::ops::RangeInclusive;
use egui::{pos2, vec2, Pos2, Ui};
use epaint::{Color32, Shape, Stroke};
use crate::items::marker_shape::MarkerShape;
use crate::items::plot_item::PlotItem;
use crate::items::value::Value;
use crate::items::values::Values;
use crate::transform::{Bounds, ScreenTransform};
use egui::{pos2, vec2, Pos2, Ui};
use epaint::{Color32, Shape, Stroke};
use std::ops::RangeInclusive;
/// A set of points.
pub struct Points {
pub(crate) series: Values,
pub(crate) shape: MarkerShape,
/// Color of the marker. `Color32::TRANSPARENT` means that it will be picked automatically.
/// Color of the marker. `Color32::TRANSPARENT` means that it will be picked
/// automatically.
pub(crate) color: Color32,
/// Whether to fill the marker. Does not apply to all types.
pub(crate) filled: bool,
@ -64,7 +67,8 @@ impl Points {
self
}
/// Whether to add stems between the markers and a horizontal reference line.
/// Whether to add stems between the markers and a horizontal reference
/// line.
#[must_use]
pub fn stems(mut self, y_reference: impl Into<f32>) -> Self {
self.stems = Some(y_reference.into());
@ -82,8 +86,8 @@ impl Points {
///
/// This name will show up in the plot legend, if legends are turned on.
///
/// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend.
/// Multiple plot items may share the same name, in which case they will
/// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn name(mut self, name: impl ToString) -> Self {

View File

@ -1,10 +1,12 @@
use std::ops::RangeInclusive;
use egui::{NumExt, Ui};
use epaint::{Color32, Rgba, Shape, Stroke};
use crate::items::plot_item::PlotItem;
use crate::items::values::Values;
use crate::items::{LineStyle, DEFAULT_FILL_ALPHA};
use crate::transform::{Bounds, ScreenTransform};
use egui::{NumExt, Ui};
use epaint::{Color32, Rgba, Shape, Stroke};
use std::ops::RangeInclusive;
/// A convex polygon.
pub struct Polygon {
@ -28,8 +30,8 @@ impl Polygon {
}
}
/// Highlight this polygon in the plot by scaling up the stroke and reducing the fill
/// transparency.
/// Highlight this polygon in the plot by scaling up the stroke and reducing
/// the fill transparency.
#[must_use]
pub fn highlight(mut self) -> Self {
self.highlight = true;
@ -50,7 +52,8 @@ impl Polygon {
self
}
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will
/// be auto-assigned.
#[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.stroke.color = color.into();
@ -75,8 +78,8 @@ impl Polygon {
///
/// This name will show up in the plot legend, if legends are turned on.
///
/// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend.
/// Multiple plot items may share the same name, in which case they will
/// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn name(mut self, name: impl ToString) -> Self {

View File

@ -1,10 +1,12 @@
use std::ops::RangeInclusive;
use egui::{Align2, Rect, TextStyle, Ui};
use epaint::{Color32, Shape, Stroke};
use crate::items::plot_item::PlotItem;
use crate::items::value::Value;
use crate::items::values::Values;
use crate::transform::{Bounds, ScreenTransform};
use egui::{Align2, Rect, Ui};
use epaint::{Color32, Shape, Stroke, TextStyle};
use std::ops::RangeInclusive;
/// Text inside the plot.
pub struct Text {
@ -45,7 +47,8 @@ impl Text {
self
}
/// Text color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
/// Text color. Default is `Color32::TRANSPARENT` which means a color will
/// be auto-assigned.
#[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.color = color.into();
@ -63,8 +66,8 @@ impl Text {
///
/// This name will show up in the plot legend, if legends are turned on.
///
/// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend.
/// Multiple plot items may share the same name, in which case they will
/// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn name(mut self, name: impl ToString) -> Self {
@ -80,10 +83,11 @@ impl PlotItem for Text {
} else {
self.color
};
let fond_id = ui.style().text_styles.get(&self.style).unwrap();
let pos = transform.position_from_value(&self.position);
let galley = ui
.fonts()
.layout_no_wrap(self.text.clone(), self.style, color);
.layout_no_wrap(self.text.clone(), fond_id.clone(), color);
let rect = self
.anchor
.anchor_rect(Rect::from_min_size(pos, galley.size()));

View File

@ -1,11 +1,13 @@
use std::ops::RangeInclusive;
use egui::Ui;
use epaint::{Color32, Shape, Stroke};
use crate::items::plot_item::PlotItem;
use crate::items::value::Value;
use crate::items::values::Values;
use crate::items::LineStyle;
use crate::transform::{Bounds, ScreenTransform};
use egui::Ui;
use epaint::{Color32, Shape, Stroke};
use std::ops::RangeInclusive;
impl PlotItem for VLine {
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
@ -95,7 +97,8 @@ impl VLine {
self
}
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will
/// be auto-assigned.
#[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.stroke.color = color.into();
@ -113,8 +116,8 @@ impl VLine {
///
/// This name will show up in the plot legend, if legends are turned on.
///
/// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend.
/// Multiple plot items may share the same name, in which case they will
/// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn name(mut self, name: impl ToString) -> Self {

View File

@ -4,8 +4,8 @@
/// large values (e.g. unix time on x axis).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Value {
/// This is often something monotonically increasing, such as time, but doesn't have to be.
/// Goes from left to right.
/// This is often something monotonically increasing, such as time, but
/// doesn't have to be. Goes from left to right.
pub x: f64,
/// Goes from bottom to top (inverse of everything else in egui!).
pub y: f64,

View File

@ -1,8 +1,9 @@
use crate::items::{ExplicitGenerator, Value};
use crate::transform::Bounds;
use std::collections::Bound;
use std::ops::{RangeBounds, RangeInclusive};
use crate::items::{ExplicitGenerator, Value};
use crate::transform::Bounds;
pub struct Values {
pub values: Vec<Value>,
generator: Option<ExplicitGenerator>,
@ -20,7 +21,8 @@ impl Values {
Self::from_values(iter.collect())
}
/// Draw a line based on a function `y=f(x)`, a range (which can be infinite) for x and the number of points.
/// Draw a line based on a function `y=f(x)`, a range (which can be
/// infinite) for x and the number of points.
pub fn from_explicit_callback(
function: impl Fn(f64) -> f64 + 'static,
x_range: impl RangeBounds<f64>,
@ -48,8 +50,9 @@ impl Values {
}
}
/// Draw a line based on a function `(x,y)=f(t)`, a range for t and the number of points.
/// The range may be specified as start..end or as start..=end.
/// Draw a line based on a function `(x,y)=f(t)`, a range for t and the
/// number of points. The range may be specified as start..end or as
/// start..=end.
pub fn from_parametric_callback(
function: impl Fn(f64) -> (f64, f64),
t_range: impl RangeBounds<f64>,
@ -92,13 +95,14 @@ impl Values {
Self::from_values(values)
}
/// Returns true if there are no data points available and there is no function to generate any.
/// Returns true if there are no data points available and there is no
/// function to generate any.
pub fn is_empty(&self) -> bool {
self.generator.is_none() && self.values.is_empty()
}
/// If initialized with a generator function, this will generate `n` evenly spaced points in the
/// given range.
/// If initialized with a generator function, this will generate `n` evenly
/// spaced points in the given range.
pub fn generate_points(&mut self, x_range: RangeInclusive<f64>) {
if let Some(generator) = self.generator.take() {
if let Some(intersection) = Self::range_intersection(&x_range, &generator.x_range) {

View File

@ -117,7 +117,8 @@ impl Bounds {
}
}
/// Contains the screen rectangle and the plot bounds and provides methods to transform them.
/// Contains the screen rectangle and the plot bounds and provides methods to
/// transform them.
#[derive(Clone)]
pub struct ScreenTransform {
/// The screen rectangle.

View File

@ -1,12 +1,13 @@
use amdgpu::helper_cmd::Command;
use amdgpu::pidfile::helper_cmd::{send_command, Command, Response};
use amdgpu_config::fan::MatrixPoint;
use egui::{emath, pos2, Layout, PointerButton, Ui};
use epaint::Color32;
use crate::app::{ChangeState, FanConfig, FanServices, StatefulConfig};
use crate::widgets;
use crate::widgets::drag_plot::PlotMsg;
use crate::widgets::reload_section::ReloadSection;
use crate::{widgets, widgets::ConfigFile};
use crate::widgets::ConfigFile;
pub struct ChangeFanSettings {
config: FanConfig,
@ -139,10 +140,10 @@ impl ChangeFanSettings {
ui.label("Saving...");
}
ChangeState::Success => {
ui.add(egui::Label::new("Saved").text_color(Color32::GREEN));
ui.add(egui::Label::new("Saved")/*.text_color(Color32::GREEN)*/);
}
ChangeState::Failure(msg) => {
ui.add(egui::Label::new(format!("Failure. {}", msg)).text_color(Color32::RED));
ui.add(egui::Label::new(format!("Failure. {}", msg))/*.text_color(Color32::RED)*/);
}
}
});
@ -164,11 +165,11 @@ impl ChangeFanSettings {
path: String::from(config.path()),
content,
};
match amdgpu::helper_cmd::send_command(command) {
Ok(amdgpu::helper_cmd::Response::ConfigFileSaveFailed(msg)) => {
match send_command(command) {
Ok(Response::ConfigFileSaveFailed(msg)) => {
state.state = ChangeState::Failure(msg);
}
Ok(amdgpu::helper_cmd::Response::ConfigFileSaved) => {
Ok(Response::ConfigFileSaved) => {
state.state = ChangeState::Success;
}
_ => {}

View File

@ -1,6 +1,5 @@
use egui::{PointerButton, Response, Sense, Ui, Widget};
use amdgpu_config::fan::MatrixPoint;
use egui::{PointerButton, Response, Sense, Ui, Widget};
use crate::app::FanConfig;

View File

@ -1,11 +1,13 @@
use crate::app::{FanConfig, FanServices};
use amdgpu::Card;
use amdmond_lib::AmdMon;
use core::option::Option;
use core::option::Option::Some;
use egui::Ui;
use std::collections::vec_deque::VecDeque;
use amdgpu::Card;
use amdmond_lib::AmdMon;
use egui::Ui;
use crate::app::{FanConfig, FanServices};
pub struct CoolingPerformance {
capacity: usize,
data: VecDeque<f64>,
@ -55,18 +57,20 @@ impl CoolingPerformance {
.name("Overheating")
.color(Color32::DARK_RED);
let plot = Plot::new("cooling performance")
ui.label("Temperature");
Plot::new("cooling performance")
.allow_drag(false)
.allow_zoom(false)
.height(600.0)
.line(curve)
.hline(zero)
.hline(optimal)
.hline(target)
.legend(Legend::default());
.show(ui, |plot_ui| {
plot_ui.line(curve);
plot_ui.hline(zero);
plot_ui.hline(optimal);
plot_ui.hline(target);
// plot_ui.legend(Legend::default());
});
ui.label("Temperature");
ui.add(plot);
// ui.add(plot);
ui.horizontal(|ui| {
ui.label("Current temperature");
ui.label(format!("{:<3.2}°C", current));

View File

@ -1,10 +1,9 @@
use egui::{emath, vec2, CursorIcon, Id, NumExt, PointerButton, Response, Sense, Ui, Vec2};
use epaint::ahash::AHashSet;
use epaint::color::Hsva;
use epaint::Color32;
use epaint::{Color32, Rounding};
use crate::items::HLine;
use crate::items::*;
use crate::items::{HLine, *};
use crate::transform::{Bounds, ScreenTransform};
use crate::widgets::drag_plot_prepared::DragPlotPrepared;
use crate::widgets::legend::Legend;
@ -16,7 +15,8 @@ pub enum PlotMsg {
Drag(emath::Vec2),
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
// , serde::Serialize, serde::Deserialize
#[derive(Clone)]
struct PlotMemory {
bounds: Bounds,
auto_bounds: bool,
@ -125,12 +125,13 @@ where
self.next_auto_color_idx += 1;
let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875
let h = i as f32 * golden_ratio;
Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO: OkLab or some other perspective color space
Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO: OkLab or some other
// perspective color space
}
/// width / height ratio of the data.
/// For instance, it can be useful to set this to `1.0` for when the two axes show the same
/// unit.
/// For instance, it can be useful to set this to `1.0` for when the two
/// axes show the same unit.
/// By default the plot window's aspect ratio is used.
#[must_use]
pub fn data_aspect(mut self, data_aspect: f32) -> Self {
@ -139,7 +140,8 @@ where
}
/// width / height ratio of the plot region.
/// By default no fixed aspect ratio is set (and width/height will fill the ui it is in).
/// By default no fixed aspect ratio is set (and width/height will fill the
/// ui it is in).
#[must_use]
pub fn view_aspect(mut self, view_aspect: f32) -> Self {
self.view_aspect = Some(view_aspect);
@ -147,7 +149,8 @@ where
}
/// Width of plot. By default a plot will fill the ui it is in.
/// If you set [`Self::view_aspect`], the width can be calculated from the height.
/// If you set [`Self::view_aspect`], the width can be calculated from the
/// height.
#[must_use]
pub fn width(mut self, width: f32) -> Self {
self.min_size.x = width;
@ -156,7 +159,8 @@ where
}
/// Height of plot. By default a plot will fill the ui it is in.
/// If you set [`Self::view_aspect`], the height can be calculated from the width.
/// If you set [`Self::view_aspect`], the height can be calculated from the
/// width.
#[must_use]
pub fn height(mut self, height: f32) -> Self {
self.min_size.y = height;
@ -240,9 +244,9 @@ where
} = self;
let plot_id = ui.make_persistent_id(id);
let memory = ui
.memory()
.id_data
.get_mut_or_insert_with(plot_id, || PlotMemory {
.ctx()
.data()
.get_persisted_mut_or_insert_with(plot_id, || PlotMemory {
bounds: min_auto_bounds,
auto_bounds: false,
hovered_entry: None,
@ -284,11 +288,11 @@ where
};
let (rect, response) = ui.allocate_exact_size(size, Sense::click_and_drag());
let plot_painter = ui.painter().sub_region(rect);
let plot_painter = ui.painter().with_clip_rect(rect);
plot_painter.add(epaint::RectShape {
rect,
corner_radius: 2.0,
rounding: Rounding::from(2.0),
fill: ui.visuals().extreme_bg_color,
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
});
@ -408,16 +412,15 @@ where
hovered_entry = legend.get_hovered_entry_name();
}
ui.memory().id_data.insert(
plot_id,
PlotMemory {
ui.ctx()
.data()
.get_persisted_mut_or_insert_with(plot_id, || PlotMemory {
bounds: t_bounds,
auto_bounds,
hovered_entry,
hidden_items,
min_auto_bounds,
},
);
});
response.on_hover_cursor(CursorIcon::Crosshair)
}
}

View File

@ -1,8 +1,7 @@
use egui::{emath, pos2, remap_clamp, vec2, Align2, Layout, NumExt, Pos2, Response, Ui};
use epaint::{Color32, Rgba, Shape, Stroke, TextStyle};
use egui::{emath, pos2, remap_clamp, vec2, Align2, Layout, NumExt, Pos2, Response, TextStyle, Ui};
use epaint::{Color32, Rgba, Shape, Stroke};
use crate::items::Value;
use crate::items::{Line, PlotItem};
use crate::items::{Line, PlotItem, Value};
use crate::transform::ScreenTransform;
pub struct DragPlotPrepared {
@ -41,7 +40,9 @@ impl DragPlotPrepared {
self.hover(ui, pointer, &mut shapes);
}
ui.painter().sub_region(*transform.frame()).extend(shapes);
ui.painter()
.with_clip_rect(*transform.frame())
.extend(shapes);
}
fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
@ -77,9 +78,12 @@ impl DragPlotPrepared {
let n = (value_main / step_size).round() as i64;
let spacing_in_points = if n % (base * base) == 0 {
step_size_in_points * (base_f * base_f) as f32 // think line (multiple of 100)
step_size_in_points * (base_f * base_f) as f32 // think line
// (multiple of
// 100)
} else if n % base == 0 {
step_size_in_points * base_f as f32 // medium line (multiple of 10)
step_size_in_points * base_f as f32 // medium line (multiple of
// 10)
} else {
step_size_in_points // thin line
};
@ -106,7 +110,11 @@ impl DragPlotPrepared {
let color = color_from_alpha(ui, text_alpha);
let text = emath::round_to_decimals(value_main, 5).to_string(); // hack
let galley = ui.painter().layout_no_wrap(text, text_style, color);
let galley = ui.painter().layout_no_wrap(
text,
ui.style().text_styles.get(&text_style).cloned().unwrap(),
color,
);
let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
@ -135,8 +143,7 @@ impl DragPlotPrepared {
let interact_radius: f32 = 16.0;
let mut closest_value = None;
let mut closest_dist_sq = interact_radius.powi(2);
for item in lines {
if let Some(values) = item.values() {
for values in lines.iter().filter_map(|v| v.values()) {
for (idx, value) in values.values.iter().enumerate() {
let pos = transform.position_from_value(value);
let dist_sq = pointer.distance_sq(pos);
@ -146,7 +153,6 @@ impl DragPlotPrepared {
}
}
}
}
closest_value
}
@ -255,11 +261,15 @@ impl DragPlotPrepared {
};
shapes.push(Shape::text(
ui.fonts(),
&*ui.fonts(),
pointer + vec2(3.0, -2.0),
Align2::LEFT_BOTTOM,
text,
TextStyle::Body,
ui.style()
.text_styles
.get(&TextStyle::Body)
.cloned()
.unwrap(),
ui.visuals().text_color(),
));
}

View File

@ -1,7 +1,9 @@
use std::string::String;
use egui::{pos2, vec2, Align, PointerButton, Rect, Response, Sense, WidgetInfo, WidgetType};
use epaint::{Color32, TextStyle};
use egui::{
pos2, vec2, Align, PointerButton, Rect, Response, Sense, TextStyle, WidgetInfo, WidgetType,
};
use epaint::Color32;
/// Where to place the plot legend.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -26,7 +28,7 @@ impl Corner {
}
/// The configuration for a plot legend.
#[derive(Clone, Copy, PartialEq)]
#[derive(Clone, PartialEq)]
pub struct Legend {
pub text_style: TextStyle,
pub background_alpha: f32,
@ -89,9 +91,15 @@ impl LegendEntry {
hovered,
} = self;
let galley =
ui.fonts()
.layout_delayed_color(text, ui.style().body_text_style, f32::INFINITY);
let galley = ui.fonts().layout_delayed_color(
text,
ui.style()
.text_styles
.get(&TextStyle::Body)
.unwrap()
.clone(),
f32::INFINITY,
);
let icon_size = galley.size().y;
let icon_spacing = icon_size / 5.0;

View File

@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use egui::{vec2, Align, Direction, Frame, Layout, Rect, Response, Ui, Widget};
use egui::style::Margin;
use egui::{Align, Direction, Frame, Layout, Rect, Response, Ui, Widget};
use epaint::ahash::AHashSet;
use epaint::Color32;
@ -15,8 +16,8 @@ pub struct LegendWidget {
}
impl LegendWidget {
/// Create a new legend from items, the names of items that are hidden and the style of the
/// text. Returns `None` if the legend has no entries.
/// Create a new legend from items, the names of items that are hidden and
/// the style of the text. Returns `None` if the legend has no entries.
pub fn try_new(
rect: Rect,
config: Legend,
@ -89,10 +90,11 @@ impl Widget for &mut LegendWidget {
let mut legend_ui = ui.child_ui(legend_rect, layout);
legend_ui
.scope(|ui| {
ui.style_mut().body_text_style = config.text_style;
// ui.style_mut().body_text_style = config.text_style;
let background_frame = Frame {
margin: vec2(8.0, 4.0),
corner_radius: ui.style().visuals.window_corner_radius,
inner_margin: Margin::symmetric(8.0, 4.0),
outer_margin: Default::default(),
rounding: ui.style().visuals.window_rounding,
shadow: epaint::Shadow::default(),
fill: ui.style().visuals.extreme_bg_color,
stroke: ui.style().visuals.window_stroke(),

View File

@ -5,6 +5,8 @@ pub mod drag_plot;
pub mod drag_plot_prepared;
pub mod legend;
pub mod legend_widget;
pub mod output_widget;
pub mod outputs_settings;
pub mod reload_section;
pub use change_fan_settings::*;

View File

@ -0,0 +1,69 @@
use std::collections::HashMap;
use amdgpu::pidfile::ports::Output;
use egui::{Response, Sense, Ui, Vec2};
use epaint::Stroke;
pub struct OutputWidget<'output, 'textures> {
output: &'output Output,
textures: &'textures HashMap<String, egui::TextureHandle>,
}
impl<'output, 'textures> OutputWidget<'output, 'textures> {
pub fn new(
output: &'output Output,
textures: &'textures HashMap<String, egui::TextureHandle>,
) -> Self {
Self { output, textures }
}
}
impl<'output, 'textures> egui::Widget for OutputWidget<'output, 'textures> {
fn ui(self, ui: &mut Ui) -> Response {
let (rect, res) = ui.allocate_exact_size(Vec2::new(80.0, 70.0), Sense::click());
// let _transform = ScreenTransform::new(rect, Bounds::new_symmetrical(1.0),
// false, false); let _frame = *transform.frame();
// eprintln!("min {:?} max {:?}", frame.min, frame.max);
let painter = ui.painter(); //.sub_region(*transform.frame());
painter.rect_filled(rect, 0.0, epaint::color::Color32::DARK_RED);
painter.rect(rect, 2.0, epaint::color::Color32::DARK_RED, {
let mut s = Stroke::default();
s.color = epaint::color::Color32::GREEN;
s.width = 1.0;
s
});
let rect_middle_point = (rect.max - rect.min) / 2.0;
painter.circle_filled(
rect.min + Vec2::new(rect_middle_point.x / 2.0, rect_middle_point.y),
3.0,
epaint::color::Color32::GREEN,
);
painter.circle_filled(
rect.min + rect_middle_point,
3.0,
epaint::color::Color32::GREEN,
);
painter.circle_filled(
rect.min
+ Vec2::new(
rect_middle_point.x + (rect_middle_point.x / 2.0),
rect_middle_point.y,
),
3.0,
epaint::color::Color32::GREEN,
);
res
}
}
fn icon(name: &str, textures: &HashMap<String, egui::TextureHandle>) {
if let Some(_texture) = textures.get(name) {
//
} else {
//
}
}

View File

@ -0,0 +1,86 @@
use std::collections::{BTreeMap, HashMap};
use amdgpu::pidfile::ports::{Output, Status};
use egui::Ui;
use epaint::ColorImage;
use image::ImageFormat;
use crate::app::StatefulConfig;
use crate::widgets::output_widget::OutputWidget;
#[derive(Default)]
pub struct OutputsSettings {
textures: HashMap<String, egui::TextureHandle>,
}
impl OutputsSettings {
pub fn draw(
&mut self,
ui: &mut Ui,
_state: &mut StatefulConfig,
outputs: &BTreeMap<String, Vec<Output>>,
) {
outputs.values().flatten().for_each(|output| {
// 160x160
let image = {
let bytes = include_bytes!("../../assets/icons/ports.jpg");
image::load_from_memory(bytes).unwrap()
};
for (_idx, _pixel, _p) in image.to_rgba8().enumerate_pixels() {
// let bytes = pixel.;
// eprintln!("{:?}", bytes);
}
if !self.textures.contains_key(&output.port_type) {
let img = image::load_from_memory_with_format(
include_bytes!("../../assets/icons/ports.jpg"),
ImageFormat::Jpeg,
)
.unwrap();
let image_buffer = img.to_rgba8();
let size = [image.width() as _, image.height() as _];
let pixels = image_buffer.as_flat_samples();
let _ = ui.ctx().load_texture(
output.port_type.clone(),
ColorImage::from_rgba_unmultiplied(size, pixels.as_slice()),
);
}
});
let _available = ui.available_rect_before_wrap();
ui.vertical(|ui| {
ui.horizontal_top(|ui| {
outputs.iter().for_each(|(name, outputs)| {
ui.vertical(|ui| {
ui.label(format!("name {name}"));
ui.horizontal_top(|ui| {
outputs.iter().for_each(|output| {
ui.vertical(|ui| {
ui.add(OutputWidget::new(output, &self.textures));
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()
));
ui.label(match output.status {
Status::Connected => "Connected",
Status::Disconnected => "Disconnected",
});
});
});
});
});
});
});
// eprintln!("==============================================================");
});
}
}
fn vga_port_icon() {
// Texture
}

View File

@ -1,7 +1,7 @@
use crate::app::{ChangeState, FanServices};
use amdgpu::helper_cmd::Command;
use amdgpu::pidfile::helper_cmd::Command;
use egui::{PointerButton, Response, Sense, Ui};
use epaint::Color32;
use crate::app::{ChangeState, FanServices};
pub struct ReloadSection<'l> {
pub services: &'l mut FanServices,
@ -18,7 +18,7 @@ impl<'l> egui::Widget for ReloadSection<'l> {
if ui.button("Reload").clicked_by(PointerButton::Primary) {
service.reload = ChangeState::Reloading;
match amdgpu::helper_cmd::send_command(Command::ReloadConfig {
match amdgpu::pidfile::helper_cmd::send_command(Command::ReloadConfig {
pid: service.pid,
}) {
Ok(response) => {
@ -37,12 +37,13 @@ impl<'l> egui::Widget for ReloadSection<'l> {
ui.label("Reloading...");
}
ChangeState::Success => {
ui.add(egui::Label::new("Reloaded").text_color(Color32::DARK_GREEN));
ui.add(
egui::Label::new("Reloaded"), /* .text_color(Color32::DARK_GREEN) */
);
}
ChangeState::Failure(msg) => {
ui.add(
egui::Label::new(format!("Failure. {}", msg))
.text_color(Color32::RED),
egui::Label::new(format!("Failure. {}", msg)), // .text_color(Color32::RED),
);
}
}

View File

@ -1,6 +1,5 @@
pub mod errors;
use crate::errors::AmdMonError;
use amdgpu::hw_mon::HwMon;
use amdgpu::utils::load_temp_inputs;
use amdgpu::{
@ -8,6 +7,8 @@ use amdgpu::{
};
use amdgpu_config::fan;
use crate::errors::AmdMonError;
pub type Result<T> = std::result::Result<T, AmdMonError>;
pub struct AmdMon {

View File

@ -2,11 +2,11 @@ mod command;
mod log_file;
mod watch;
use amdgpu::utils::ensure_config_dir;
use amdgpu_config::monitor::{load_config, Config, DEFAULT_MONITOR_CONFIG_PATH};
use gumdrop::Options;
use crate::command::Command;
use amdgpu::utils::ensure_config_dir;
use amdgpu_config::monitor::{load_config, Config, DEFAULT_MONITOR_CONFIG_PATH};
#[derive(gumdrop::Options)]
pub struct Opts {

View File

@ -1,13 +1,12 @@
use amdgpu::ports::{sock_file, Command, Response};
use amdgpu::{ports::*, IoFailure};
use std::fs::{DirEntry, Permissions};
use std::io::{Read, Write};
use std::net::Shutdown;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::{UnixListener, UnixStream};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use amdgpu::pidfile::ports::{sock_file, Command, Response, *};
use amdgpu::IoFailure;
fn parse_output(entry: DirEntry) -> Option<Output> {
let ty = entry.file_type().ok()?;
if ty.is_dir() {
@ -60,45 +59,7 @@ async fn read_outputs(state: Arc<Mutex<Vec<Output>>>) {
}
}
pub struct Service(UnixStream);
impl Service {
/// Serialize and send command
pub fn write_response(&mut self, res: Response) {
match ron::to_string(&res) {
Ok(buffer) => match self.0.write_all(buffer.as_bytes()) {
Ok(_) => {
log::info!("Response successfully written")
}
Err(e) => log::warn!("Failed to write response. {:?}", e),
},
Err(e) => {
log::warn!("Failed to serialize response {:?}. {:?}", res, e)
}
}
}
/// Read from `.sock` file new line separated commands
pub fn read_command(&mut self) -> Option<String> {
let mut command = String::with_capacity(100);
log::info!("Reading stream...");
read_line(&mut self.0, &mut command);
if command.is_empty() {
return None;
}
Some(command)
}
/// Close connection with no operation response
pub fn kill(mut self) {
self.write_response(Response::NoOp);
self.close();
}
pub fn close(self) {
let _ = self.0.shutdown(Shutdown::Both);
}
}
pub type Service = amdgpu::pidfile::Service<Response>;
async fn service(state: Arc<Mutex<Vec<Output>>>) {
let sock_path = sock_file();
@ -121,27 +82,8 @@ async fn service(state: Arc<Mutex<Vec<Output>>>) {
}
}
fn read_line(stream: &mut UnixStream, command: &mut String) {
let mut buffer = [0];
while stream.read_exact(&mut buffer).is_ok() {
if buffer[0] == b'\n' {
break;
}
match std::str::from_utf8(&buffer) {
Ok(s) => {
command.push_str(s);
}
Err(e) => {
log::error!("Failed to read from client. {:?}", e);
let _ = stream.shutdown(Shutdown::Both);
continue;
}
}
}
}
fn handle_connection(stream: UnixStream, state: Arc<Mutex<Vec<Output>>>) {
let mut service = Service(stream);
let mut service = Service::new(stream);
let command = match service.read_command() {
Some(s) => s,

View File

@ -1,7 +1,6 @@
use gumdrop::Options;
use amdgpu::utils::ensure_config_dir;
use amdgpu_config::voltage::{load_config, Config};
use gumdrop::Options;
use crate::command::VoltageCommand;
use crate::error::VoltageError;

7
rustfmt.toml Normal file
View File

@ -0,0 +1,7 @@
imports_granularity = "Module"
group_imports = "StdExternalCrate"
reorder_modules = true
reorder_imports = true
use_field_init_shorthand = true
wrap_comments = true
edition = "2021"