diff --git a/src/config.rs b/src/config.rs index 33405d5..77f4ad1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,9 @@ use cosmic::{ cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry}, theme, }; +use lexopt::prelude::*; use serde::{Deserialize, Serialize}; +use std::{path::PathBuf, process}; use crate::wrappers::HWDeviceType; @@ -41,3 +43,52 @@ impl Default for Config { } } } + +impl Config { + pub fn with_args(&mut self, args: &mut Args) { + if let Some(decoder) = args.decoder { + self.hw_decoder = decoder; + } + } +} + +pub struct Args { + pub paths: Vec, + pub decoder: Option, +} + +impl Args { + pub fn parse_args() -> Result { + let mut paths = Vec::new(); + let mut decoder = None; + + let mut parser = lexopt::Parser::from_env(); + while let Some(arg) = parser.next()? { + match arg { + Long("list-hwdec") => { + println!("Supported hardware decoders:"); + for hwdec in HWDeviceType::supported_devices() { + println!("\t* [{}] {hwdec}", hwdec.short_name()); + } + process::exit(0); + } + Long("hwdec") => { + decoder = Some(parser.value()?.parse()?); + } + Value(path) => { + let path = path.parse()?; + paths.push(path); + } + _ => return Err(arg.unexpected()), + } + } + + if paths.is_empty() { + return Err(lexopt::Error::MissingValue { + option: Some("missing video path".into()), + }); + } + + Ok(Self { paths, decoder }) + } +} diff --git a/src/main.rs b/src/main.rs index c24456a..68a120d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,14 +16,12 @@ use cosmic::{ use std::{ any::TypeId, collections::HashMap, - env, - path::PathBuf, - process, + env, process, sync::{mpsc, Arc, Mutex}, time::{Duration, Instant}, }; -use config::{AppTheme, Config, CONFIG_VERSION}; +use config::{AppTheme, Args, Config, CONFIG_VERSION}; mod config; use key_bind::{key_binds, KeyBind}; @@ -42,16 +40,26 @@ fn main() -> Result<(), Box> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); localize::localize(); + + let mut args = match Args::parse_args() { + Ok(args) => args, + Err(e) => { + log::error!("{e}"); + process::exit(1); + } + }; let (config_handler, config) = match cosmic_config::Config::new(App::APP_ID, CONFIG_VERSION) { Ok(config_handler) => { - let config = match Config::get_entry(&config_handler) { + let mut config = match Config::get_entry(&config_handler) { Ok(ok) => ok, Err((errs, config)) => { log::info!("errors loading config: {:?}", errs); config } }; + // Update config with command line args + config.with_args(&mut args); (Some(config_handler), config) } Err(err) => { @@ -60,14 +68,9 @@ fn main() -> Result<(), Box> { } }; - //TODO: support multiple paths - let path = match env::args().skip(1).next() { - Some(arg) => PathBuf::from(arg), - None => { - log::error!("no argument provided"); - process::exit(1); - } - }; + //TODO: support using multiple paths + let Args { mut paths, .. } = args; + let path = paths.pop().unwrap(); let (player_tx, video_queue_lock) = player::run(path); diff --git a/src/wrappers.rs b/src/wrappers.rs index 38b2de2..2bbc617 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::str::FromStr; +use std::{fmt, iter::FusedIterator, str::FromStr}; use ffmpeg_next::ffi::{av_hwdevice_iterate_types, AVHWDeviceType}; use serde::{ @@ -25,6 +25,8 @@ pub enum HWDeviceType { /// DirectX Video Acceleration 2.0 /// https://learn.microsoft.com/en-us/windows/win32/medfound/about-dxva-2-0 Dxva2, + /// Direct Rendering Manager + /// https://dri.freedesktop.org/wiki/DRM/ Drm, /// MediaCodec /// Android only @@ -70,7 +72,26 @@ impl HWDeviceType { } } - /// Supported hardware decoders + /// Short name for CLI arguments + pub const fn short_name(self) -> &'static str { + match self { + Self::None => "none", + Self::Cuda => "cuda", + Self::Dxva2 => "dxva2", + Self::D3d11va => "d3d11va", + Self::D3d12va => "d3d12va", + Self::Drm => "drm", + Self::MediaCodec => "mediacodec", + Self::OpenCl => "opencl", + Self::Qsv => "qsv", + Self::Vaapi => "vaapi", + Self::Vdpau => "vdpau", + Self::VideoToolbox => "videotoolbox", + Self::Vulkan => "vulkan", + } + } + + /// System's supported hardware decoders pub fn supported_devices() -> SupportedDeviceIter { SupportedDeviceIter::default() } @@ -104,6 +125,12 @@ impl FromStr for HWDeviceType { } } +impl fmt::Display for HWDeviceType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + impl From for HWDeviceType { fn from(value: AVHWDeviceType) -> Self { match value { @@ -131,6 +158,7 @@ impl Default for HWDeviceType { } } +/// Iterator over system's supported hardware decoders. pub struct SupportedDeviceIter { current: AVHWDeviceType, } @@ -147,13 +175,36 @@ impl Iterator for SupportedDeviceIter { type Item = HWDeviceType; fn next(&mut self) -> Option { + // None is a sentinel value that indicates the iterator is exhausted if self.current == AVHWDeviceType::AV_HWDEVICE_TYPE_NONE { None } else { let prev = self.current; + // SAFETY: The docs and examples state that the iterator yields the next value + // when the previous is passed in. self.current = unsafe { av_hwdevice_iterate_types(prev) }; Some(prev.into()) } } } + +impl FusedIterator for SupportedDeviceIter {} + +#[cfg(test)] +mod tests { + use std::hint::black_box; + + use super::*; + + // The iterator's yielded values aren't important since hardware decoders vary by system + // This is just a sanity check to ensure the iterator works + #[test] + fn supported_device_iter_doesnt_seg_fault() { + for decoder in HWDeviceType::supported_devices() { + black_box(decoder); + } + + let _decoders: Vec<_> = black_box(HWDeviceType::supported_devices().collect()); + } +}