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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
use gumdrop::Options;
use amdgpu::utils::hw_mons; use amdgpu::utils::hw_mons;
use amdgpu::{config_reloaded, is_reload_required, listen_unix_signal}; use amdgpu::{config_reloaded, is_reload_required, listen_unix_signal};
use amdgpu_config::fan::Config; use amdgpu_config::fan::Config;
use gumdrop::Options;
use crate::command::Fan; use crate::command::Fan;
use crate::AmdFanError; 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<()> { pub fn run(mut config: Config) -> crate::Result<()> {
listen_unix_signal(); listen_unix_signal();

View File

@ -28,7 +28,8 @@ impl MatrixPoint {
pub struct Config { pub struct Config {
#[serde(skip)] #[serde(skip)]
path: String, 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!) /// If nothing is provided higher reading will be taken (this is not good!)
temp_input: Option<TempInput>, temp_input: Option<TempInput>,
log_level: LogLevel, log_level: LogLevel,
@ -242,9 +243,8 @@ pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
#[cfg(test)] #[cfg(test)]
mod parse_config { mod parse_config {
use serde::Deserialize;
use amdgpu::{AmdGpuError, Card, TempInput}; use amdgpu::{AmdGpuError, Card, TempInput};
use serde::Deserialize;
#[derive(Deserialize, PartialEq, Eq, Debug)] #[derive(Deserialize, PartialEq, Eq, Debug)]
pub struct Foo { pub struct Foo {

View File

@ -1,7 +1,6 @@
use serde::{Deserialize, Serialize};
use amdgpu::utils::ensure_config; use amdgpu::utils::ensure_config;
use amdgpu::LogLevel; use amdgpu::LogLevel;
use serde::{Deserialize, Serialize};
pub static DEFAULT_MONITOR_CONFIG_PATH: &str = "/etc/amdfand/monitor.toml"; 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 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 struct IoFailure {
pub io: std::io::Error, pub io: std::io::Error,
pub path: std::path::PathBuf, pub path: std::path::PathBuf,

View File

@ -1,16 +1,14 @@
use serde::{Deserialize, Serialize};
pub use card::*; pub use card::*;
pub use error::*; pub use error::*;
use serde::{Deserialize, Serialize};
pub use temp_input::*; pub use temp_input::*;
mod card; mod card;
mod error; mod error;
#[cfg(feature = "gui-helper")] #[cfg(feature = "gui-helper")]
pub mod helper_cmd;
pub mod hw_mon; pub mod hw_mon;
pub mod lock_file; pub mod lock_file;
pub mod ports; pub mod pidfile;
mod temp_input; mod temp_input;
pub mod utils; pub mod utils;
@ -28,7 +26,9 @@ pub static PULSE_WIDTH_MODULATION_MAX: &str = "pwm1_max";
/// pulse width modulation fan level (0-255) /// pulse width modulation fan level (0-255)
pub static PULSE_WIDTH_MODULATION: &str = "pwm1"; 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"; pub static PULSE_WIDTH_MODULATION_MODE: &str = "pwm1_enable";
// static PULSE_WIDTH_MODULATION_DISABLED: &str = "0"; // static PULSE_WIDTH_MODULATION_DISABLED: &str = "0";

View File

@ -1,11 +1,13 @@
//! Create lock file and prevent running 2 identical services. //! Create lock file and prevent running 2 identical services.
//! NOTE: For 2 amdfand services you may just give 2 different names //! 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 std::path::Path;
use nix::libc;
use crate::pidfile::Pid;
use crate::IoFailure;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum LockFileError { pub enum LockFileError {
#[error("Failed to read {path}. {err:?}")] #[error("Failed to read {path}. {err:?}")]
@ -56,7 +58,8 @@ impl PidLock {
/// Create new lock file. File will be created if: /// Create new lock file. File will be created if:
/// * pid file does not exists /// * pid file does not exists
/// * pid file exists but process is dead /// * 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> { pub fn acquire(&mut self) -> Result<(), crate::error::AmdGpuError> {
log::debug!("PID LOCK acquiring {}", self.pid_path); log::debug!("PID LOCK acquiring {}", self.pid_path);
let pid = self.process_pid(); let pid = self.process_pid();
@ -84,7 +87,7 @@ impl PidLock {
log::warn!("Conflicting {} and {} for process {}", old.0, pid.0, name); log::warn!("Conflicting {} and {} for process {}", old.0, pid.0, name);
return Err(LockFileError::Conflict { pid: old, name }.into()); return Err(LockFileError::Conflict { pid: old, name }.into());
} }
Ok(name /*name isn't the same*/) => { Ok(name /* name isn't the same */) => {
log::debug!( log::debug!(
"Old process {:?} and current process {:?} have different names, overriding....", "Old process {:?} and current process {:?} have different names, overriding....",
name, self.name name, self.name

View File

@ -1,9 +1,10 @@
//! AMD GUI helper communication toolkit //! AMD GUI helper communication toolkit
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::ops::Deref;
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use crate::pidfile::{Pid, PidResponse};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum GuiHelperError { pub enum GuiHelperError {
#[error("GUI Helper socket file not found. Is service running?")] #[error("GUI Helper socket file not found. Is service running?")]
@ -14,18 +15,6 @@ pub enum GuiHelperError {
Serialize(#[from] ron::Error), 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)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum Command { pub enum Command {
ReloadConfig { pid: Pid }, ReloadConfig { pid: Pid },
@ -41,6 +30,12 @@ pub enum Response {
ConfigFileSaveFailed(String), ConfigFileSaveFailed(String),
} }
impl PidResponse for Response {
fn kill_response() -> Self {
Self::NoOp
}
}
pub fn sock_file() -> std::path::PathBuf { pub fn sock_file() -> std::path::PathBuf {
std::path::Path::new("/tmp").join("amdgui-helper.sock") 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 //! AMD GUI helper communication toolkit
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::ops::Deref;
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use std::path::PathBuf; use std::path::PathBuf;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::pidfile::PidResponse;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum PortsError { pub enum PortsError {
#[error("AMD GPU ports socket file not found. Is service running?")] #[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)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum Command { pub enum Command {
Ports, Ports,
@ -97,6 +86,12 @@ pub enum Response {
NoOp, NoOp,
} }
impl PidResponse for Response {
fn kill_response() -> Self {
Self::NoOp
}
}
pub fn sock_file() -> PathBuf { pub fn sock_file() -> PathBuf {
std::path::Path::new("/tmp").join("amdgpu-ports.sock") std::path::Path::new("/tmp").join("amdgpu-ports.sock")
} }

View File

@ -1,6 +1,7 @@
use crate::AmdGpuError;
use serde::Serializer; use serde::Serializer;
use crate::AmdGpuError;
#[derive(PartialEq, Eq, Debug, Copy, Clone)] #[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub struct TempInput(pub u16); 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 //! Special daemon with root privileges. Since GUI should not have (and
//! privileges and service processes are designed to be as small as possible this is proxy. //! sometimes can't have) root privileges and service processes are designed to
//! be as small as possible this is proxy.
//! //!
//! It is responsible for: //! 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 //! * Reload amdfand process with signal SIGHUP
//! * Save changed config file //! * Save changed config file
//! //!
//! It is using `/tmp/amdgui-helper.sock` file and `ron` serialization for communication. //! It is using `/tmp/amdgui-helper.sock` file and `ron` serialization for
//! After each operation connection is terminated so each command needs new connection. //! communication. After each operation connection is terminated so each command
//! needs new connection.
#![allow(clippy::non_octal_unix_permissions)] #![allow(clippy::non_octal_unix_permissions)]
use amdgpu::helper_cmd::{Command, Pid, Response};
use amdgpu::IoFailure;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs::Permissions; use std::fs::Permissions;
use std::io::{Read, Write};
use std::net::Shutdown;
use std::os::unix::fs::PermissionsExt; 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)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -38,7 +41,7 @@ fn main() -> Result<()> {
let mut lock = amdgpu::lock_file::PidLock::new("amdgui", String::from("helper"))?; let mut lock = amdgpu::lock_file::PidLock::new("amdgui", String::from("helper"))?;
lock.acquire()?; lock.acquire()?;
let sock_path = amdgpu::helper_cmd::sock_file(); let sock_path = amdgpu::pidfile::helper_cmd::sock_file();
let listener = { let listener = {
let _ = std::fs::remove_file(&sock_path); let _ = std::fs::remove_file(&sock_path);
@ -52,86 +55,27 @@ fn main() -> Result<()> {
} }
while let Ok((stream, _addr)) = listener.accept() { while let Ok((stream, _addr)) = listener.accept() {
handle_connection(stream); handle_connection::<_, Command, Response>(stream, handle_command);
} }
lock.release()?; lock.release()?;
Ok(()) Ok(())
} }
pub struct Service(UnixStream); pub type Service = amdgpu::pidfile::Service<Response>;
impl Service { fn handle_command(service: Service, cmd: Command) {
/// 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) {
match cmd { match cmd {
Command::ReloadConfig { pid } => { Command::ReloadConfig { pid } => {
log::info!("Reloading config file for pid {:?}", pid); log::info!("Reloading config file for pid {:?}", pid);
handle_reload_config(service, pid); handle_reload_config(service, pid);
} }
Command::FanServices => handle_fan_services(service), Command::FanServices => handle_fan_services(service),
Command::SaveFanConfig { path, content } => { Command::SaveFanConfig { path, content } => handle_save_fan_config(service, path, content),
handle_save_fan_config(&mut 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) { match std::fs::write(path, content) {
Err(e) => service.write_response(Response::ConfigFileSaveFailed(format!("{:?}", e))), Err(e) => service.write_response(Response::ConfigFileSaveFailed(format!("{:?}", e))),
Ok(..) => service.write_response(Response::ConfigFileSaved), Ok(..) => service.write_response(Response::ConfigFileSaved),
@ -145,25 +89,6 @@ fn handle_fan_services(mut service: Service) {
service.write_response(Response::Services(services)); 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) { fn handle_reload_config(service: Service, pid: Pid) {
unsafe { unsafe {
nix::libc::kill(pid.0, nix::sys::signal::Signal::SIGHUP as i32); 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" } log = { version = "0.4" }
pretty_env_logger = { version = "0.4" } pretty_env_logger = { version = "0.4" }
egui = { version = "0.15", optional = true } egui = { version = "0.18", optional = true, features = [] }
epaint = { version = "0.15", features = ["serialize"], optional = true } epaint = { version = "0.18", features = [], optional = true }
epi = { version = "0.15", optional = true } epi = { version = "0.17.0", optional = true }
winit = { version = "0.25", optional = true } winit = { version = "0.26", optional = true }
egui-winit = { version = "0.15", optional = true } egui-winit = { version = "0.18", optional = true }
# vulkan # vulkan
egui_vulkano = { version = "0.4", optional = true } egui_vulkano = { version = "0.8.0", optional = true }
vulkano-win = { version = "0.25", optional = true } vulkano-win = { version = "0.29.0", optional = true }
vulkano = { version = "0.25", optional = true } vulkano = { version = "0.29.0", optional = true }
vulkano-shaders = { version = "0.25", optional = true } vulkano-shaders = { version = "0.29.0", optional = true }
bytemuck = { version = "*" }
# xorg glium # xorg glium
glium = { version = "0.30", optional = true } glium = { version = "0.32.1", optional = true }
egui_glium = { version = "0.15", optional = true } egui_glium = { version = "0.18.0", optional = true }
# xorg glow # xorg glow
glutin = { version = "0.27", optional = true } glutin = { version = "0.29", optional = true }
glow = { version = "0.11", 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"] } 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] [dev-dependencies]
amdgpu = { path = "../amdgpu", version = "1.0", features = ["gui-helper"] } 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 std::collections::{BTreeMap, HashMap};
use amdgpu::helper_cmd::Pid; use std::sync::Arc;
use egui::{CtxRef, Ui};
use amdgpu::pidfile::ports::Output;
use amdgpu::pidfile::Pid;
use egui::Ui;
use epi::Frame; use epi::Frame;
use parking_lot::Mutex; 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 { pub enum ChangeState {
New, New,
@ -61,6 +65,7 @@ impl From<Vec<Pid>> for FanServices {
pub enum Page { pub enum Page {
Config, Config,
Monitoring, Monitoring,
Outputs,
Settings, Settings,
} }
@ -85,14 +90,16 @@ pub struct StatefulConfig {
pub struct AmdGui { pub struct AmdGui {
pub page: Page, pub page: Page,
pid_files: FanServices, pid_files: FanServices,
outputs: BTreeMap<String, Vec<Output>>,
cooling_performance: CoolingPerformance, cooling_performance: CoolingPerformance,
change_fan_settings: ChangeFanSettings, change_fan_settings: ChangeFanSettings,
outputs_settings: OutputsSettings,
config: StatefulConfig, config: StatefulConfig,
reload_pid_list_delay: u8, reload_pid_list_delay: u8,
} }
impl epi::App for AmdGui { 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 { fn name(&self) -> &str {
"AMD GUI" "AMD GUI"
@ -104,8 +111,10 @@ impl AmdGui {
Self { Self {
page: Default::default(), page: Default::default(),
pid_files: FanServices::from(vec![]), pid_files: FanServices::from(vec![]),
outputs: Default::default(),
cooling_performance: CoolingPerformance::new(100, config.clone()), cooling_performance: CoolingPerformance::new(100, config.clone()),
change_fan_settings: ChangeFanSettings::new(config.clone()), change_fan_settings: ChangeFanSettings::new(config.clone()),
outputs_settings: OutputsSettings::default(),
config: StatefulConfig { config: StatefulConfig {
config, config,
state: ChangeState::New, state: ChangeState::New,
@ -124,6 +133,10 @@ impl AmdGui {
self.cooling_performance.draw(ui, &self.pid_files); self.cooling_performance.draw(ui, &self.pid_files);
} }
Page::Settings => {} Page::Settings => {}
Page::Outputs => {
self.outputs_settings
.draw(ui, &mut self.config, &self.outputs);
}
} }
} }
@ -131,13 +144,15 @@ impl AmdGui {
self.cooling_performance.tick(); self.cooling_performance.tick();
if self.pid_files.0.is_empty() || self.reload_pid_list_delay.checked_sub(1).is_none() { 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; 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); self.pid_files = FanServices::from(services);
} }
Ok(amdgpu::helper_cmd::Response::Services(_services)) => { Ok(Response::Services(_services)) => {
// SKIP // SKIP
} }
Ok(res) => { Ok(res) => {
@ -147,6 +162,55 @@ impl AmdGui {
log::warn!("Failed to load amd fan services pid list. {:?}", e); 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 { } else {
self.reload_pid_list_delay -= 1; self.reload_pid_list_delay -= 1;
} }

View File

@ -1,10 +1,11 @@
use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
use glium::glutin;
use parking_lot::Mutex;
use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::mpsc::UnboundedReceiver;
use crate::app::AmdGui; use crate::app::AmdGui;
use crate::backend::create_ui; use crate::backend::create_ui;
use glium::glutin;
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
let window_builder = glutin::window::WindowBuilder::new() 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); 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 => { glutin::event::Event::LoopDestroyed => {
egui.destroy(&gl); egui.destroy(&gl);

View File

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

View File

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

View File

@ -2,10 +2,9 @@
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
pub use arrows::*;
use egui::Pos2; use egui::Pos2;
use epaint::{Color32, Shape, Stroke}; use epaint::{Color32, Shape, Stroke};
pub use arrows::*;
pub use h_line::*; pub use h_line::*;
pub use line::*; pub use line::*;
pub use marker_shape::*; 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 { pub struct ExplicitGenerator {
function: Box<dyn Fn(f64) -> f64>, function: Box<dyn Fn(f64) -> f64>,
x_range: RangeInclusive<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 /// Returns the x-coordinate of a possible intersection between a line segment
/// a horizontal line at the given y-coordinate. /// from `p1` to `p2` and a horizontal line at the given y-coordinate.
#[inline(always)] #[inline(always)]
pub fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option<f32> { pub fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option<f32> {
((p1.y > y && p2.y < y) || (p1.y < y && p2.y > y)) ((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::plot_item::PlotItem;
use crate::items::values::Values; use crate::items::values::Values;
use crate::transform::{Bounds, ScreenTransform}; use crate::transform::{Bounds, ScreenTransform};
use egui::Ui;
use epaint::{Color32, Shape};
use std::ops::RangeInclusive;
/// A set of arrows. /// A set of arrows.
pub struct 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::plot_item::PlotItem;
use crate::items::value::Value; use crate::items::value::Value;
use crate::items::values::Values; use crate::items::values::Values;
use crate::items::LineStyle; use crate::items::LineStyle;
use crate::transform::{Bounds, ScreenTransform}; 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 /// A horizontal line in a plot, filling the full width
#[derive(Clone, Debug, PartialEq)] #[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] #[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self { pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.stroke.color = color.into(); 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;
use crate::items::plot_item::PlotItem; use crate::items::plot_item::PlotItem;
use crate::items::value::Value; use crate::items::value::Value;
use crate::items::values::Values; use crate::items::values::Values;
use crate::items::{LineStyle, DEFAULT_FILL_ALPHA}; use crate::items::{LineStyle, DEFAULT_FILL_ALPHA};
use crate::transform::{Bounds, ScreenTransform}; 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 { impl PlotItem for Line {
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) { 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] #[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self { pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.stroke.color = color.into(); 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::plot_item::PlotItem;
use crate::items::value::Value; use crate::items::value::Value;
use crate::items::values::Values; use crate::items::values::Values;
use crate::transform::{Bounds, ScreenTransform}; 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. /// An image in the plot.
pub struct PlotImage { pub struct PlotImage {
@ -65,8 +67,8 @@ impl PlotImage {
/// ///
/// This name will show up in the plot legend, if legends are turned on. /// 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 /// Multiple plot items may share the same name, in which case they will
/// the legend. /// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
#[must_use] #[must_use]
pub fn name(mut self, name: impl ToString) -> Self { pub fn name(mut self, name: impl ToString) -> Self {

View File

@ -1,8 +1,10 @@
use crate::items::Values; use std::ops::RangeInclusive;
use crate::transform::{Bounds, ScreenTransform};
use egui::Ui; use egui::Ui;
use epaint::{Color32, Shape}; 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. /// Trait shared by things that can be drawn in the plot.
pub trait PlotItem { 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::marker_shape::MarkerShape;
use crate::items::plot_item::PlotItem; use crate::items::plot_item::PlotItem;
use crate::items::value::Value; use crate::items::value::Value;
use crate::items::values::Values; use crate::items::values::Values;
use crate::transform::{Bounds, ScreenTransform}; use crate::transform::{Bounds, ScreenTransform};
use egui::{pos2, vec2, Pos2, Ui};
use epaint::{Color32, Shape, Stroke};
use std::ops::RangeInclusive;
/// A set of points. /// A set of points.
pub struct Points { pub struct Points {
pub(crate) series: Values, pub(crate) series: Values,
pub(crate) shape: MarkerShape, 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, pub(crate) color: Color32,
/// Whether to fill the marker. Does not apply to all types. /// Whether to fill the marker. Does not apply to all types.
pub(crate) filled: bool, pub(crate) filled: bool,
@ -64,7 +67,8 @@ impl Points {
self 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] #[must_use]
pub fn stems(mut self, y_reference: impl Into<f32>) -> Self { pub fn stems(mut self, y_reference: impl Into<f32>) -> Self {
self.stems = Some(y_reference.into()); 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. /// 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 /// Multiple plot items may share the same name, in which case they will
/// the legend. /// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
#[must_use] #[must_use]
pub fn name(mut self, name: impl ToString) -> Self { 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::plot_item::PlotItem;
use crate::items::values::Values; use crate::items::values::Values;
use crate::items::{LineStyle, DEFAULT_FILL_ALPHA}; use crate::items::{LineStyle, DEFAULT_FILL_ALPHA};
use crate::transform::{Bounds, ScreenTransform}; use crate::transform::{Bounds, ScreenTransform};
use egui::{NumExt, Ui};
use epaint::{Color32, Rgba, Shape, Stroke};
use std::ops::RangeInclusive;
/// A convex polygon. /// A convex polygon.
pub struct 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 /// Highlight this polygon in the plot by scaling up the stroke and reducing
/// transparency. /// the fill transparency.
#[must_use] #[must_use]
pub fn highlight(mut self) -> Self { pub fn highlight(mut self) -> Self {
self.highlight = true; self.highlight = true;
@ -50,7 +52,8 @@ impl Polygon {
self 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] #[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self { pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.stroke.color = color.into(); 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. /// 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 /// Multiple plot items may share the same name, in which case they will
/// the legend. /// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
#[must_use] #[must_use]
pub fn name(mut self, name: impl ToString) -> Self { 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::plot_item::PlotItem;
use crate::items::value::Value; use crate::items::value::Value;
use crate::items::values::Values; use crate::items::values::Values;
use crate::transform::{Bounds, ScreenTransform}; use crate::transform::{Bounds, ScreenTransform};
use egui::{Align2, Rect, Ui};
use epaint::{Color32, Shape, Stroke, TextStyle};
use std::ops::RangeInclusive;
/// Text inside the plot. /// Text inside the plot.
pub struct Text { pub struct Text {
@ -45,7 +47,8 @@ impl Text {
self 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] #[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self { pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.color = color.into(); self.color = color.into();
@ -63,8 +66,8 @@ impl Text {
/// ///
/// This name will show up in the plot legend, if legends are turned on. /// 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 /// Multiple plot items may share the same name, in which case they will
/// the legend. /// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
#[must_use] #[must_use]
pub fn name(mut self, name: impl ToString) -> Self { pub fn name(mut self, name: impl ToString) -> Self {
@ -80,10 +83,11 @@ impl PlotItem for Text {
} else { } else {
self.color self.color
}; };
let fond_id = ui.style().text_styles.get(&self.style).unwrap();
let pos = transform.position_from_value(&self.position); let pos = transform.position_from_value(&self.position);
let galley = ui let galley = ui
.fonts() .fonts()
.layout_no_wrap(self.text.clone(), self.style, color); .layout_no_wrap(self.text.clone(), fond_id.clone(), color);
let rect = self let rect = self
.anchor .anchor
.anchor_rect(Rect::from_min_size(pos, galley.size())); .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::plot_item::PlotItem;
use crate::items::value::Value; use crate::items::value::Value;
use crate::items::values::Values; use crate::items::values::Values;
use crate::items::LineStyle; use crate::items::LineStyle;
use crate::transform::{Bounds, ScreenTransform}; use crate::transform::{Bounds, ScreenTransform};
use egui::Ui;
use epaint::{Color32, Shape, Stroke};
use std::ops::RangeInclusive;
impl PlotItem for VLine { impl PlotItem for VLine {
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) { fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
@ -95,7 +97,8 @@ impl VLine {
self 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] #[must_use]
pub fn color(mut self, color: impl Into<Color32>) -> Self { pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.stroke.color = color.into(); 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. /// 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 /// Multiple plot items may share the same name, in which case they will
/// the legend. /// also share an entry in the legend.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
#[must_use] #[must_use]
pub fn name(mut self, name: impl ToString) -> Self { pub fn name(mut self, name: impl ToString) -> Self {

View File

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

View File

@ -1,8 +1,9 @@
use crate::items::{ExplicitGenerator, Value};
use crate::transform::Bounds;
use std::collections::Bound; use std::collections::Bound;
use std::ops::{RangeBounds, RangeInclusive}; use std::ops::{RangeBounds, RangeInclusive};
use crate::items::{ExplicitGenerator, Value};
use crate::transform::Bounds;
pub struct Values { pub struct Values {
pub values: Vec<Value>, pub values: Vec<Value>,
generator: Option<ExplicitGenerator>, generator: Option<ExplicitGenerator>,
@ -20,7 +21,8 @@ impl Values {
Self::from_values(iter.collect()) 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( pub fn from_explicit_callback(
function: impl Fn(f64) -> f64 + 'static, function: impl Fn(f64) -> f64 + 'static,
x_range: impl RangeBounds<f64>, 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. /// Draw a line based on a function `(x,y)=f(t)`, a range for t and the
/// The range may be specified as start..end or as start..=end. /// number of points. The range may be specified as start..end or as
/// start..=end.
pub fn from_parametric_callback( pub fn from_parametric_callback(
function: impl Fn(f64) -> (f64, f64), function: impl Fn(f64) -> (f64, f64),
t_range: impl RangeBounds<f64>, t_range: impl RangeBounds<f64>,
@ -92,13 +95,14 @@ impl Values {
Self::from_values(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 { pub fn is_empty(&self) -> bool {
self.generator.is_none() && self.values.is_empty() self.generator.is_none() && self.values.is_empty()
} }
/// If initialized with a generator function, this will generate `n` evenly spaced points in the /// If initialized with a generator function, this will generate `n` evenly
/// given range. /// spaced points in the given range.
pub fn generate_points(&mut self, x_range: RangeInclusive<f64>) { pub fn generate_points(&mut self, x_range: RangeInclusive<f64>) {
if let Some(generator) = self.generator.take() { if let Some(generator) = self.generator.take() {
if let Some(intersection) = Self::range_intersection(&x_range, &generator.x_range) { 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)] #[derive(Clone)]
pub struct ScreenTransform { pub struct ScreenTransform {
/// The screen rectangle. /// 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 amdgpu_config::fan::MatrixPoint;
use egui::{emath, pos2, Layout, PointerButton, Ui}; use egui::{emath, pos2, Layout, PointerButton, Ui};
use epaint::Color32; use epaint::Color32;
use crate::app::{ChangeState, FanConfig, FanServices, StatefulConfig}; use crate::app::{ChangeState, FanConfig, FanServices, StatefulConfig};
use crate::widgets;
use crate::widgets::drag_plot::PlotMsg; use crate::widgets::drag_plot::PlotMsg;
use crate::widgets::reload_section::ReloadSection; use crate::widgets::reload_section::ReloadSection;
use crate::{widgets, widgets::ConfigFile}; use crate::widgets::ConfigFile;
pub struct ChangeFanSettings { pub struct ChangeFanSettings {
config: FanConfig, config: FanConfig,
@ -139,10 +140,10 @@ impl ChangeFanSettings {
ui.label("Saving..."); ui.label("Saving...");
} }
ChangeState::Success => { 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) => { 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()), path: String::from(config.path()),
content, content,
}; };
match amdgpu::helper_cmd::send_command(command) { match send_command(command) {
Ok(amdgpu::helper_cmd::Response::ConfigFileSaveFailed(msg)) => { Ok(Response::ConfigFileSaveFailed(msg)) => {
state.state = ChangeState::Failure(msg); state.state = ChangeState::Failure(msg);
} }
Ok(amdgpu::helper_cmd::Response::ConfigFileSaved) => { Ok(Response::ConfigFileSaved) => {
state.state = ChangeState::Success; state.state = ChangeState::Success;
} }
_ => {} _ => {}

View File

@ -1,6 +1,5 @@
use egui::{PointerButton, Response, Sense, Ui, Widget};
use amdgpu_config::fan::MatrixPoint; use amdgpu_config::fan::MatrixPoint;
use egui::{PointerButton, Response, Sense, Ui, Widget};
use crate::app::FanConfig; 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;
use core::option::Option::Some; use core::option::Option::Some;
use egui::Ui;
use std::collections::vec_deque::VecDeque; use std::collections::vec_deque::VecDeque;
use amdgpu::Card;
use amdmond_lib::AmdMon;
use egui::Ui;
use crate::app::{FanConfig, FanServices};
pub struct CoolingPerformance { pub struct CoolingPerformance {
capacity: usize, capacity: usize,
data: VecDeque<f64>, data: VecDeque<f64>,
@ -55,18 +57,20 @@ impl CoolingPerformance {
.name("Overheating") .name("Overheating")
.color(Color32::DARK_RED); .color(Color32::DARK_RED);
let plot = Plot::new("cooling performance") ui.label("Temperature");
Plot::new("cooling performance")
.allow_drag(false) .allow_drag(false)
.allow_zoom(false) .allow_zoom(false)
.height(600.0) .height(600.0)
.line(curve) .show(ui, |plot_ui| {
.hline(zero) plot_ui.line(curve);
.hline(optimal) plot_ui.hline(zero);
.hline(target) plot_ui.hline(optimal);
.legend(Legend::default()); plot_ui.hline(target);
// plot_ui.legend(Legend::default());
});
ui.label("Temperature"); // ui.add(plot);
ui.add(plot);
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Current temperature"); ui.label("Current temperature");
ui.label(format!("{:<3.2}°C", current)); 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 egui::{emath, vec2, CursorIcon, Id, NumExt, PointerButton, Response, Sense, Ui, Vec2};
use epaint::ahash::AHashSet; use epaint::ahash::AHashSet;
use epaint::color::Hsva; use epaint::color::Hsva;
use epaint::Color32; use epaint::{Color32, Rounding};
use crate::items::HLine; use crate::items::{HLine, *};
use crate::items::*;
use crate::transform::{Bounds, ScreenTransform}; use crate::transform::{Bounds, ScreenTransform};
use crate::widgets::drag_plot_prepared::DragPlotPrepared; use crate::widgets::drag_plot_prepared::DragPlotPrepared;
use crate::widgets::legend::Legend; use crate::widgets::legend::Legend;
@ -16,7 +15,8 @@ pub enum PlotMsg {
Drag(emath::Vec2), Drag(emath::Vec2),
} }
#[derive(Clone, serde::Serialize, serde::Deserialize)] // , serde::Serialize, serde::Deserialize
#[derive(Clone)]
struct PlotMemory { struct PlotMemory {
bounds: Bounds, bounds: Bounds,
auto_bounds: bool, auto_bounds: bool,
@ -125,12 +125,13 @@ where
self.next_auto_color_idx += 1; self.next_auto_color_idx += 1;
let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875 let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875
let h = i as f32 * golden_ratio; 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. /// 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 /// For instance, it can be useful to set this to `1.0` for when the two
/// unit. /// axes show the same unit.
/// By default the plot window's aspect ratio is used. /// By default the plot window's aspect ratio is used.
#[must_use] #[must_use]
pub fn data_aspect(mut self, data_aspect: f32) -> Self { pub fn data_aspect(mut self, data_aspect: f32) -> Self {
@ -139,7 +140,8 @@ where
} }
/// width / height ratio of the plot region. /// 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] #[must_use]
pub fn view_aspect(mut self, view_aspect: f32) -> Self { pub fn view_aspect(mut self, view_aspect: f32) -> Self {
self.view_aspect = Some(view_aspect); 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. /// 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] #[must_use]
pub fn width(mut self, width: f32) -> Self { pub fn width(mut self, width: f32) -> Self {
self.min_size.x = width; self.min_size.x = width;
@ -156,7 +159,8 @@ where
} }
/// Height of plot. By default a plot will fill the ui it is in. /// 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] #[must_use]
pub fn height(mut self, height: f32) -> Self { pub fn height(mut self, height: f32) -> Self {
self.min_size.y = height; self.min_size.y = height;
@ -240,9 +244,9 @@ where
} = self; } = self;
let plot_id = ui.make_persistent_id(id); let plot_id = ui.make_persistent_id(id);
let memory = ui let memory = ui
.memory() .ctx()
.id_data .data()
.get_mut_or_insert_with(plot_id, || PlotMemory { .get_persisted_mut_or_insert_with(plot_id, || PlotMemory {
bounds: min_auto_bounds, bounds: min_auto_bounds,
auto_bounds: false, auto_bounds: false,
hovered_entry: None, hovered_entry: None,
@ -284,11 +288,11 @@ where
}; };
let (rect, response) = ui.allocate_exact_size(size, Sense::click_and_drag()); 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 { plot_painter.add(epaint::RectShape {
rect, rect,
corner_radius: 2.0, rounding: Rounding::from(2.0),
fill: ui.visuals().extreme_bg_color, fill: ui.visuals().extreme_bg_color,
stroke: ui.visuals().widgets.noninteractive.bg_stroke, stroke: ui.visuals().widgets.noninteractive.bg_stroke,
}); });
@ -408,16 +412,15 @@ where
hovered_entry = legend.get_hovered_entry_name(); hovered_entry = legend.get_hovered_entry_name();
} }
ui.memory().id_data.insert( ui.ctx()
plot_id, .data()
PlotMemory { .get_persisted_mut_or_insert_with(plot_id, || PlotMemory {
bounds: t_bounds, bounds: t_bounds,
auto_bounds, auto_bounds,
hovered_entry, hovered_entry,
hidden_items, hidden_items,
min_auto_bounds, min_auto_bounds,
}, });
);
response.on_hover_cursor(CursorIcon::Crosshair) 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 egui::{emath, pos2, remap_clamp, vec2, Align2, Layout, NumExt, Pos2, Response, TextStyle, Ui};
use epaint::{Color32, Rgba, Shape, Stroke, TextStyle}; use epaint::{Color32, Rgba, Shape, Stroke};
use crate::items::Value; use crate::items::{Line, PlotItem, Value};
use crate::items::{Line, PlotItem};
use crate::transform::ScreenTransform; use crate::transform::ScreenTransform;
pub struct DragPlotPrepared { pub struct DragPlotPrepared {
@ -41,7 +40,9 @@ impl DragPlotPrepared {
self.hover(ui, pointer, &mut shapes); 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>) { 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 n = (value_main / step_size).round() as i64;
let spacing_in_points = if n % (base * base) == 0 { 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 { } 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 { } else {
step_size_in_points // thin line step_size_in_points // thin line
}; };
@ -106,7 +110,11 @@ impl DragPlotPrepared {
let color = color_from_alpha(ui, text_alpha); let color = color_from_alpha(ui, text_alpha);
let text = emath::round_to_decimals(value_main, 5).to_string(); // hack 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); 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 interact_radius: f32 = 16.0;
let mut closest_value = None; let mut closest_value = None;
let mut closest_dist_sq = interact_radius.powi(2); let mut closest_dist_sq = interact_radius.powi(2);
for item in lines { for values in lines.iter().filter_map(|v| v.values()) {
if let Some(values) = item.values() {
for (idx, value) in values.values.iter().enumerate() { for (idx, value) in values.values.iter().enumerate() {
let pos = transform.position_from_value(value); let pos = transform.position_from_value(value);
let dist_sq = pointer.distance_sq(pos); let dist_sq = pointer.distance_sq(pos);
@ -146,7 +153,6 @@ impl DragPlotPrepared {
} }
} }
} }
}
closest_value closest_value
} }
@ -255,11 +261,15 @@ impl DragPlotPrepared {
}; };
shapes.push(Shape::text( shapes.push(Shape::text(
ui.fonts(), &*ui.fonts(),
pointer + vec2(3.0, -2.0), pointer + vec2(3.0, -2.0),
Align2::LEFT_BOTTOM, Align2::LEFT_BOTTOM,
text, text,
TextStyle::Body, ui.style()
.text_styles
.get(&TextStyle::Body)
.cloned()
.unwrap(),
ui.visuals().text_color(), ui.visuals().text_color(),
)); ));
} }

View File

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

View File

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

View File

@ -5,6 +5,8 @@ pub mod drag_plot;
pub mod drag_plot_prepared; pub mod drag_plot_prepared;
pub mod legend; pub mod legend;
pub mod legend_widget; pub mod legend_widget;
pub mod output_widget;
pub mod outputs_settings;
pub mod reload_section; pub mod reload_section;
pub use change_fan_settings::*; 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::pidfile::helper_cmd::Command;
use amdgpu::helper_cmd::Command;
use egui::{PointerButton, Response, Sense, Ui}; use egui::{PointerButton, Response, Sense, Ui};
use epaint::Color32;
use crate::app::{ChangeState, FanServices};
pub struct ReloadSection<'l> { pub struct ReloadSection<'l> {
pub services: &'l mut FanServices, pub services: &'l mut FanServices,
@ -18,7 +18,7 @@ impl<'l> egui::Widget for ReloadSection<'l> {
if ui.button("Reload").clicked_by(PointerButton::Primary) { if ui.button("Reload").clicked_by(PointerButton::Primary) {
service.reload = ChangeState::Reloading; service.reload = ChangeState::Reloading;
match amdgpu::helper_cmd::send_command(Command::ReloadConfig { match amdgpu::pidfile::helper_cmd::send_command(Command::ReloadConfig {
pid: service.pid, pid: service.pid,
}) { }) {
Ok(response) => { Ok(response) => {
@ -37,12 +37,13 @@ impl<'l> egui::Widget for ReloadSection<'l> {
ui.label("Reloading..."); ui.label("Reloading...");
} }
ChangeState::Success => { 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) => { ChangeState::Failure(msg) => {
ui.add( ui.add(
egui::Label::new(format!("Failure. {}", msg)) egui::Label::new(format!("Failure. {}", msg)), // .text_color(Color32::RED),
.text_color(Color32::RED),
); );
} }
} }

View File

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

View File

@ -2,11 +2,11 @@ mod command;
mod log_file; mod log_file;
mod watch; mod watch;
use amdgpu::utils::ensure_config_dir;
use amdgpu_config::monitor::{load_config, Config, DEFAULT_MONITOR_CONFIG_PATH};
use gumdrop::Options; use gumdrop::Options;
use crate::command::Command; use crate::command::Command;
use amdgpu::utils::ensure_config_dir;
use amdgpu_config::monitor::{load_config, Config, DEFAULT_MONITOR_CONFIG_PATH};
#[derive(gumdrop::Options)] #[derive(gumdrop::Options)]
pub struct Opts { 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::fs::{DirEntry, Permissions};
use std::io::{Read, Write};
use std::net::Shutdown;
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::{UnixListener, UnixStream}; use std::os::unix::net::{UnixListener, UnixStream};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use amdgpu::pidfile::ports::{sock_file, Command, Response, *};
use amdgpu::IoFailure;
fn parse_output(entry: DirEntry) -> Option<Output> { fn parse_output(entry: DirEntry) -> Option<Output> {
let ty = entry.file_type().ok()?; let ty = entry.file_type().ok()?;
if ty.is_dir() { if ty.is_dir() {
@ -60,45 +59,7 @@ async fn read_outputs(state: Arc<Mutex<Vec<Output>>>) {
} }
} }
pub struct Service(UnixStream); pub type Service = amdgpu::pidfile::Service<Response>;
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);
}
}
async fn service(state: Arc<Mutex<Vec<Output>>>) { async fn service(state: Arc<Mutex<Vec<Output>>>) {
let sock_path = sock_file(); 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>>>) { 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() { let command = match service.read_command() {
Some(s) => s, Some(s) => s,

View File

@ -1,7 +1,6 @@
use gumdrop::Options;
use amdgpu::utils::ensure_config_dir; use amdgpu::utils::ensure_config_dir;
use amdgpu_config::voltage::{load_config, Config}; use amdgpu_config::voltage::{load_config, Config};
use gumdrop::Options;
use crate::command::VoltageCommand; use crate::command::VoltageCommand;
use crate::error::VoltageError; 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"