use std::env;
use std::fs;
use std::os;
use std::path;
use anyhow::Context;
use drm::control::{Device as ControlDevice, Mode};
use drm::Device;
mod fsr;
struct Card(std::fs::File);
impl os::fd::AsFd for Card {
fn as_fd(&self) -> os::fd::BorrowedFd<'_> {
self.0.as_fd()
}
}
impl Card {
pub fn open<P: AsRef<path::Path>>(path: P) -> Self {
let mut options = std::fs::OpenOptions::new();
options.read(true);
options.write(true);
Card(options.open(path).unwrap())
}
}
impl Device for Card {}
impl ControlDevice for Card {}
pub fn gamescope(res: (u16, u16), fsr_mode: &str) -> anyhow::Result<Vec<String>> {
let gamescope_bin: String = env::var("RRES_GAMESCOPE").unwrap_or("gamescope".to_string());
let mut gamescope_runner: Vec<String> = vec![gamescope_bin];
let args = if !fsr_mode.is_empty() && fsr_mode.to_lowercase() != "native" {
let Ok(fsr) = fsr::Fsr::try_from(fsr_mode) else {
return Err(anyhow::anyhow!("invalid FSR mode: {}", fsr_mode));
};
let fsr_res = fsr.generate(res);
format!(
"-W {} -H {} -U -w {} -h {}",
res.0, res.1, fsr_res.0, fsr_res.1
)
} else {
format!("-W {} -H {}", res.0, res.1)
};
gamescope_runner.extend(args.split(' ').map(|s| s.to_owned()));
Ok(gamescope_runner)
}
pub fn get_displays(card: Option<String>) -> anyhow::Result<Vec<Mode>> {
let mut displays: Vec<Mode> = vec![];
let mut cards: Vec<path::PathBuf> = vec![];
if let Some(c) = card {
let mut file = path::PathBuf::from("/dev/dri/");
file.push(&c);
if !file.exists() || !c.starts_with("card") {
return Err(anyhow::anyhow!("invalid card ({c})"));
}
cards.push(file);
} else {
for entry in fs::read_dir("/dev/dri/")? {
let file = entry?;
if let Some(name) = file.file_name().to_str() {
if name.starts_with("card") {
cards.push(file.path());
}
}
}
}
cards.sort();
for file in cards {
let gpu = Card::open(file);
let info = gpu.get_driver()?;
log::debug!("Found GPU: {}", info.name().to_string_lossy());
match get_card_modes(&gpu) {
Ok(modes) => displays.extend_from_slice(&modes),
Err(e) => log::error!("failed to read modes: {e}"),
}
}
Ok(displays)
}
pub fn get_res() -> anyhow::Result<(u16, u16)> {
get_res_card(None)
}
pub fn get_res_card(card: Option<String>) -> anyhow::Result<(u16, u16)> {
let res;
if let Ok(forced) = env::var("RRES_FORCE_RES") {
if let Some((x, y)) = forced.split_once('x') {
res = (x.parse()?, y.parse()?);
} else {
return Err(anyhow::anyhow!("failed to parse RRES_FORCE_RES"));
}
} else {
let displays = get_displays(card)?;
let selection: usize = env::var("RRES_DISPLAY")
.unwrap_or_else(|_| "0".to_string())
.parse()
.context("Failed to parse RRES_DISPLAY")?;
if selection > displays.len() - 1 {
return Err(anyhow::anyhow!("invalid display: {}", selection));
}
res = displays[selection].size();
}
Ok(res)
}
pub fn get_card_modes<G: ControlDevice>(gpu: &G) -> anyhow::Result<Vec<Mode>> {
let mut modes: Vec<Mode> = vec![];
let resources = gpu
.resource_handles()
.context("failed to get resource handles")?;
let connectors = resources.connectors();
for handle in connectors {
let connector = gpu
.get_connector(*handle, false)
.context("failed to get connector handle")?;
if connector.state() == drm::control::connector::State::Connected {
modes.push(get_connector_mode(gpu, &connector)?);
}
}
Ok(modes)
}
fn get_connector_mode<G: ControlDevice>(
gpu: &G,
connector: &drm::control::connector::Info,
) -> anyhow::Result<Mode> {
if connector.state() != drm::control::connector::State::Connected {
return Err(anyhow::anyhow!("Connector is disconnected"));
}
if let Some(encoder_handle) = connector.current_encoder() {
let encoder = gpu.get_encoder(encoder_handle)?;
if let Some(crtc_handle) = encoder.crtc() {
let crtc = gpu.get_crtc(crtc_handle).context("failed to get crtc")?;
if let Some(current_mode) = crtc.mode() {
log::debug!(
"Found display: {:?}, {}x{}",
connector.interface(),
current_mode.size().0,
current_mode.size().1
);
return Ok(current_mode);
}
}
}
log::warn!(
"Could not detect current mode for display {:?},",
connector.interface()
);
log::warn!("reading native resolution");
return Ok(connector.modes()[0]);
}