cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix/hero-release-note
goq / crates / sigil-host / src / source / preflight.rs
preflight.rs 4.8K
1use std::fs;23use anyhow::{Context, Result, ensure};4use tokio::process::Command;56use crate::config::{7 GamescopeEncoderBackend, GamescopePipewireConfig, HostConfig, VaapiRateControl,8};910use super::{diagnostic, gamescope_config, run_bounded_command};1112#[cfg(unix)]13use std::os::unix::fs::{FileTypeExt, PermissionsExt};1415const MAX_INSPECT_OUTPUT: usize = 1024 * 1024;1617pub(super) async fn preflight_gamescope_static(config: &HostConfig) -> Result<()> {18 let pipewire = gamescope_config(config)?;1920 for (name, path) in [21 ("pw_dump_path", &pipewire.pw_dump_path),22 ("gst_inspect_path", &pipewire.gst_inspect_path),23 ] {24 validate_executable(name, path)?;25 }26 if pipewire.encoder_backend == GamescopeEncoderBackend::ExternalGstLaunch {27 validate_executable("gst_launch_path", &pipewire.gst_launch_path)?;28 }29 validate_render_node(&pipewire.vaapi_render_node)?;3031 let terminal_sink = match pipewire.encoder_backend {32 GamescopeEncoderBackend::ExternalGstLaunch => "fdsink",33 GamescopeEncoderBackend::InProcessGstreamer => "appsink",34 };35 for element in [36 "pipewiresrc",37 "queue",38 "videoconvert",39 "videoscale",40 "videorate",41 "capsfilter",42 pipewire.vaapi_encoder.as_str(),43 "h264parse",44 terminal_sink,45 ] {46 let mut command = Command::new(&pipewire.gst_inspect_path);47 command.arg(element).env("LC_ALL", "C");48 let output = run_bounded_command(command, MAX_INSPECT_OUTPUT).await?;49 ensure!(50 output.status.success(),51 "GStreamer element {element:?} is unavailable: {}",52 diagnostic(&output.stderr)53 );54 }55 preflight_encoder_properties(pipewire).await?;5657 Ok(())58}5960async fn preflight_encoder_properties(config: &GamescopePipewireConfig) -> Result<()> {61 let executable = std::env::current_exe().context("resolving the running Sigil executable")?;62 let rate_control = match config.rate_control {63 VaapiRateControl::Cbr => "cbr",64 VaapiRateControl::Cqp => "cqp",65 };66 let mut command = Command::new(executable);67 command.args([68 "capture",69 "encoder-preflight",70 "--vaapi-encoder",71 config.vaapi_encoder.as_str(),72 "--vaapi-render-node",73 ]);74 command.arg(&config.vaapi_render_node);75 command.args(["--rate-control", rate_control]);76 let output = run_bounded_command(command, MAX_INSPECT_OUTPUT).await?;77 ensure!(78 output.status.success(),79 "configured VA encoder {:?} failed programmatic property preflight: {}",80 config.vaapi_encoder,81 diagnostic(&output.stderr)82 );83 Ok(())84}8586pub(crate) fn validate_executable(name: &str, path: &std::path::Path) -> Result<()> {87 let metadata = fs::symlink_metadata(path)88 .with_context(|| format!("inspecting gamescope_pipewire.{name} at {}", path.display()))?;89 ensure!(90 !metadata.file_type().is_symlink(),91 "gamescope_pipewire.{name} must not be a symlink"92 );93 ensure!(94 metadata.is_file(),95 "gamescope_pipewire.{name} is not a file"96 );97 #[cfg(unix)]98 {99 use std::os::unix::fs::MetadataExt;100101 ensure!(102 metadata.permissions().mode() & 0o111 != 0,103 "gamescope_pipewire.{name} is not executable"104 );105 ensure!(106 metadata.permissions().mode() & 0o022 == 0,107 "gamescope_pipewire.{name} must not be writable by group or other users"108 );109 let owner = metadata.uid();110 let effective_user = unsafe { libc::geteuid() };111 ensure!(112 owner == 0 || owner == effective_user,113 "gamescope_pipewire.{name} must be owned by root or the current user"114 );115 }116 Ok(())117}118119pub(crate) async fn preflight_gstreamer_element(120 gst_inspect_path: &std::path::Path,121 element: &str,122) -> Result<()> {123 let mut command = Command::new(gst_inspect_path);124 command.arg(element).env("LC_ALL", "C");125 let output = run_bounded_command(command, MAX_INSPECT_OUTPUT).await?;126 ensure!(127 output.status.success(),128 "GStreamer element {element:?} is unavailable: {}",129 diagnostic(&output.stderr)130 );131 Ok(())132}133134fn validate_render_node(path: &std::path::Path) -> Result<()> {135 let metadata = fs::symlink_metadata(path)136 .with_context(|| format!("inspecting VAAPI render node {}", path.display()))?;137 #[cfg(unix)]138 ensure!(139 metadata.file_type().is_char_device(),140 "VAAPI render node {} is not a character device",141 path.display()142 );143 fs::OpenOptions::new()144 .read(true)145 .write(true)146 .open(path)147 .with_context(|| {148 format!(149 "opening VAAPI render node {} read/write as the current user",150 path.display()151 )152 })?;153 Ok(())154}