feat: per-profile autostart command

This commit is contained in:
Gabriele Musco 2024-02-04 17:17:26 +01:00
commit bf96a54675
No known key found for this signature in database
GPG key ID: 1068D795C80E51DE
5 changed files with 81 additions and 18 deletions

View file

@ -1,9 +1,6 @@
use crate::{ use crate::{
file_utils::get_writer, file_utils::get_writer,
paths::{ paths::{get_data_dir, get_ipc_file_path, BWRAP_SYSTEM_PREFIX, SYSTEM_PREFIX},
data_monado_path, data_opencomposite_path, get_data_dir, get_ipc_file_path,
BWRAP_SYSTEM_PREFIX, SYSTEM_PREFIX,
},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt::Display, fs::File, io::BufReader, path::Path, slice::Iter}; use std::{collections::HashMap, fmt::Display, fs::File, io::BufReader, path::Path, slice::Iter};
@ -230,6 +227,7 @@ pub struct Profile {
pub lighthouse_driver: LighthouseDriver, pub lighthouse_driver: LighthouseDriver,
#[serde(default = "String::default")] #[serde(default = "String::default")]
pub xrservice_launch_options: String, pub xrservice_launch_options: String,
pub autostart_command: Option<String>,
} }
impl Display for Profile { impl Display for Profile {
@ -284,6 +282,7 @@ impl Default for Profile {
lighthouse_driver: LighthouseDriver::default(), lighthouse_driver: LighthouseDriver::default(),
xrservice_launch_options: String::default(), xrservice_launch_options: String::default(),
uuid, uuid,
autostart_command: None,
} }
} }
} }

View file

@ -92,6 +92,8 @@ pub struct App {
#[tracker::do_not_track] #[tracker::do_not_track]
xrservice_worker: Option<JobWorker>, xrservice_worker: Option<JobWorker>,
#[tracker::do_not_track] #[tracker::do_not_track]
autostart_worker: Option<JobWorker>,
#[tracker::do_not_track]
restart_xrservice: bool, restart_xrservice: bool,
#[tracker::do_not_track] #[tracker::do_not_track]
build_worker: Option<JobWorker>, build_worker: Option<JobWorker>,
@ -112,6 +114,7 @@ pub struct App {
pub enum Msg { pub enum Msg {
OnServiceLog(Vec<String>), OnServiceLog(Vec<String>),
OnServiceExit(i32), OnServiceExit(i32),
OnAutostartExit(i32),
OnBuildLog(Vec<String>), OnBuildLog(Vec<String>),
OnBuildExit(i32), OnBuildExit(i32),
ClockTicking, ClockTicking,
@ -197,6 +200,23 @@ impl App {
debug, debug,
); );
worker.start(); worker.start();
if let Some(autostart_cmd) = prof.autostart_command {
let autostart_worker = JobWorker::new_with_timer(
Duration::from_secs(5),
WorkerJob::new_cmd(
Some(prof.environment.clone()),
"sh".into(),
Some(vec!["-c".into(), autostart_cmd.clone()]),
),
sender.input_sender(),
|msg| match msg {
JobWorkerOut::Log(rows) => Msg::OnServiceLog(rows),
JobWorkerOut::Exit(code) => Msg::OnAutostartExit(code),
},
);
autostart_worker.start();
self.autostart_worker = Some(autostart_worker)
}
self.xrservice_worker = Some(worker); self.xrservice_worker = Some(worker);
self.main_view self.main_view
.sender() .sender()
@ -242,6 +262,9 @@ impl App {
if let Some(worker) = self.xrservice_worker.as_ref() { if let Some(worker) = self.xrservice_worker.as_ref() {
worker.stop(); worker.stop();
} }
if let Some(worker) = self.autostart_worker.as_ref() {
worker.stop();
}
self.libmonado = None; self.libmonado = None;
self.restore_openxr_openvr_files(); self.restore_openxr_openvr_files();
self.main_view self.main_view
@ -325,24 +348,20 @@ impl SimpleComponent for App {
.emit(DebugViewMsg::LogUpdated(rows)); .emit(DebugViewMsg::LogUpdated(rows));
} }
} }
Msg::OnServiceExit(_) => { Msg::OnServiceExit(code) => {
self.main_view self.main_view
.sender() .sender()
.emit(MainViewMsg::XRServiceActiveChanged(false, None)); .emit(MainViewMsg::XRServiceActiveChanged(false, None));
self.debug_view self.debug_view
.sender() .sender()
.emit(DebugViewMsg::XRServiceActiveChanged(false)); .emit(DebugViewMsg::XRServiceActiveChanged(false));
if let Some(worker) = self.xrservice_worker.as_ref() { if code != 0 && code != 15 {
if let Some(code) = worker.exit_code() { // 15 is SIGTERM
if code != 0 && code != 15 { sender.input(Msg::OnServiceLog(vec![format!(
// 15 is SIGTERM "{} exited with code {}",
sender.input(Msg::OnServiceLog(vec![format!( self.get_selected_profile().xrservice_type,
"{} exited with code {}", code
self.get_selected_profile().xrservice_type, )]));
code
)]));
}
}
} }
self.xrservice_worker = None; self.xrservice_worker = None;
if self.restart_xrservice { if self.restart_xrservice {
@ -350,6 +369,7 @@ impl SimpleComponent for App {
self.start_xrservice(sender, false); self.start_xrservice(sender, false);
} }
} }
Msg::OnAutostartExit(_) => self.autostart_worker = None,
Msg::ClockTicking => { Msg::ClockTicking => {
self.main_view.sender().emit(MainViewMsg::ClockTicking); self.main_view.sender().emit(MainViewMsg::ClockTicking);
if let Some(w) = self.xrservice_worker.as_ref() { if let Some(w) = self.xrservice_worker.as_ref() {
@ -798,6 +818,7 @@ impl SimpleComponent for App {
config, config,
profiles, profiles,
xrservice_worker: None, xrservice_worker: None,
autostart_worker: None,
build_worker: None, build_worker: None,
xr_devices: vec![], xr_devices: vec![],
fbt_config_editor: None, fbt_config_editor: None,

View file

@ -7,12 +7,21 @@ pub struct CmdWorkerData {
pub args: Vec<String>, pub args: Vec<String>,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
pub struct FuncWorkerOut { pub struct FuncWorkerOut {
pub success: bool, pub success: bool,
pub out: Vec<String>, pub out: Vec<String>,
} }
impl Default for FuncWorkerOut {
fn default() -> Self {
Self {
success: true,
out: vec![],
}
}
}
pub struct FuncWorkerData { pub struct FuncWorkerData {
pub func: Box<dyn FnOnce() -> FuncWorkerOut + Send + Sync + 'static>, pub func: Box<dyn FnOnce() -> FuncWorkerOut + Send + Sync + 'static>,
} }

View file

@ -1,6 +1,6 @@
use self::{ use self::{
internal_worker::{InternalJobWorker, JobWorkerInit, JobWorkerMsg, JobWorkerOut}, internal_worker::{InternalJobWorker, JobWorkerInit, JobWorkerMsg, JobWorkerOut},
job::WorkerJob, job::{FuncWorkerOut, WorkerJob},
state::JobWorkerState, state::JobWorkerState,
}; };
use crate::profile::Profile; use crate::profile::Profile;
@ -44,6 +44,32 @@ impl JobWorker {
} }
} }
pub fn new_with_timer<X: 'static, F: (Fn(JobWorkerOut) -> X) + 'static>(
duration: Duration,
job: WorkerJob,
sender: &Sender<X>,
transform: F,
) -> Self {
let timer_job = WorkerJob::new_func(Box::new(move || {
thread::sleep(duration);
FuncWorkerOut::default()
}));
let mut jobs = VecDeque::new();
let state = Arc::new(Mutex::new(JobWorkerState::default()));
jobs.push_back(timer_job);
jobs.push_back(job);
let init = JobWorkerInit {
jobs,
state: state.clone(),
};
Self {
worker: InternalJobWorker::builder()
.detach_worker(init)
.forward(sender, transform),
state,
}
}
pub fn xrservice_worker_wrap_from_profile<X: 'static, F: (Fn(JobWorkerOut) -> X) + 'static>( pub fn xrservice_worker_wrap_from_profile<X: 'static, F: (Fn(JobWorkerOut) -> X) + 'static>(
prof: &Profile, prof: &Profile,
sender: &Sender<X>, sender: &Sender<X>,

View file

@ -113,6 +113,14 @@ impl SimpleComponent for ProfileEditor {
prof.borrow_mut().prefix = n_path.unwrap_or_default() prof.borrow_mut().prefix = n_path.unwrap_or_default()
}), }),
), ),
add: &entry_row("Autostart Command",
model.profile.borrow().autostart_command.as_ref().unwrap_or(&String::default()),
clone!(@strong prof => move |row| {
let txt = row.text().trim().to_string();
prof.borrow_mut().autostart_command =
if txt.is_empty() {None} else {Some(txt)};
})
)
}, },
add: xrservicegrp = &adw::PreferencesGroup { add: xrservicegrp = &adw::PreferencesGroup {
set_title: "XR Service", set_title: "XR Service",