diff --git a/wayvr/src/backend/input.rs b/wayvr/src/backend/input.rs index 956327c9..d6c69992 100644 --- a/wayvr/src/backend/input.rs +++ b/wayvr/src/backend/input.rs @@ -20,7 +20,7 @@ use crate::subsystem::hid::WheelDelta; use crate::subsystem::input::KeyboardFocus; use crate::windowing::backend::OverlayEventData; use crate::windowing::manager::OverlayWindowManager; -use crate::windowing::window::{self, OverlayWindowData, realign, scalar_scale, window_scale}; +use crate::windowing::window::{self, OverlayWindowData, realign, scalar_scale}; use crate::windowing::{OverlayID, OverlaySelector}; use super::task::TaskType; diff --git a/wayvr/src/backend/openvr/mod.rs b/wayvr/src/backend/openvr/mod.rs index a44696b1..90d212f4 100644 --- a/wayvr/src/backend/openvr/mod.rs +++ b/wayvr/src/backend/openvr/mod.rs @@ -32,7 +32,6 @@ use crate::{ graphics::{GpuFutures, init_openvr_graphics}, overlays::toast::Toast, state::AppState, - subsystem::notifications::NotificationManager, windowing::{ backend::{RenderResources, RenderTarget, ShouldRender}, manager::OverlayWindowManager, @@ -112,12 +111,11 @@ pub fn openvr_run( log::info!("IPD: {:.0} mm", app.input_state.ipd); } + app.late_init(); + let _ = install_manifest(&mut app_mgr); let mut overlays = OverlayWindowManager::::new(&mut app, headless)?; - let mut notifications = NotificationManager::new(); - notifications.run_dbus(&mut app.dbus); - notifications.run_udp(); let mut playspace = playspace::PlayspaceMover::new(); playspace.playspace_changed(&mut compositor_mgr, &mut chaperone_mgr); @@ -215,9 +213,7 @@ pub fn openvr_run( next_device_update = Instant::now() + Duration::from_secs(30); } - app.dbus.tick(); - notifications.submit_pending(&mut app); - + app.tick(); app.tasks.retrieve_due(&mut due_tasks); while let Some(task) = due_tasks.pop_front() { diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index 535aa9cd..d0afb393 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -24,7 +24,6 @@ use crate::{ graphics::{GpuFutures, init_openxr_graphics}, overlays::{toast::Toast, watch::WATCH_NAME}, state::AppState, - subsystem::notifications::NotificationManager, windowing::{ backend::{RenderResources, RenderTarget, ShouldRender}, manager::OverlayWindowManager, @@ -86,13 +85,9 @@ pub fn openxr_run( let mut lines = LinePool::new(&app)?; let mut current_lines = Vec::with_capacity(2); - let mut notifications = NotificationManager::new(); - notifications.run_dbus(&mut app.dbus); - notifications.run_udp(); - let mut delete_queue = vec![]; - app.monado_state_init(); + app.late_init(); let mut playspace_mover = app.monado_state.as_mut().and_then(|m| { playspace::PlayspaceMover::new(&mut m.ipc) @@ -475,9 +470,7 @@ pub fn openxr_run( )?; // End layer submit - app.dbus.tick(); - notifications.submit_pending(&mut app); - + app.tick(); app.tasks.retrieve_due(&mut due_tasks); while let Some(task) = due_tasks.pop_front() { match task { diff --git a/wayvr/src/overlays/whisper.rs b/wayvr/src/overlays/whisper.rs index c3c34aa1..10e3c615 100644 --- a/wayvr/src/overlays/whisper.rs +++ b/wayvr/src/overlays/whisper.rs @@ -1,6 +1,6 @@ use std::{rc::Rc, time::Duration}; -use glam::{Affine3A, Quat, Vec3, vec3}; +use glam::Affine3A; use wgui::{ components::button::ComponentButton, event::EventCallback, @@ -38,11 +38,9 @@ use crate::{ }, }; -pub const WHISPER_NAME: &str = "whisper"; +const WHISPER_NAME: &str = "whisper"; -#[derive(Default)] struct WhisperState { - whisper_sst: Option, clipboard_provider: Option>, last_transcription: Option>, } @@ -92,7 +90,7 @@ pub fn create_whisper( let state = WhisperState { clipboard_provider, - ..Default::default() + last_transcription: None, }; let xml = "gui/whisper.xml"; @@ -118,18 +116,18 @@ pub fn create_whisper( let button = button.clone(); let callback: EventCallback = match command { - "::WhisperTranscribeStart" => Box::new(move |_common, data, app, state| { + "::WhisperTranscribeStart" => Box::new(move |_common, data, app, _state| { if !test_button(data) || !test_duration(&button, app) { return Ok(EventResult::Pass); } - let whisper = match state.whisper_sst.as_mut() { + let whisper = match app.whisper_sst.as_mut() { Some(x) => x, None => { let model_path = data_dir::get_path("whisper") .join(app.session.config.whisper_model.as_ref()); if model_path.is_file() { - state.whisper_sst = match WhisperStt::new(model_path) + app.whisper_sst = match WhisperStt::new(model_path) .log_err("Error while starting Whisper engine") { Ok(x) => Some(x), @@ -157,7 +155,7 @@ pub fn create_whisper( return Ok(EventResult::Consumed); } - state.whisper_sst.as_mut().unwrap() + app.whisper_sst.as_mut().unwrap() } }; @@ -167,12 +165,12 @@ pub fn create_whisper( Ok(EventResult::Consumed) }), - "::WhisperTranscribeStop" => Box::new(move |_common, data, app, state| { + "::WhisperTranscribeStop" => Box::new(move |_common, data, app, _state| { if !test_button(data) || !test_duration(&button, app) { return Ok(EventResult::Pass); } - if let Some(whisper) = state.whisper_sst.as_mut() { + if let Some(whisper) = app.whisper_sst.as_mut() { let _ = whisper .ptt_end() .log_err("Could not stop Whisper transcription"); @@ -240,12 +238,12 @@ pub fn create_whisper( Ok(EventResult::Consumed) }), - "::WhisperUnloadAndClose" => Box::new(move |_common, data, app, state| { + "::WhisperUnloadAndClose" => Box::new(move |_common, data, app, _state| { if !test_button(data) || !test_duration(&button, app) { return Ok(EventResult::Pass); } - state.whisper_sst = None; + app.whisper_sst = None; app.tasks .enqueue(TaskType::Overlay(OverlayTask::ToggleOverlay( OverlaySelector::Name(WHISPER_NAME.into()), @@ -297,14 +295,14 @@ pub fn create_whisper( .push(GuiTimer::new(Duration::from_millis(100), 0)); let on_label_tick: EventCallback = - Box::new(move |common, data, _app, state| { - if let Some(whisper_stt) = state.whisper_sst.as_mut() - && let Some(text) = whisper_stt.take_transcription() - { - let text: Rc = text.into(); - state.last_transcription = Some(text.clone()); - let label = data.obj.get_as_mut::().unwrap(); - label.set_text(common, Translation::from_raw_text_rc(text)); + Box::new(move |common, data, app, state| { + if let Some(whisper_stt) = app.whisper_sst.as_mut() { + if let Some(text) = whisper_stt.take_transcription() { + let text: Rc = text.into(); + state.last_transcription = Some(text.clone()); + let label = data.obj.get_as_mut::().unwrap(); + label.set_text(common, Translation::from_raw_text_rc(text)); + } } Ok(EventResult::Pass) }); diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index de36c007..95a2b71b 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -22,9 +22,12 @@ use wlx_common::{ use crate::backend; use crate::backend::wayvr::WvrServerState; +use crate::subsystem::notifications::NotificationManager; #[cfg(feature = "osc")] use crate::subsystem::osc::OscSender; +#[cfg(feature = "whisper")] +use crate::subsystem::whisper_stt::WhisperStt; use crate::{ backend::{XrBackend, input::InputState, task::TaskContainer}, config::load_general_config, @@ -45,6 +48,8 @@ pub struct AppState { pub audio_system: audio::AudioSystem, pub audio_sample_player: audio::SamplePlayer, + pub notifications: NotificationManager, + pub wgui_shared: WSharedContext, pub input_state: InputState, @@ -69,6 +74,9 @@ pub struct AppState { pub wvr_server: Option, + #[cfg(feature = "whisper")] + pub whisper_sst: Option, + #[cfg(feature = "openxr")] pub monado_state: Option, @@ -182,11 +190,14 @@ impl AppState { ipc_server, wayvr_signals: wvr_signals, desktop_finder, + notifications: NotificationManager::new(), #[cfg(feature = "osc")] osc_sender, wvr_server, + #[cfg(feature = "whisper")] + whisper_sst: None, #[cfg(feature = "openxr")] monado_state: None, @@ -200,19 +211,38 @@ impl AppState { Ok(app_state) } - #[cfg(feature = "openxr")] - pub fn monado_state_init(&mut self) { - use crate::backend::openxr::monado_state::MonadoState; + pub fn late_init(&mut self) { + self.notifications.run_dbus(&mut self.dbus); + self.notifications.run_udp(); - log::debug!("Connecting to Monado IPC"); - self.monado_state = None; // stop connection first + if matches!(self.xr_backend, XrBackend::OpenXR) { + use crate::backend::openxr::monado_state::MonadoState; - match MonadoState::new() { - Ok(m) => { - self.monado_state = Some(m); + log::debug!("Connecting to Monado IPC"); + self.monado_state = None; // stop connection first + + match MonadoState::new() { + Ok(m) => { + self.monado_state = Some(m); + } + Err(e) => { + log::error!("Will not use libmonado: {e:?}"); + } } - Err(e) => { - log::error!("Will not use libmonado: {e:?}"); + } + } + + pub fn tick(&mut self) { + self.dbus.tick(); + + for toast in self.notifications.drain_pending(&self.session) { + toast.submit(self); + } + + #[cfg(feature = "whisper")] + { + if self.whisper_sst.as_ref().is_some_and(|x| x.should_unload()) { + self.whisper_sst = None; } } } diff --git a/wayvr/src/subsystem/notifications.rs b/wayvr/src/subsystem/notifications.rs index 149444fe..4bdd61d1 100644 --- a/wayvr/src/subsystem/notifications.rs +++ b/wayvr/src/subsystem/notifications.rs @@ -11,7 +11,7 @@ use std::{ }; use wlx_common::overlays::ToastTopic; -use crate::{overlays::toast::Toast, state::AppState, subsystem::dbus::DbusConnector}; +use crate::{overlays::toast::Toast, state::AppSession, subsystem::dbus::DbusConnector}; pub struct NotificationManager { rx_toast: mpsc::Receiver, @@ -29,14 +29,14 @@ impl NotificationManager { } } - pub fn submit_pending(&self, app: &mut AppState) { - if app.session.config.notifications_enabled { - self.rx_toast.try_iter().for_each(|toast| { - toast.submit(app); - }); + pub fn drain_pending(&self, session: &AppSession) -> Vec { + if session.config.notifications_enabled { + self.rx_toast.try_iter().collect() } else { // consume without submitting - self.rx_toast.try_iter().last(); + for _ in self.rx_toast.try_iter() {} + + Vec::new() } } diff --git a/wayvr/src/subsystem/whisper_stt.rs b/wayvr/src/subsystem/whisper_stt.rs index f0499931..654de566 100644 --- a/wayvr/src/subsystem/whisper_stt.rs +++ b/wayvr/src/subsystem/whisper_stt.rs @@ -14,6 +14,7 @@ use wlx_common::audio::rodio::{ const WHISPER_SAMPLE_RATE: usize = 16_000; const MAX_DURATION: Duration = Duration::from_secs(30); +const UNLOAD_AFTER: Duration = Duration::from_mins(5); #[derive(Clone, Debug)] pub struct WhisperSttConfig { @@ -107,6 +108,7 @@ pub struct WhisperStt { completed_tx: mpsc::Sender>, last_error: Option, + unload_at: Instant, } impl WhisperStt { @@ -133,11 +135,13 @@ impl WhisperStt { completed_rx, completed_tx, last_error: None, + unload_at: Instant::now() + UNLOAD_AFTER, }) } /// starts a fresh capture stream and a transcription worker pub fn ptt_start(&mut self) -> Result<(), WhisperSttError> { + self.unload_at = Instant::now() + UNLOAD_AFTER; self.reap_finished_recognizers(); if self.active.is_some() { @@ -237,6 +241,7 @@ impl WhisperStt { /// stops the pw stream & finalizes recognition asynchronously /// poll `take_transcription()` from your main loop to receive transcription pub fn ptt_end(&mut self) -> Result<(), WhisperSttError> { + self.unload_at = Instant::now() + UNLOAD_AFTER; self.stop_active_capture() } @@ -246,6 +251,7 @@ impl WhisperStt { let latest = self.drain_completed_transcriptions(); if latest.is_some() { + self.unload_at = Instant::now() + UNLOAD_AFTER; return latest; } @@ -263,12 +269,8 @@ impl WhisperStt { None } - pub fn take_error(&mut self) -> Option { - self.last_error.take() - } - - pub fn is_recording(&self) -> bool { - self.active.is_some() + pub fn should_unload(&self) -> bool { + self.unload_at < Instant::now() } fn reap_finished_recognizers(&mut self) { diff --git a/wayvr/src/windowing/window.rs b/wayvr/src/windowing/window.rs index cda85cab..0060a5e5 100644 --- a/wayvr/src/windowing/window.rs +++ b/wayvr/src/windowing/window.rs @@ -290,14 +290,6 @@ pub fn realign(transform: &mut Affine3A, hmd: &Affine3A, scale: f32) { transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot; } -pub fn window_scale(state: &OverlayWindowState) -> f32 { - state - .saved_transform - .as_ref() - .map(scalar_scale) - .unwrap_or_else(|| scalar_scale(&state.transform)) -} - pub fn scalar_scale(a: &Affine3A) -> f32 { let det = a.matrix3.determinant(); (a.matrix3.x_axis.length() * det.signum()