diff --git a/src/backend/notifications.rs b/src/backend/notifications.rs index 8c55049..60eeae2 100644 --- a/src/backend/notifications.rs +++ b/src/backend/notifications.rs @@ -4,7 +4,13 @@ use dbus::{ message::MatchRule, }; use serde::Deserialize; -use std::sync::{mpsc, Arc}; +use std::{ + sync::{ + mpsc::{self}, + Arc, + }, + time::Duration, +}; use crate::{overlays::toast::Toast, state::AppState}; @@ -25,6 +31,10 @@ impl NotificationManager { } pub fn submit_pending(&self, app: &mut AppState) { + if let Some((c, _)) = &self.dbus_data { + let _ = c.process(Duration::ZERO); + } + self.rx_toast.try_iter().for_each(|toast| { toast.submit(app); }); @@ -44,20 +54,20 @@ impl NotificationManager { let sender = self.tx_toast.clone(); - let token = c.start_receive( - rule, - Box::new(move |msg, _| { - if let Ok(toast) = parse_dbus(&msg) { - match sender.try_send(toast) { - Ok(_) => {} - Err(e) => { - log::error!("Failed to send notification: {:?}", e); - } + let Ok(token) = c.add_match(rule, move |_: (), _, msg| { + if let Ok(toast) = parse_dbus(&msg) { + match sender.try_send(toast) { + Ok(_) => {} + Err(e) => { + log::error!("Failed to send notification: {:?}", e); } } - true - }), - ); + } + true + }) else { + log::error!("Failed to add dbus match. Desktop notifications will not work."); + return; + }; self.dbus_data = Some((c, token)); } @@ -73,7 +83,7 @@ impl NotificationManager { return; } }; - let mut buf = [0u8; 1500]; + let mut buf = [0u8; 1024 * 16]; // vrcx embeds icons as b64 loop { if let Ok((num_bytes, _)) = socket.recv_from(&mut buf) { @@ -84,6 +94,7 @@ impl NotificationManager { continue; } }; + log::info!("Received notification message: {}", json_str); let msg = match serde_json::from_str::(json_str) { Ok(m) => m, Err(e) => { @@ -92,10 +103,13 @@ impl NotificationManager { } }; + if msg.messageType != 1 { + continue; + } + let toast = Toast::new(msg.title, msg.content.unwrap_or_else(|| "".into())) - .with_timeout(msg.timeout) - .with_volume(msg.volume) - .with_opacity(msg.opacity); + .with_timeout(msg.timeout.unwrap_or(5.)) + .with_sound(msg.volume.unwrap_or(0.) > 0.1); match sender.try_send(toast) { Ok(_) => {} @@ -131,23 +145,26 @@ fn parse_dbus(msg: &dbus::Message) -> anyhow::Result { summary }; - Ok(Toast::new(title.into(), body.into())) + Ok(Toast::new(title.into(), body.into()) + .with_sound(true) + .with_timeout(5.0) + .with_opacity(1.0)) } #[allow(non_snake_case)] #[derive(Debug, Deserialize)] struct XsoMessage { messageType: i32, - index: i32, - volume: f32, - audioPath: Arc, - timeout: f32, + index: Option, + volume: Option, + audioPath: Option>, + timeout: Option, title: Arc, content: Option>, icon: Option>, - height: f32, - opacity: f32, - useBase64Icon: bool, + height: Option, + opacity: Option, + useBase64Icon: Option, sourceApp: Option>, - alwaysShow: bool, + alwaysShow: Option, } diff --git a/src/overlays/keyboard.rs b/src/overlays/keyboard.rs index b7d9870..6911572 100644 --- a/src/overlays/keyboard.rs +++ b/src/overlays/keyboard.rs @@ -13,12 +13,12 @@ use crate::{ config, gui::{color_parse, CanvasBuilder, Control}, hid::{KeyModifier, VirtualKey, ALT, CTRL, KEYS_TO_MODS, META, SHIFT, SUPER}, - state::{AppSession, AppState}, + state::AppState, }; use glam::{vec2, vec3a, Affine2, Vec4}; use once_cell::sync::Lazy; use regex::Regex; -use rodio::{Decoder, OutputStream, OutputStreamHandle, Source}; +use rodio::{Decoder, Source}; use serde::{Deserialize, Serialize}; const PIXELS_PER_UNIT: f32 = 80.; @@ -39,9 +39,6 @@ where let data = KeyboardData { modifiers: 0, processes: vec![], - audio_stream: None, - first_try: true, - audio_handle: None, }; let mut canvas = CanvasBuilder::new( @@ -142,7 +139,7 @@ fn key_press( ) { match control.state.as_mut() { Some(KeyButtonData::Key { vk, pressed }) => { - data.key_click(&app.session); + data.key_click(app); if let PointerMode::Right = mode { data.modifiers |= SHIFT; @@ -155,11 +152,11 @@ fn key_press( Some(KeyButtonData::Modifier { modifier, sticky }) => { *sticky = data.modifiers & *modifier == 0; data.modifiers |= *modifier; - data.key_click(&app.session); + data.key_click(app); app.hid_provider.set_modifiers(data.modifiers); } Some(KeyButtonData::Macro { verbs }) => { - data.key_click(&app.session); + data.key_click(app); for (vk, press) in verbs { app.hid_provider.send_key(*vk as _, *press); } @@ -169,7 +166,7 @@ fn key_press( data.processes .retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_)))); - data.key_click(&app.session); + data.key_click(app); if let Ok(child) = Command::new(program).args(args).spawn() { data.processes.push(child); } @@ -228,28 +225,16 @@ fn test_highlight( struct KeyboardData { modifiers: KeyModifier, processes: Vec, - audio_stream: Option, - audio_handle: Option, - first_try: bool, } impl KeyboardData { - fn key_click(&mut self, session: &AppSession) { - if !session.config.keyboard_sound_enabled { + fn key_click(&mut self, app: &mut AppState) { + if !app.session.config.keyboard_sound_enabled { return; } - if self.audio_stream.is_none() && self.first_try { - self.first_try = false; - if let Ok((stream, handle)) = OutputStream::try_default() { - self.audio_stream = Some(stream); - self.audio_handle = Some(handle); - } else { - log::error!("Failed to open audio stream"); - } - } - - if let Some(handle) = &self.audio_handle { + if let Some(handle) = app.audio.get_handle() { + // https://freesound.org/people/UberBosser/sounds/421581/ let wav = include_bytes!("../res/421581.wav"); let cursor = Cursor::new(wav); let source = Decoder::new_wav(cursor).unwrap(); diff --git a/src/overlays/toast.rs b/src/overlays/toast.rs index 22784e3..fb7d591 100644 --- a/src/overlays/toast.rs +++ b/src/overlays/toast.rs @@ -1,8 +1,11 @@ use std::{ + io::Cursor, ops::Add, sync::{atomic::AtomicUsize, Arc}, }; +use rodio::{Decoder, Source}; + use glam::vec3a; use crate::{ @@ -24,8 +27,8 @@ pub struct Toast { pub title: Arc, pub body: Arc, pub opacity: f32, - pub volume: f32, pub timeout: f32, + pub sound: bool, } #[allow(dead_code)] @@ -36,7 +39,7 @@ impl Toast { body, opacity: 1.0, timeout: 3.0, - volume: 0.0, + sound: false, } } pub fn with_timeout(mut self, timeout: f32) -> Self { @@ -47,8 +50,8 @@ impl Toast { self.opacity = opacity; self } - pub fn with_volume(mut self, volume: f32) -> Self { - self.volume = volume; + pub fn with_sound(mut self, sound: bool) -> Self { + self.sound = sound; self } pub fn submit(self, app: &mut AppState) { @@ -59,6 +62,8 @@ impl Toast { let destroy_at = std::time::Instant::now().add(std::time::Duration::from_secs_f32(self.timeout)); + let has_sound = self.sound; + app.tasks.enqueue(TaskType::CreateOverlay( selector.clone(), Box::new(move |app| new_toast(self, name, app)), @@ -66,6 +71,15 @@ impl Toast { app.tasks .enqueue_at(TaskType::DropOverlay(selector), destroy_at); + + if has_sound { + if let Some(handle) = app.audio.get_handle() { + let wav = include_bytes!("../res/557297.wav"); + let cursor = Cursor::new(wav); + let source = Decoder::new_wav(cursor).unwrap(); + let _ = handle.play_raw(source.convert_samples()); + } + } } } @@ -91,9 +105,11 @@ fn new_toast( .ok()?; (w0.max(w1), h1 + 50.) } else { - app.fc + let (w, h) = app + .fc .get_text_size(&title, FONT_SIZE, app.graphics.clone()) - .ok()? + .ok()?; + (w, h + 20.) }; let og_width = size.0; @@ -123,7 +139,7 @@ fn new_toast( canvas.label_centered(PADDING.0, 16., og_width, FONT_SIZE as f32 + 2., title); } else { log::info!("Toast: {}", title); - canvas.label(0., 0., size.0, size.1, title); + canvas.label_centered(PADDING.0, 0., og_width, size.1, title); } let state = OverlayState { diff --git a/src/overlays/watch.rs b/src/overlays/watch.rs index c959a70..f9ed2c0 100644 --- a/src/overlays/watch.rs +++ b/src/overlays/watch.rs @@ -1,6 +1,6 @@ use std::{ f32::consts::PI, - io::Read, + io::{Cursor, Read}, process::{self, Stdio}, sync::Arc, time::Instant, @@ -9,6 +9,7 @@ use std::{ use chrono::Local; use chrono_tz::Tz; use glam::{Quat, Vec3, Vec3A}; +use rodio::{Decoder, Source}; use serde::Deserialize; use crate::{ @@ -395,6 +396,15 @@ fn btn_mirror_dn( } } +fn audio_thump(app: &mut AppState) { + if let Some(handle) = app.audio.get_handle() { + let wav = include_bytes!("../res/380885.wav"); + let cursor = Cursor::new(wav); + let source = Decoder::new_wav(cursor).unwrap(); + let _ = handle.play_raw(source.convert_samples()); + } +} + fn btn_func_dn( control: &mut Control<(), ElemState>, _: &mut (), @@ -434,6 +444,7 @@ fn btn_func_dn( .submit(app); }), )); + audio_thump(app); } ButtonFunc::SwitchWatchHand => { app.tasks.enqueue(TaskType::Overlay( @@ -456,6 +467,7 @@ fn btn_func_dn( .submit(app); }), )); + audio_thump(app); } } } @@ -736,6 +748,7 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut } }), )); + audio_thump(app); } PointerMode::Middle => { app.tasks.enqueue(TaskType::Overlay( @@ -751,6 +764,7 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut } }), )); + audio_thump(app); } _ => {} } diff --git a/src/res/380885.wav b/src/res/380885.wav new file mode 100644 index 0000000..be1b804 Binary files /dev/null and b/src/res/380885.wav differ diff --git a/src/res/557297.wav b/src/res/557297.wav new file mode 100644 index 0000000..6bc3ef8 Binary files /dev/null and b/src/res/557297.wav differ diff --git a/src/state.rs b/src/state.rs index 7d7b154..e62181a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,6 +2,7 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::bail; use glam::{Quat, Vec3}; +use rodio::{OutputStream, OutputStreamHandle}; use vulkano::format::Format; use crate::{ @@ -25,6 +26,7 @@ pub struct AppState { pub format: vulkano::format::Format, pub input_state: InputState, pub hid_provider: Box, + pub audio: AudioOutput, } impl AppState { @@ -59,6 +61,7 @@ impl AppState { format: Format::R8G8B8A8_UNORM, input_state: InputState::new(), hid_provider: crate::hid::initialize(), + audio: AudioOutput::new(), }) } } @@ -115,3 +118,30 @@ impl AppSession { } } } + +pub struct AudioOutput { + audio_stream: Option<(OutputStream, OutputStreamHandle)>, + first_try: bool, +} + +impl AudioOutput { + pub fn new() -> Self { + AudioOutput { + audio_stream: None, + first_try: true, + } + } + + pub fn get_handle(&mut self) -> Option<&OutputStreamHandle> { + if self.audio_stream.is_none() && self.first_try { + self.first_try = false; + if let Ok((stream, handle)) = OutputStream::try_default() { + self.audio_stream = Some((stream, handle)); + } else { + log::error!("Failed to open audio stream"); + return None; + } + } + self.audio_stream.as_ref().map(|(_, h)| h) + } +}