diff --git a/src/file_builders/mod.rs b/src/file_builders/mod.rs index ab8ccfc..b6078ae 100644 --- a/src/file_builders/mod.rs +++ b/src/file_builders/mod.rs @@ -2,3 +2,4 @@ pub mod active_runtime_json; pub mod monado_autorun; pub mod openvrpaths_vrpath; pub mod wivrn_config; +pub mod wivrn_encoder_presets; diff --git a/src/file_builders/wivrn_config.rs b/src/file_builders/wivrn_config.rs index a28c2cb..8362617 100644 --- a/src/file_builders/wivrn_config.rs +++ b/src/file_builders/wivrn_config.rs @@ -95,7 +95,7 @@ pub struct WivrnConfEncoder { pub group: Option, /// contains unknown fields #[serde(flatten)] - other: Map, + pub other: Map, } impl Default for WivrnConfEncoder { @@ -128,6 +128,7 @@ pub struct WivrnConfig { pub scale: Option<[f32; 2]>, #[serde(skip_serializing_if = "Option::is_none")] pub bitrate: Option, + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub encoders: Vec, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/file_builders/wivrn_encoder_presets.rs b/src/file_builders/wivrn_encoder_presets.rs new file mode 100644 index 0000000..b2ba057 --- /dev/null +++ b/src/file_builders/wivrn_encoder_presets.rs @@ -0,0 +1,84 @@ +use super::wivrn_config::{Codec, Encoder, WivrnConfEncoder}; +use lazy_static::lazy_static; + +fn wivrn_encoder_presets() -> Vec<(&'static str, &'static str, Vec)> { + vec![ + ( + "3x VAAPI", + "Use 3 hardware accelerated encoders using VAAPI and the H265 codec.", + vec![ + WivrnConfEncoder { + encoder: Encoder::Vaapi, + width: Some(0.5), + height: Some(0.25), + offset_x: Some(0.0), + offset_y: Some(0.0), + group: Some(0), + codec: Codec::H265, + ..Default::default() + }, + WivrnConfEncoder { + encoder: Encoder::Vaapi, + width: Some(0.5), + height: Some(0.75), + offset_x: Some(0.0), + offset_y: Some(0.25), + group: Some(0), + codec: Codec::H265, + ..Default::default() + }, + WivrnConfEncoder { + encoder: Encoder::Vaapi, + width: Some(0.5), + height: Some(1.0), + offset_x: Some(0.5), + offset_y: Some(0.0), + group: Some(1), + codec: Codec::H265, + ..Default::default() + }, + ], + ), + ( + "2x VAAPI + 1 Software", + "Use 2 hardware accelerated encoders using VAAPI and a third one using software encoding. The hardware encoders use the H265 codec, while the software encoder uses H264.", + vec![ + WivrnConfEncoder { + encoder: Encoder::Vaapi, + width: Some(0.5), + height: Some(0.25), + offset_x: Some(0.0), + offset_y: Some(0.0), + group: Some(0), + codec: Codec::H265, + ..Default::default() + }, + WivrnConfEncoder { + encoder: Encoder::Vaapi, + width: Some(0.5), + height: Some(0.75), + offset_x: Some(0.0), + offset_y: Some(0.25), + group: Some(0), + codec: Codec::H265, + ..Default::default() + }, + WivrnConfEncoder { + encoder: Encoder::X264, + width: Some(0.5), + height: Some(1.0), + offset_x: Some(0.5), + offset_y: Some(0.0), + group: Some(1), + codec: Codec::H264, + ..Default::default() + }, + ], + ), + ] +} + +lazy_static! { + pub static ref WIVRN_ENCODER_PRESETS: Vec<(&'static str, &'static str, Vec)> = + wivrn_encoder_presets(); +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 87dd809..ae84ddb 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -19,5 +19,6 @@ mod steamvr_calibration_box; mod term_widget; mod util; mod wivrn_conf_editor; +pub mod wivrn_encoder_presets_win; pub const SENDER_IO_ERR_MSG: &str = "relm4 sender i/o failed"; diff --git a/src/ui/wivrn_conf_editor.rs b/src/ui/wivrn_conf_editor.rs index 1192f35..6c7027a 100644 --- a/src/ui/wivrn_conf_editor.rs +++ b/src/ui/wivrn_conf_editor.rs @@ -3,12 +3,18 @@ use super::{ WivrnEncoderModel, WivrnEncoderModelInit, WivrnEncoderModelOutMsg, }, util::bits_from_mbits, + wivrn_encoder_presets_win::{ + WivrnEncoderPresetsWin, WivrnEncoderPresetsWinMsg, WivrnEncoderPresetsWinOutMsg, + }, }; use crate::{ - file_builders::wivrn_config::{dump_wivrn_config, get_wivrn_config, WivrnConfig}, + file_builders::wivrn_config::{ + dump_wivrn_config, get_wivrn_config, WivrnConfEncoder, WivrnConfig, + }, ui::{ preference_rows::{number_entry_row, spin_row, switch_row}, util::bits_to_mbits, + wivrn_encoder_presets_win::WivrnEncoderPresetsWinInit, }, }; use adw::prelude::*; @@ -28,6 +34,8 @@ pub struct WivrnConfEditor { pub scaley_row: Option, #[tracker::do_not_track] bitrate_row: Option, + #[tracker::do_not_track] + wivrn_encoder_presets_win: Option>, } #[derive(Debug)] @@ -37,6 +45,8 @@ pub enum WivrnConfEditorMsg { AddEncoder, DeleteEncoder(String), TcpOnlyChanged(bool), + OpenEncoderPresetsWin, + SetEncoderPreset(Vec), } pub struct WivrnConfEditorInit { @@ -145,6 +155,13 @@ impl SimpleComponent for WivrnConfEditor { set_title: "Encoders", adw::ActionRow { set_title: "Add encoder", + add_suffix: presets_btn = >k::Button { + set_valign: gtk::Align::Center, + set_label: "Use Preset", + connect_clicked[sender] => move |_| { + sender.input(Self::Input::OpenEncoderPresetsWin) + } + }, add_suffix: add_encoder_btn = >k::Button { set_halign: gtk::Align::Center, set_valign: gtk::Align::Center, @@ -170,6 +187,23 @@ impl SimpleComponent for WivrnConfEditor { self.set_conf(get_wivrn_config()); self.win.as_ref().unwrap().present(); } + Self::Input::OpenEncoderPresetsWin => { + self.wivrn_encoder_presets_win + .as_ref() + .unwrap() + .sender() + .emit(WivrnEncoderPresetsWinMsg::Present); + } + Self::Input::SetEncoderPreset(preset) => { + self.encoder_models.as_mut().unwrap().guard().clear(); + preset.iter().for_each(|enc| { + self.encoder_models.as_mut().unwrap().guard().push_back( + WivrnEncoderModelInit { + encoder_conf: Some(enc.clone()), + }, + ); + }); + } Self::Input::Save => { let x = self.scalex_row.as_ref().unwrap().adjustment().value(); let y = self.scaley_row.as_ref().unwrap().adjustment().value(); @@ -234,6 +268,7 @@ impl SimpleComponent for WivrnConfEditor { scalex_row: None, scaley_row: None, bitrate_row: None, + wivrn_encoder_presets_win: None, tracker: 0, }; @@ -258,6 +293,18 @@ impl SimpleComponent for WivrnConfEditor { model.win = Some(widgets.win.clone()); + model.wivrn_encoder_presets_win = Some( + WivrnEncoderPresetsWin::builder() + .launch(WivrnEncoderPresetsWinInit { + parent_win: widgets.win.clone().upcast(), + }) + .forward(sender.input_sender(), move |msg| match msg { + WivrnEncoderPresetsWinOutMsg::Selected(preset) => { + Self::Input::SetEncoderPreset(preset) + } + }), + ); + ComponentParts { model, widgets } } } diff --git a/src/ui/wivrn_encoder_presets_win.rs b/src/ui/wivrn_encoder_presets_win.rs new file mode 100644 index 0000000..9608504 --- /dev/null +++ b/src/ui/wivrn_encoder_presets_win.rs @@ -0,0 +1,111 @@ +use adw::prelude::*; +use relm4::prelude::*; + +use crate::file_builders::{ + wivrn_config::WivrnConfEncoder, wivrn_encoder_presets::WIVRN_ENCODER_PRESETS, +}; + +use super::SENDER_IO_ERR_MSG; + +#[tracker::track] +pub struct WivrnEncoderPresetsWin { + win: Option, +} + +#[derive(Debug)] +pub enum WivrnEncoderPresetsWinMsg { + Present, + Selected(Vec), +} + +#[derive(Debug)] +pub enum WivrnEncoderPresetsWinOutMsg { + Selected(Vec), +} + +pub struct WivrnEncoderPresetsWinInit { + pub parent_win: gtk::Window, +} + +#[relm4::component(pub)] +impl SimpleComponent for WivrnEncoderPresetsWin { + type Init = WivrnEncoderPresetsWinInit; + type Input = WivrnEncoderPresetsWinMsg; + type Output = WivrnEncoderPresetsWinOutMsg; + + view! { + #[name(win)] + adw::Window { + set_modal: true, + set_hide_on_close: true, + set_transient_for: Some(&init.parent_win), + set_title: Some("WiVRn Encoder Presets"), + set_default_height: 400, + set_default_width: 300, + adw::ToolbarView { + set_top_bar_style: adw::ToolbarStyle::Flat, + set_hexpand: true, + set_vexpand: true, + add_top_bar: top_bar = &adw::HeaderBar { + set_vexpand: false, + }, + #[wrap(Some)] + set_content: prefpage = &adw::PreferencesPage { + set_title: "Encoder Presets", + add: prefgrp = &adw::PreferencesGroup { + + } + } + }, + } + } + + fn update(&mut self, message: Self::Input, sender: ComponentSender) { + self.reset(); + + match message { + Self::Input::Present => { + self.win.as_ref().unwrap().present(); + } + Self::Input::Selected(preset) => { + sender + .output(Self::Output::Selected(preset)) + .expect(SENDER_IO_ERR_MSG); + self.win.as_ref().unwrap().close(); + } + } + } + + fn init( + init: Self::Init, + root: Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + let mut model = Self { + win: None, + tracker: 0, + }; + + let widgets = view_output!(); + + model.win = Some(widgets.win.clone()); + + WIVRN_ENCODER_PRESETS + .iter() + .for_each(|(name, desc, enc_preset)| { + let row = adw::ActionRow::builder() + .title(*name) + .subtitle_lines(0) + .subtitle(*desc) + .activatable(true) + .build(); + let sndr = sender.clone(); + row.connect_activated(move |_| { + sndr.input(Self::Input::Selected(enc_preset.clone())) + }); + widgets.prefgrp.add(&row); + }); + + ComponentParts { model, widgets } + } +}