Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 054a9c4

Browse files
committedApr 8, 2024
Merge branch 'release/1.12.7'
2 parents f506d9b + 7d382d0 commit 054a9c4

File tree

29 files changed

+1900
-1100
lines changed

29 files changed

+1900
-1100
lines changed
 

‎.github/workflows/build-steam-beta-release.yml‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ on:
66

77
jobs:
88
build-steam-release:
9-
concurrency:
10-
group: ${{ github.workflow }}-${{ github.ref }}-steam-beta
11-
cancel-in-progress: true
129
strategy:
1310
fail-fast: false
1411
matrix:

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.12.7]
9+
10+
### Changed
11+
- Improved device detection for Bigscreen Beyond
12+
- Increased timeout duration for initialization tasks
13+
- Improved telemetry
14+
15+
### Fixed
16+
- Disabled swipe navigation in main window
17+
818
## [1.12.6]
919

1020
### Changed

‎package-lock.json‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "oyasumi",
3-
"version": "1.12.6",
3+
"version": "1.12.7",
44
"author": "Raphiiko",
55
"license": "MIT",
66
"type": "module",

‎src-core/Cargo.lock‎

Lines changed: 1445 additions & 994 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src-core/Cargo.toml‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
[package]
22
name = "oyasumivr"
3-
version = "1.12.6"
3+
version = "1.12.7"
44
description = ""
55
authors = ["Raphiiko"]
66
license = "MIT"
77
repository = "https://github.com/Raphiiko/Oyasumi"
88
default-run = "oyasumivr"
99
edition = "2021"
10-
rust-version = "1.70.0"
10+
rust-version = "1.71.1"
1111

1212
[build-dependencies]
1313
tauri-build = { version = "1.5.1", features = [] }
@@ -19,6 +19,7 @@ serde_json = "1.0.108"
1919
serde = { version = "1.0.193", features = ["derive"] }
2020
lazy_static = "1.4.0"
2121
cronjob = "0.4.17"
22+
webview2-com = "0.19"
2223
rosc = "0.10.1"
2324
futures = "0.3"
2425
reqwest = "0.11.17"
@@ -124,7 +125,7 @@ features = [
124125

125126
[dependencies.ovr_overlay]
126127
git = "https://github.com/Raphiiko/ovr_overlay_oyasumi"
127-
rev = "7f2fcc1059e5ca8d586d2a46d2751769f779713d"
128+
rev = "b7e51d0"
128129
# path = "../../ovr_overlay_oyasumi"
129130
features = ["ovr_system", "ovr_settings"]
130131

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
use std::os::windows::ffi::OsStrExt;
2+
use std::{ffi::OsStr, iter::once};
3+
4+
use hidapi::HidApi;
5+
use log::error;
6+
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
7+
use winapi::shared::minwindef::{LPARAM, LRESULT, UINT, WPARAM};
8+
use winapi::um::libloaderapi::GetModuleHandleW;
9+
use winapi::um::winuser::{
10+
DefWindowProcW, DispatchMessageW, GetMessageW, GetWindowLongPtrW, PostQuitMessage,
11+
SetWindowLongPtrW, TranslateMessage, GWLP_USERDATA, MSG, WM_CREATE, WM_DESTROY,
12+
WM_DEVICECHANGE,
13+
};
14+
use winapi::{
15+
shared::{
16+
ntdef::LPCWSTR,
17+
windef::{HBRUSH, HCURSOR, HICON, HWND},
18+
},
19+
um::winuser::{CreateWindowExW, RegisterClassW, WNDCLASSW},
20+
};
21+
22+
pub enum PnPDetectorEvent {
23+
Plug { device_ref: PnPDetectorDevice },
24+
Unplug { device_ref: PnPDetectorDevice },
25+
}
26+
27+
#[derive(PartialEq, Clone)]
28+
pub struct PnPDetectorDevice {
29+
pub vid: u16,
30+
pub pid: u16,
31+
}
32+
33+
pub struct PnPDetector {
34+
hwnd: HWND,
35+
hidapi: HidApi,
36+
tx: UnboundedSender<PnPDetectorEvent>,
37+
known_devices: Vec<PnPDetectorDevice>,
38+
}
39+
40+
impl PnPDetector {
41+
pub fn start() -> UnboundedReceiver<PnPDetectorEvent> {
42+
let (tx, rx) = unbounded_channel::<PnPDetectorEvent>();
43+
tokio::task::spawn_blocking(move || {
44+
let hidapi = match HidApi::new() {
45+
Ok(a) => a,
46+
Err(e) => {
47+
error!("[Core] Failed to initialize HIDAPI: {}", e);
48+
return;
49+
}
50+
};
51+
let mut detector = Self {
52+
hwnd: std::ptr::null_mut(),
53+
hidapi,
54+
tx,
55+
known_devices: Vec::new(),
56+
};
57+
detector.create_window();
58+
detector.list_devices(false);
59+
detector.detect();
60+
});
61+
rx
62+
}
63+
64+
fn handle_hotplug_event(&mut self) {
65+
self.list_devices(true);
66+
}
67+
68+
fn list_devices(&mut self, emit_events: bool) {
69+
let _ = self.hidapi.refresh_devices();
70+
// Check for added devices
71+
let devices = self.hidapi.device_list();
72+
for device_info in devices {
73+
// Check if device already in known devices
74+
let device = PnPDetectorDevice {
75+
vid: device_info.vendor_id(),
76+
pid: device_info.product_id(),
77+
};
78+
if self.known_devices.contains(&device) {
79+
continue;
80+
}
81+
// Add device to known devices
82+
self.known_devices.push(device.clone());
83+
// Emit event
84+
if emit_events {
85+
self.tx
86+
.send(PnPDetectorEvent::Plug {
87+
device_ref: device.clone(),
88+
})
89+
.unwrap();
90+
}
91+
}
92+
// Check for removed devices
93+
let mut removed_devices = Vec::new();
94+
for known_device in &self.known_devices {
95+
let mut found = false;
96+
let devices = self.hidapi.device_list();
97+
for device_info in devices {
98+
let device = PnPDetectorDevice {
99+
vid: device_info.vendor_id(),
100+
pid: device_info.product_id(),
101+
};
102+
if known_device == &device {
103+
found = true;
104+
break;
105+
}
106+
}
107+
if !found {
108+
removed_devices.push(known_device.clone());
109+
}
110+
}
111+
for removed_device in removed_devices {
112+
self.known_devices.retain(|d| d != &removed_device);
113+
// Emit event
114+
if emit_events {
115+
self.tx
116+
.send(PnPDetectorEvent::Unplug {
117+
device_ref: removed_device,
118+
})
119+
.unwrap();
120+
}
121+
}
122+
}
123+
124+
/// Detect USB events: just run a Windows event loop
125+
fn detect(&self) {
126+
unsafe {
127+
let mut msg: MSG = std::mem::MaybeUninit::zeroed().assume_init();
128+
loop {
129+
let val = GetMessageW(&mut msg, self.hwnd, 0, 0);
130+
if val == 0 {
131+
break;
132+
} else {
133+
TranslateMessage(&msg);
134+
DispatchMessageW(&msg);
135+
}
136+
}
137+
}
138+
}
139+
140+
/// Window procedure function to handle events
141+
pub unsafe extern "system" fn window_proc(
142+
hwnd: HWND,
143+
msg: UINT,
144+
wparam: WPARAM,
145+
lparam: LPARAM,
146+
) -> LRESULT {
147+
match msg {
148+
WM_CREATE => {
149+
let create_struct = lparam as *mut winapi::um::winuser::CREATESTRUCTW;
150+
let window_state_ptr = create_struct.as_ref().unwrap().lpCreateParams;
151+
SetWindowLongPtrW(hwnd, GWLP_USERDATA, window_state_ptr as isize);
152+
}
153+
WM_DESTROY => {
154+
PostQuitMessage(0);
155+
}
156+
WM_DEVICECHANGE => {
157+
let self_ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
158+
let window_state: &mut Self = &mut *(self_ptr as *mut Self);
159+
window_state.handle_hotplug_event();
160+
}
161+
_ => return DefWindowProcW(hwnd, msg, wparam, lparam),
162+
}
163+
return 0;
164+
}
165+
166+
/// Create an invisible window to handle WM_DEVICECHANGE message
167+
fn create_window(&mut self) {
168+
let winapi_class_name: Vec<u16> = OsStr::new("OyasumiVRPnPDetectWindowClass")
169+
.encode_wide()
170+
.chain(once(0))
171+
.collect();
172+
let hinstance = unsafe { GetModuleHandleW(std::ptr::null()) };
173+
174+
let wc = WNDCLASSW {
175+
style: 0,
176+
lpfnWndProc: Some(Self::window_proc),
177+
cbClsExtra: 0,
178+
cbWndExtra: 0,
179+
hInstance: hinstance,
180+
hIcon: 0 as HICON,
181+
hCursor: 0 as HCURSOR,
182+
hbrBackground: 0 as HBRUSH,
183+
lpszMenuName: 0 as LPCWSTR,
184+
lpszClassName: winapi_class_name.as_ptr(),
185+
};
186+
187+
let error_code = unsafe { RegisterClassW(&wc) };
188+
assert_ne!(error_code, 0, "failed to register the window class");
189+
190+
let window_name: Vec<u16> = OsStr::new("OyasumiVRPnPDetectWindow")
191+
.encode_wide()
192+
.chain(once(0))
193+
.collect();
194+
195+
let hwnd = unsafe {
196+
CreateWindowExW(
197+
0,
198+
winapi_class_name.as_ptr(),
199+
window_name.as_ptr(),
200+
0,
201+
0,
202+
0,
203+
0,
204+
0,
205+
std::ptr::null_mut(),
206+
std::ptr::null_mut(),
207+
hinstance,
208+
self as *mut _ as *mut _,
209+
)
210+
};
211+
212+
if hwnd.is_null() {
213+
panic!("Something went wrong while creating a window");
214+
}
215+
self.hwnd = hwnd;
216+
}
217+
}

‎src-core/src/hardware/beyond/mod.rs‎

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use std::sync::atomic::{AtomicBool, Ordering};
22

33
use hidapi::{HidApi, HidDevice};
4-
use log::{error, info};
4+
use log::{error, info, warn};
55
use tokio::sync::Mutex;
66

77
use crate::utils::send_event;
88

99
pub mod commands;
10+
mod detector;
1011

1112
const BIGSCREEN_VID: u16 = 0x35bd;
1213
const BEYOND_PID: u16 = 0x0101;
@@ -17,53 +18,79 @@ lazy_static! {
1718
}
1819

1920
pub async fn init() {
20-
let mut api = match HidApi::new() {
21-
Ok(a) => a,
22-
Err(e) => {
23-
error!("[Core] Failed to initialize HIDAPI: {}", e);
24-
return;
25-
}
26-
};
27-
2821
tokio::spawn(async move {
22+
let mut api = match HidApi::new() {
23+
Ok(a) => a,
24+
Err(e) => {
25+
error!("[Core] Failed to initialize HIDAPI: {}", e);
26+
return;
27+
}
28+
};
29+
// Check if beyond is currently connected
30+
match api.refresh_devices() {
31+
Ok(_) => {}
32+
Err(e) => {
33+
error!("[Core][Beyond] Could not refresh device list: {}", e);
34+
return;
35+
}
36+
}
37+
let devices = api.device_list();
38+
for device_info in devices {
39+
if device_info.vendor_id() == BIGSCREEN_VID && device_info.product_id() == BEYOND_PID {
40+
on_bsb_plugged(&api).await;
41+
break;
42+
}
43+
}
44+
// Detect USB plug/unplug events
45+
let mut detector = detector::PnPDetector::start();
2946
loop {
30-
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
31-
let _ = api.refresh_devices();
32-
let devices = api.device_list();
33-
let mut device: Option<HidDevice> = None;
34-
for device_info in devices {
35-
if device_info.vendor_id() == BIGSCREEN_VID
36-
&& device_info.product_id() == BEYOND_PID
37-
{
38-
device = Some(match device_info.open_device(&api) {
39-
Ok(d) => d,
40-
Err(e) => {
41-
error!(
42-
"[Core][Beyond] Could not open device for Bigscreen Beyond: {}",
43-
e
44-
);
45-
continue;
46-
}
47-
});
48-
break;
47+
let event = match detector.recv().await {
48+
Some(e) => e,
49+
None => {
50+
warn!("[Core][Beyond] PnP detector task terminated");
51+
return;
52+
}
53+
};
54+
match event {
55+
detector::PnPDetectorEvent::Plug { device_ref } => {
56+
if device_ref.vid == BIGSCREEN_VID && device_ref.pid == BEYOND_PID {
57+
on_bsb_plugged(&api).await;
58+
}
59+
}
60+
detector::PnPDetectorEvent::Unplug { device_ref } => {
61+
if device_ref.vid == BIGSCREEN_VID && device_ref.pid == BEYOND_PID {
62+
on_bsb_unplugged().await;
63+
}
4964
}
50-
}
51-
let connected = BSB_CONNECTED.load(Ordering::Relaxed);
52-
if connected && device.is_none() {
53-
*BSB_DEVICE.lock().await = None;
54-
BSB_CONNECTED.store(false, Ordering::Relaxed);
55-
info!("[Core] Bigscreen Beyond disconnected");
56-
send_event("BIGSCREEN_BEYOND_CONNECTED", false).await;
57-
} else if !connected && device.is_some() {
58-
*BSB_DEVICE.lock().await = device;
59-
BSB_CONNECTED.store(true, Ordering::Relaxed);
60-
info!("[Core] Bigscreen Beyond connected");
61-
send_event("BIGSCREEN_BEYOND_CONNECTED", true).await;
6265
}
6366
}
6467
});
6568
}
6669

70+
async fn on_bsb_plugged(api: &HidApi) {
71+
let device = match api.open(BIGSCREEN_VID, BEYOND_PID) {
72+
Ok(d) => d,
73+
Err(e) => {
74+
error!(
75+
"[Core][Beyond] Could not open device for Bigscreen Beyond: {}",
76+
e
77+
);
78+
return;
79+
}
80+
};
81+
*BSB_DEVICE.lock().await = Some(device);
82+
BSB_CONNECTED.store(true, Ordering::Relaxed);
83+
info!("[Core] Bigscreen Beyond connected");
84+
send_event("BIGSCREEN_BEYOND_CONNECTED", true).await;
85+
}
86+
87+
async fn on_bsb_unplugged() {
88+
*BSB_DEVICE.lock().await = None;
89+
BSB_CONNECTED.store(false, Ordering::Relaxed);
90+
info!("[Core] Bigscreen Beyond disconnected");
91+
send_event("BIGSCREEN_BEYOND_CONNECTED", false).await;
92+
}
93+
6794
pub fn set_led_color(device: &HidDevice, r: u8, g: u8, b: u8) -> Result<(), String> {
6895
match device.send_feature_report(&[0, 0x4c, r, g, b]) {
6996
Ok(_) => Ok(()),

‎src-core/src/main.rs‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ mod telemetry;
2828
mod utils;
2929
mod vrc_log_parser;
3030

31-
use std::sync::atomic::Ordering;
31+
use std::{mem, sync::atomic::Ordering};
3232

3333
use config::Config;
3434
pub use flavour::BUILD_FLAVOUR;
@@ -42,6 +42,7 @@ use serde_json::json;
4242
use tauri::{plugin::TauriPlugin, AppHandle, Manager, Wry};
4343
use tauri_plugin_aptabase::EventTracker;
4444
use tauri_plugin_log::{LogTarget, RotationStrategy};
45+
use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Settings6;
4546

4647
fn main() {
4748
tauri_plugin_deep_link::prepare("co.raphii.oyasumi.deeplink");
@@ -160,6 +161,7 @@ fn configure_command_handlers() -> impl Fn(tauri::Invoke) {
160161
elevated_sidecar::commands::start_elevated_sidecar,
161162
elevated_sidecar::commands::elevated_sidecar_get_grpc_web_port,
162163
elevated_sidecar::commands::elevated_sidecar_get_grpc_port,
164+
mdns_sidecar::commands::mdns_sidecar_started,
163165
overlay_sidecar::commands::start_overlay_sidecar,
164166
overlay_sidecar::commands::overlay_sidecar_get_grpc_web_port,
165167
overlay_sidecar::commands::overlay_sidecar_get_grpc_port,
@@ -291,12 +293,25 @@ async fn app_setup(app_handle: tauri::AppHandle) {
291293
load_configs().await;
292294
// Set up app reference
293295
*TAURI_APP_HANDLE.lock().await = Some(app_handle.clone());
296+
let window = app_handle.get_window("main").unwrap();
294297
// Open devtools if we're in debug mode
295298
#[cfg(debug_assertions)]
296299
{
297-
let window = app_handle.get_window("main").unwrap();
298300
window.open_devtools();
299301
}
302+
// Disable swipe navigation in main window
303+
window
304+
.with_webview(|webview| unsafe {
305+
let settings = webview
306+
.controller()
307+
.CoreWebView2()
308+
.unwrap()
309+
.Settings()
310+
.unwrap();
311+
let settings: ICoreWebView2Settings6 = mem::transmute(settings);
312+
settings.SetIsSwipeNavigationEnabled(false).unwrap();
313+
})
314+
.unwrap();
300315
// Get dependencies
301316
let cache_dir = app_handle.path_resolver().app_cache_dir().unwrap();
302317
// Initialize utility module
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#[tauri::command]
2+
#[oyasumivr_macros::command_profiling]
3+
pub async fn mdns_sidecar_started() -> bool {
4+
let mut sidecar_manager_guard = super::SIDECAR_MANAGER.lock().await;
5+
let sidecar_manager = sidecar_manager_guard.as_mut().unwrap();
6+
sidecar_manager.has_started().await
7+
}

‎src-core/src/mdns_sidecar/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use tokio::sync::Mutex;
22

3+
pub mod commands;
34
use crate::{
45
utils::{models::MdnsSidecarMode, send_event, sidecar_manager::SidecarManager},
56
Models::oyasumi_core::MdnsSidecarStartArgs,

‎src-core/src/openvr/devices.rs‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use super::models::{
66
use super::{GestureDetector, SleepDetector, OVR_CONTEXT};
77
use crate::utils::send_event;
88
use byteorder::{ByteOrder, LE};
9-
use chrono::{Duration, NaiveDateTime, Utc};
9+
use chrono::{DateTime, Duration, Utc};
1010
use log::error;
1111
use ovr::input::InputValueHandle;
1212
use ovr::sys::EVRInputError;
@@ -18,10 +18,10 @@ lazy_static! {
1818
static ref OVR_DEVICES: Mutex<Vec<OVRDevice>> = Mutex::new(Vec::new());
1919
static ref SLEEP_DETECTOR: Mutex<SleepDetector> = Mutex::new(SleepDetector::new());
2020
static ref GESTURE_DETECTOR: Mutex<GestureDetector> = Mutex::new(GestureDetector::new());
21-
static ref NEXT_DEVICE_REFRESH: Mutex<NaiveDateTime> =
22-
Mutex::new(NaiveDateTime::from_timestamp_millis(0).unwrap());
23-
static ref NEXT_POSE_BROADCAST: Mutex<NaiveDateTime> =
24-
Mutex::new(NaiveDateTime::from_timestamp_millis(0).unwrap());
21+
static ref NEXT_DEVICE_REFRESH: Mutex<DateTime::<Utc>> =
22+
Mutex::new(DateTime::from_timestamp_millis(0).unwrap());
23+
static ref NEXT_POSE_BROADCAST: Mutex<DateTime::<Utc>> =
24+
Mutex::new(DateTime::from_timestamp_millis(0).unwrap());
2525
static ref DEVICE_CLASS_CACHE: Mutex<HashMap<u32, TrackedDeviceClass>> =
2626
Mutex::new(HashMap::new());
2727
static ref DEVICE_HANDLE_TYPE_CACHE: Mutex<HashMap<u32, OVRHandleType>> =
@@ -31,8 +31,8 @@ lazy_static! {
3131
pub async fn on_ovr_tick() {
3232
// Refresh all devices when needed
3333
let mut next_device_refresh = NEXT_DEVICE_REFRESH.lock().await;
34-
if (Utc::now().naive_utc() - *next_device_refresh).num_milliseconds() > 0 {
35-
*next_device_refresh = Utc::now().naive_utc() + Duration::seconds(5);
34+
if (Utc::now() - *next_device_refresh).num_milliseconds() > 0 {
35+
*next_device_refresh = Utc::now() + Duration::seconds(5);
3636
update_handle_types().await;
3737
update_all_devices(true).await;
3838
}
@@ -296,8 +296,8 @@ async fn refresh_device_poses<'a>() {
296296
if n == 0 {
297297
// Only for the HMD, the rest is not required
298298
let mut next_pose_broadcast = NEXT_POSE_BROADCAST.lock().await;
299-
if (Utc::now().naive_utc() - *next_pose_broadcast).num_milliseconds() > 0 {
300-
*next_pose_broadcast = Utc::now().naive_utc() + Duration::milliseconds(250);
299+
if (Utc::now() - *next_pose_broadcast).num_milliseconds() > 0 {
300+
*next_pose_broadcast = Utc::now() + Duration::milliseconds(250);
301301
drop(next_pose_broadcast);
302302
send_event(
303303
"OVR_POSE_UPDATE",

‎src-core/src/openvr/mod.rs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
openvr::models::{OpenVRAction, OpenVRActionSet},
1414
utils::send_event,
1515
};
16-
use chrono::{naive::NaiveDateTime, Utc};
16+
use chrono::{DateTime, Utc};
1717
use gesture_detector::GestureDetector;
1818
use log::{error, info};
1919
use models::OpenVRStatus;
@@ -55,7 +55,7 @@ pub async fn init() {
5555
pub async fn task() {
5656
// Task state
5757
let mut ovr_active = false;
58-
let mut ovr_next_init = NaiveDateTime::from_timestamp_millis(0).unwrap();
58+
let mut ovr_next_init = DateTime::from_timestamp_millis(0).unwrap();
5959

6060
// Main Loop
6161
'ovr_loop: loop {
@@ -64,11 +64,11 @@ pub async fn task() {
6464
// If we're not active, try to initialize OpenVR
6565
if OVR_CONTEXT.lock().await.is_none() {
6666
// Stop if we cannot yet (re)initialize OpenVR
67-
if (Utc::now().naive_utc() - ovr_next_init).num_milliseconds() <= 0 {
67+
if (Utc::now() - ovr_next_init).num_milliseconds() <= 0 {
6868
continue;
6969
}
7070
// If we need to reinitialize OpenVR after this, wait at least 3 seconds
71-
ovr_next_init = Utc::now().naive_utc() + chrono::Duration::seconds(3);
71+
ovr_next_init = Utc::now() + chrono::Duration::seconds(3);
7272
// Check if SteamVR is running, snd stop initializing if it's not.
7373
if !crate::utils::is_process_active("vrmonitor.exe", false).await {
7474
update_status(OpenVRStatus::Inactive).await;
@@ -270,7 +270,7 @@ pub async fn task() {
270270
}
271271
*OVR_CONTEXT.lock().await = None;
272272
// Schedule next initialization attempt
273-
ovr_next_init = Utc::now().naive_utc() + chrono::Duration::seconds(5);
273+
ovr_next_init = Utc::now() + chrono::Duration::seconds(5);
274274
continue 'ovr_loop;
275275
}
276276
// Handle other events

‎src-core/src/os/audio_devices/device.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::ptr::null_mut;
22
use std::sync::Arc;
33

4-
use chrono::{NaiveDateTime, Utc};
4+
use chrono::{DateTime, Utc};
55
use log::{error, info};
66
use serde::Serialize;
77
use tokio::runtime::Handle;
@@ -292,7 +292,7 @@ impl AudioDevice {
292292
drop(metering_enabled);
293293
tokio::spawn(async move {
294294
const ACTIVATION_TIMEOUT: i64 = 1000;
295-
let mut last_activation = NaiveDateTime::from_timestamp_millis(0).unwrap();
295+
let mut last_activation = DateTime::from_timestamp_millis(0).unwrap();
296296
let mut previously_active = false;
297297
loop {
298298
// Stop if metering has been disabled
@@ -320,10 +320,10 @@ impl AudioDevice {
320320
.await;
321321
// Determine if the mic is considered "active"
322322
let currently_active = if value >= threshold {
323-
last_activation = Utc::now().naive_utc();
323+
last_activation = Utc::now();
324324
true
325325
} else {
326-
let now = Utc::now().naive_utc();
326+
let now = Utc::now();
327327
let duration = now - last_activation;
328328
duration.num_milliseconds() < ACTIVATION_TIMEOUT
329329
};

‎src-core/src/osc/vrchat.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::time::Duration;
22

3-
use chrono::{NaiveDateTime, Utc};
3+
use chrono::{DateTime, Utc};
44
use tokio::sync::Mutex;
55

66
use crate::Models::overlay_sidecar::MicrophoneActivityMode;
@@ -10,7 +10,7 @@ const VOICE_ACTIVITY_TIMEOUT: u64 = 1000;
1010
lazy_static! {
1111
static ref VOICE_ACTIVE: Mutex<bool> = Mutex::new(false);
1212
static ref VOICE_LAST_VALUE: Mutex<f32> = Mutex::new(0.0);
13-
static ref VOICE_LAST_ACTIVE: Mutex<NaiveDateTime> = Mutex::new(Utc::now().naive_utc());
13+
static ref VOICE_LAST_ACTIVE: Mutex<DateTime::<Utc>> = Mutex::new(Utc::now());
1414
}
1515

1616
async fn process_voice_parameter(value: Option<f32>) {
@@ -26,15 +26,15 @@ async fn process_voice_parameter(value: Option<f32>) {
2626
on_voice_activity_changed(true).await;
2727
}
2828
*voice_active = true;
29-
*voice_last_active = Utc::now().naive_utc();
29+
*voice_last_active = Utc::now();
3030
*voice_last_value = value;
3131
} else if *voice_last_value != 0.0 {
3232
*voice_last_value = 0.0;
3333
tokio::spawn(async {
3434
tokio::time::sleep(Duration::from_millis(VOICE_ACTIVITY_TIMEOUT)).await;
3535
let mut voice_active = VOICE_ACTIVE.lock().await;
3636
let voice_last_active = VOICE_LAST_ACTIVE.lock().await;
37-
let timed_out = (Utc::now().naive_utc() - *voice_last_active).num_milliseconds()
37+
let timed_out = (Utc::now() - *voice_last_active).num_milliseconds()
3838
>= VOICE_ACTIVITY_TIMEOUT.try_into().unwrap();
3939
if *voice_active && timed_out {
4040
on_voice_activity_changed(false).await;

‎src-core/src/telemetry/mod.rs‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
use std::sync::atomic::AtomicBool;
1+
use std::{
2+
sync::atomic::{AtomicBool, Ordering},
3+
time::Duration,
4+
};
25

36
use log::info;
47
use serde_json::json;
58
use tauri_plugin_aptabase::EventTracker;
6-
use tokio::sync::Mutex;
9+
use tokio::{sync::Mutex, time::Instant};
710

811
use crate::BUILD_FLAVOUR;
912

@@ -24,4 +27,22 @@ pub async fn init_telemetry(handle: &tauri::AppHandle) {
2427
.unwrap()
2528
.to_uppercase();
2629
handle.track_event("app_started", Some(json!({ "flavour": flavour.clone() })));
30+
// Send heartbeats roughly every hour
31+
tokio::task::spawn(async {
32+
let mut start_time = Instant::now();
33+
let one_hour = Duration::from_secs(3600 - 30);
34+
loop {
35+
let elapsed = start_time.elapsed();
36+
if elapsed >= one_hour {
37+
start_time = Instant::now();
38+
if TELEMETRY_ENABLED.load(Ordering::Relaxed) {
39+
let handle = crate::globals::TAURI_APP_HANDLE.lock().await;
40+
if let Some(handle) = handle.as_ref() {
41+
handle.track_event("app_heartbeat", None);
42+
}
43+
}
44+
}
45+
tokio::time::sleep(Duration::from_secs(5)).await // Check every second (adjust as needed)
46+
}
47+
});
2748
}

‎src-core/tauri.conf.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
},
1010
"package": {
1111
"productName": "OyasumiVR",
12-
"version": "1.12.6"
12+
"version": "1.12.7"
1313
},
1414
"tauri": {
1515
"allowlist": {
@@ -188,7 +188,7 @@
188188
"center": true,
189189
"theme": "Dark",
190190
"transparent": true,
191-
"userAgent": "OyasumiVR/1.12.6 (https://github.com/Raphiiko/OyasumiVR)"
191+
"userAgent": "OyasumiVR/1.12.7 (https://github.com/Raphiiko/OyasumiVR)"
192192
},
193193
{
194194
"width": 700,
@@ -200,7 +200,7 @@
200200
"center": true,
201201
"theme": "Dark",
202202
"transparent": true,
203-
"userAgent": "OyasumiVR/1.12.6 (https://github.com/Raphiiko/OyasumiVR)"
203+
"userAgent": "OyasumiVR/1.12.7 (https://github.com/Raphiiko/OyasumiVR)"
204204
}
205205
]
206206
}

‎src-elevated-sidecar/Cargo.lock‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src-elevated-sidecar/Cargo.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[package]
22
name = "oyasumivr-elevated-sidecar"
3-
version = "1.12.6"
3+
version = "1.12.7"
44
authors = ["Raphiiko"]
55
license = "MIT"
66
edition = "2021"
7-
rust-version = "1.70.0"
7+
rust-version = "1.71.1"
88

99
[build-dependencies]
1010
tonic-build = "0.9"

‎src-overlay-ui/package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src-overlay-ui/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "oyasumivr-overlay-ui",
3-
"version": "1.12.6",
3+
"version": "1.12.7",
44
"private": true,
55
"scripts": {
66
"dev": "vite dev",

‎src-shared-rust/Cargo.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[package]
22
name = "oyasumivr-shared"
3-
version = "1.12.6"
3+
version = "1.12.7"
44
authors = ["Raphiiko"]
55
edition = "2021"
66
license = "MIT"
7-
rust-version = "1.70.0"
7+
rust-version = "1.71.1"
88

99
[dependencies.winapi]
1010
version = "0.3.9"

‎src-shared-ts/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "src-shared-ts",
33
"description": "Shared typescript code for Oyasumi modules",
44
"scripts": {},
5-
"version": "1.12.6",
5+
"version": "1.12.7",
66
"author": "Raphiiko",
77
"license": "MIT",
88
"type": "module",

‎src-ui/app/app.module.ts‎

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ import { BSBFanSpeedControlModalComponent } from './components/bsb-fan-speed-con
195195
import { DiscordService } from './services/discord.service';
196196
import { trackEvent } from '@aptabase/tauri';
197197
import { pTimeout } from './utils/promise-utils';
198+
import { MdnsSidecarService } from './services/mdns-sidecar.service';
198199

199200
[
200201
localeEN,
@@ -345,7 +346,8 @@ export class AppModule {
345346
private sleepService: SleepService,
346347
private oscService: OscService,
347348
private oscControlService: OscControlService,
348-
private sidecarService: ElevatedSidecarService,
349+
private elevatedSidecarService: ElevatedSidecarService,
350+
private mdnsSidecarService: MdnsSidecarService,
349351
private updateService: UpdateService,
350352
private telemetryService: TelemetryService,
351353
private appSettingsService: AppSettingsService,
@@ -425,7 +427,7 @@ export class AppModule {
425427
}
426428

427429
private async logInit<T>(action: string, promise: Promise<T>): Promise<T> {
428-
const TIMEOUT = 10000;
430+
const TIMEOUT = 30000;
429431
if (FLAVOUR === 'DEV') console.log(`[Init] Running ${action}`);
430432
try {
431433
const result = await pTimeout<T>(
@@ -436,7 +438,12 @@ export class AppModule {
436438
if (FLAVOUR === 'DEV') info(`[Init] '${action}' ran successfully`);
437439
return result;
438440
} catch (e) {
439-
trackEvent('app_init_error', { action, error: `${e}` });
441+
trackEvent('app_init_error', {
442+
action,
443+
error: `${e}`,
444+
timeout: TIMEOUT,
445+
metadata: `action=${action}, timeout=${TIMEOUT}, error=${e}`,
446+
});
440447
error(`[Init] Running '${action}' failed: ` + e);
441448
throw e;
442449
}
@@ -451,11 +458,9 @@ export class AppModule {
451458
this.developerDebugService.init()
452459
);
453460
// Clean cache
454-
await this.logInit('cache clean', CachedValue.cleanCache())
455-
.catch(() => {}); // Allow initialization to continue if failed
456-
// Preload assets
457-
await this.logInit('asset preload', this.preloadAssets())
458-
.catch(() => {}); // Allow initialization to continue if failed
461+
await this.logInit('cache clean', CachedValue.cleanCache()).catch(() => {}); // Allow initialization to continue if failed
462+
// Preload assets (Not blocking)
463+
this.logInit('asset preload', this.preloadAssets());
459464
// Initialize base utilities
460465
await Promise.all([
461466
this.logInit('AppSettingsService initialization', this.appSettingsService.init()),
@@ -500,11 +505,12 @@ export class AppModule {
500505
this.logInit('HotkeyHandlerService initialization', this.hotkeyHandlerService.init()),
501506
]);
502507
// Initialize GPU control services
503-
await this.logInit('SidecarService initialization', this.sidecarService.init()).then(
504-
async () => {
505-
await this.logInit('NVMLService initialization', this.nvmlService.init());
506-
}
507-
);
508+
await this.logInit(
509+
'SidecarService initialization',
510+
this.elevatedSidecarService.init()
511+
).then(async () => {
512+
await this.logInit('NVMLService initialization', this.nvmlService.init());
513+
});
508514
// Initialize Brightness Control
509515
await Promise.all([
510516
this.logInit(
@@ -523,6 +529,7 @@ export class AppModule {
523529
// Initialize IPC
524530
await this.logInit('IpcService initialization', this.ipcService.init());
525531
await this.logInit('OverlayService initialization', this.overlayService.init());
532+
await this.logInit('MDNSSidecarService initialization', this.mdnsSidecarService.init());
526533
await this.logInit(
527534
'OverlayAppStateSyncService initialization',
528535
this.overlayAppStateSyncService.init()
@@ -732,7 +739,7 @@ export class AppModule {
732739
}
733740

734741
private async preloadImageAsset(imageUrl: string) {
735-
const TIMEOUT = 8000;
742+
const TIMEOUT = 30000;
736743
const TIMEOUT_ERR = 'TIMEOUT_REACHED';
737744
try {
738745
await pTimeout(
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Injectable } from '@angular/core';
2+
import { BehaviorSubject, Observable } from 'rxjs';
3+
import { listen } from '@tauri-apps/api/event';
4+
import { info } from 'tauri-plugin-log-api';
5+
import { invoke } from '@tauri-apps/api';
6+
7+
@Injectable({
8+
providedIn: 'root',
9+
})
10+
export class MdnsSidecarService {
11+
private _sidecarStarted: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
12+
public sidecarStarted: Observable<boolean> = this._sidecarStarted.asObservable();
13+
14+
constructor() {}
15+
16+
public async init() {
17+
this._sidecarStarted.next(await this.checkIfStarted());
18+
await Promise.all([
19+
listen<boolean>('MDNS_SIDECAR_STARTED', () => {
20+
info('[MDNSSidecar] MDNS sidecar has started');
21+
this._sidecarStarted.next(true);
22+
}),
23+
listen<boolean>('MDNS_SIDECAR_STOPPED', () => {
24+
info('[MDNSSidecar] MDNS sidecar has stopped');
25+
this._sidecarStarted.next(false);
26+
}),
27+
]);
28+
}
29+
30+
async checkIfStarted(): Promise<boolean> {
31+
return await invoke('mdns_sidecar_started');
32+
}
33+
}

‎src-ui/app/services/overlay/overlay.service.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { VRChatService } from '../vrchat.service';
1919
providedIn: 'root',
2020
})
2121
export class OverlayService {
22-
public readonly overlaySidecarActive = this.ipcService.overlaySidecarClient.pipe(map(Boolean));
22+
public readonly sidecarStarted = this.ipcService.overlaySidecarClient.pipe(map(Boolean));
2323
private appSettings: AppSettings = cloneDeep(APP_SETTINGS_DEFAULT);
2424

2525
constructor(

‎src-ui/app/views/dashboard-view/views/settings-status-info-view/settings-status-info-view.component.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { invoke } from '@tauri-apps/api';
1414
import { hshrink } from '../../../../utils/animations';
1515
import { TStringTranslatePipe } from '../../../../pipes/tstring-translate.pipe';
1616
import { writeText } from '@tauri-apps/api/clipboard';
17+
import { MdnsSidecarService } from 'src-ui/app/services/mdns-sidecar.service';
1718

1819
@Component({
1920
selector: 'app-settings-status-info-view',
@@ -37,6 +38,7 @@ export class SettingsStatusInfoViewComponent {
3738
osc: OscService,
3839
elevatedSidecar: ElevatedSidecarService,
3940
overlaySidecar: OverlayService,
41+
mdnsSidecar: MdnsSidecarService,
4042
openvr: OpenVRService,
4143
ipc: IPCService,
4244
fontLoader: FontLoaderService,
@@ -58,7 +60,15 @@ export class SettingsStatusInfoViewComponent {
5860
},
5961
{
6062
key: 'Overlay Sidecar',
61-
value: overlaySidecar.overlaySidecarActive.pipe(
63+
value: overlaySidecar.sidecarStarted.pipe(
64+
map((s) => {
65+
return s ? 'Running' : 'Not running';
66+
})
67+
),
68+
},
69+
{
70+
key: 'MDNS Sidecar',
71+
value: mdnsSidecar.sidecarStarted.pipe(
6272
map((s) => {
6373
return s ? 'Running' : 'Not running';
6474
})

‎src-ui/styles.scss‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
html,
2020
body {
2121
margin: 0;
22-
height: 0;
2322
background-color: var(--color-surface-0);
2423
color: var(--color-text-0);
2524
font-size: 12px;
2625
min-height: 100vh;
2726
max-width: 100vw;
2827
transition: background-color 0.15s ease, color 0.15s ease;
28+
overflow: hidden;
29+
position: fixed;
30+
width: 100vw;
31+
height: 100vh;
2932
}
3033

3134
* {
-45.3 KB
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.