Files
Ilya Zlobintsev e2caa24ca8 feat: add displays info (#1052)
* test

* feat: dp info on amd

* chore: add other display types

* feat: use libdisplay-info, add screen size

* feat: add GUI

* feat: WIP nvidia

* feat: reuse display handle

* chore: clippy fixes

* chore: cleanup

* chore: make display-info an optional feature, add build dependency

* fix: ci

* fix ui imports

* fix: add new dependency to flatpak

* fix: add hwdata flatpak module
2026-06-19 22:22:03 +03:00

254 lines
9.3 KiB
Rust

#[cfg(feature = "display-info")]
mod display;
pub mod gpu_controller;
pub mod handler;
mod metrics;
mod opencl;
mod profiles;
mod vulkan;
use self::handler::Handler;
use crate::{config::Config, socket, system};
use anyhow::Context;
use futures::future::join_all;
use lact_schema::{Pong, Request, Response};
use serde::Serialize;
use std::fmt::Debug;
use tokio::{
io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader},
net::{TcpListener, UnixListener},
sync::Notify,
};
use tracing::{debug, error, info, instrument, trace};
pub struct Server {
pub handler: Handler,
unix_listener: UnixListener,
tcp_listener: Option<TcpListener>,
}
impl Server {
pub async fn new(config: Config) -> anyhow::Result<Self> {
let (unix_listener, socket_path) = socket::listen()?;
let tcp_listener = if let Some(address) = &config.daemon.tcp_listen_address {
let listener = TcpListener::bind(address)
.await
.with_context(|| format!("Could not bind to TCP address {address}"))?;
info!("TCP listening on {}", listener.local_addr()?);
Some(listener)
} else {
info!("TCP listener disabled");
None
};
system::power_profiles_daemon::setup().await;
let handler = Handler::new(config).await?;
socket::set_permissions(&socket_path, &handler.config.read().await.daemon)
.await
.inspect_err(|_| {
// Clean up the socket if permissions failed to be set
socket::cleanup();
})?;
if let Some(metrics_config) = handler.config.read().await.daemon.metrics.clone() {
metrics::setup(handler.clone(), metrics_config);
} else {
debug!("metrics exporter disabled");
}
Ok(Self {
handler,
unix_listener,
tcp_listener,
})
}
pub async fn run(self) {
let mut tasks = vec![];
let unix_handler = self.handler.clone();
let unix_task = tokio::task::spawn_local(async move {
loop {
match self.unix_listener.accept().await {
Ok((stream, _)) => {
let handler = unix_handler.clone();
tokio::task::spawn_local(async move {
if let Err(error) = handle_stream(stream, handler).await {
error!("{error}");
}
});
}
Err(error) => {
error!("failed to handle connection: {error}");
}
}
}
});
tasks.push(unix_task);
if let Some(tcp_listener) = self.tcp_listener {
let tcp_task = tokio::task::spawn_local(async move {
loop {
match tcp_listener.accept().await {
Ok((stream, _)) => {
let handler = self.handler.clone();
tokio::task::spawn_local(async move {
if let Err(error) = handle_stream(stream, handler).await {
error!("{error}");
}
});
}
Err(error) => {
error!("failed to handle connection: {error}");
}
}
}
});
tasks.push(tcp_task);
}
join_all(tasks).await;
}
}
#[instrument(level = "debug", skip(stream, handler))]
pub async fn handle_stream<T: AsyncRead + AsyncWrite + Unpin>(
stream: T,
handler: Handler,
) -> anyhow::Result<()> {
let disconnect_notify = std::sync::Arc::new(Notify::new());
let mut stream = BufReader::new(stream);
let mut buf = String::new();
while stream.read_line(&mut buf).await? != 0 {
trace!("handling request: {}", buf.trim_end());
let maybe_request = serde_json::from_str(&buf);
let response = match maybe_request {
Ok(request) => match handle_request(request, &handler, &disconnect_notify).await {
Ok(response) => response,
Err(error) => serde_json::to_vec(&Response::<()>::from(error))?,
},
Err(error) => serde_json::to_vec(&Response::<()>::from(
anyhow::Error::new(error).context("Failed to deserialize"),
))?,
};
stream.write_all(&response).await?;
stream.write_all(b"\n").await?;
buf.clear();
}
disconnect_notify.notify_waiters();
Ok(())
}
#[instrument(level = "debug", skip(handler, disconnect_notify))]
async fn handle_request<'a>(
request: Request<'a>,
handler: &'a Handler,
disconnect_notify: &std::sync::Arc<Notify>,
) -> anyhow::Result<Vec<u8>> {
match request {
Request::Ping => ok_response(ping()),
Request::SystemInfo => ok_response(system::info().await?),
Request::ListDevices => ok_response(handler.list_devices().await),
Request::DeviceInfo {
id,
include_api_info,
} => ok_response(handler.get_device_info(id, include_api_info).await?),
Request::DeviceApiInfo { id } => ok_response(handler.get_device_api_info(id).await?),
Request::DeviceStats { id } => ok_response(handler.get_gpu_stats(id).await?),
Request::DeviceClocksInfo { id } => ok_response(handler.get_clocks_info(id).await?),
Request::DisplaysInfo { id } => ok_response(handler.get_displays_info(id).await?),
Request::DevicePowerProfileModes { id } => {
ok_response(handler.get_power_profile_modes(id).await?)
}
Request::SetFanControl(opts) => ok_response(handler.set_fan_control(opts).await?),
Request::ResetPmfw { id } => ok_response(handler.reset_pmfw(id).await?),
Request::SetPowerCap { id, cap } => ok_response(handler.set_power_cap(id, cap).await?),
Request::SetPerformanceLevel {
id,
performance_level,
} => ok_response(handler.set_performance_level(id, performance_level).await?),
Request::SetClocksValue { id, command } => {
ok_response(handler.set_clocks_value(id, command).await?)
}
Request::BatchSetClocksValue { id, commands } => {
ok_response(handler.batch_set_clocks_value(id, commands).await?)
}
Request::SetPowerProfileMode {
id,
index,
custom_heuristics,
} => ok_response(
handler
.set_power_profile_mode(id, index, custom_heuristics)
.await?,
),
Request::GetPowerStates { id } => ok_response(handler.get_power_states(id).await?),
Request::SetEnabledPowerStates { id, kind, states } => {
ok_response(handler.set_enabled_power_states(id, kind, states).await?)
}
Request::VbiosDump { id } => ok_response(handler.vbios_dump(id).await?),
Request::ListProfiles { include_state } => {
ok_response(handler.list_profiles(include_state).await)
}
Request::GetProfile { name } => {
ok_response(handler.get_profile(name.map(Into::into)).await?)
}
Request::SetProfile { name, auto_switch } => ok_response(
handler
.set_profile(name.map(Into::into), auto_switch)
.await?,
),
Request::CreateProfile { name, base } => {
ok_response(handler.create_profile(name, base).await?)
}
Request::DeleteProfile { name } => ok_response(handler.delete_profile(name).await?),
Request::MoveProfile { name, new_position } => {
ok_response(handler.move_profile(&name, new_position).await?)
}
Request::HoldProfile { name } => ok_response(
handler
.hold_profile(name, disconnect_notify.clone())
.await?,
),
Request::ReleaseProfile { cookie } => ok_response(handler.release_profile(cookie).await?),
Request::EvaluateProfileRule { rule } => ok_response(handler.evaluate_profile_rule(&rule)?),
Request::SetProfileRule { name, rule, hooks } => {
ok_response(handler.set_profile_rule(&name, rule, hooks).await?)
}
Request::GetGpuConfig { id } => ok_response(handler.get_gpu_config(id).await?),
Request::SetGpuConfig { id, config } => {
ok_response(handler.set_gpu_config(id, *config).await?)
}
Request::ProcessList { id } => ok_response(handler.process_list(id).await?),
Request::EnableOverdrive => ok_response(system::enable_overdrive().await?),
Request::DisableOverdrive => ok_response(system::disable_overdrive().await?),
Request::GenerateSnapshot => ok_response(handler.generate_snapshot().await?),
Request::ConfirmPendingConfig(command) => {
ok_response(handler.confirm_pending_config(command)?)
}
Request::RestConfig => {
handler.reset_config().await;
ok_response(())
}
}
}
fn ok_response<T: Serialize + Debug>(data: T) -> anyhow::Result<Vec<u8>> {
trace!("responding with {data:?}");
Ok(serde_json::to_vec(&Response::Ok(data))?)
}
fn ping() -> Pong {
Pong
}