app.tick() + whisper auto-unload

This commit is contained in:
galister 2026-07-04 03:20:04 +09:00
parent 90f8ecafe5
commit 00a9dd2b2f
8 changed files with 80 additions and 69 deletions

View File

@ -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;

View File

@ -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::<OpenVrOverlayData>::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() {

View File

@ -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 {

View File

@ -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<WhisperStt>,
clipboard_provider: Option<Box<dyn ClipboardProvider>>,
last_transcription: Option<Rc<str>>,
}
@ -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<AppState, WhisperState> = 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<AppState, WhisperState> =
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<str> = text.into();
state.last_transcription = Some(text.clone());
let label = data.obj.get_as_mut::<WidgetLabel>().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<str> = text.into();
state.last_transcription = Some(text.clone());
let label = data.obj.get_as_mut::<WidgetLabel>().unwrap();
label.set_text(common, Translation::from_raw_text_rc(text));
}
}
Ok(EventResult::Pass)
});

View File

@ -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<WvrServerState>,
#[cfg(feature = "whisper")]
pub whisper_sst: Option<WhisperStt>,
#[cfg(feature = "openxr")]
pub monado_state: Option<backend::openxr::monado_state::MonadoState>,
@ -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;
}
}
}

View File

@ -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<Toast>,
@ -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<Toast> {
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()
}
}

View File

@ -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<Result<String, String>>,
last_error: Option<String>,
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<String> {
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) {

View File

@ -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()