Skip to content

Commit 6e1295d

Browse files
sofoxe1Raphiiko
andauthored
Add option to log sleep mode events to VRCX's game log
* log sleep mode change to vrcx (1) * log sleep mode change to vrcx (2) * make requested changes * Various improvements to VRCX logging * Add contribution --------- Co-authored-by: Raphii <iam@raphii.co>
1 parent 5cc6b8f commit 6e1295d

25 files changed

+527
-234
lines changed

src-core/Cargo.lock

Lines changed: 10 additions & 0 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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ md5 = "0.8.0"
4040
mime = "0.3.17"
4141
mime_guess = "2.0.5"
4242
nalgebra = "0.33.2"
43+
named_pipe = "0.4.1"
4344
oyasumivr-shared = { path = "../src-shared-rust", version = '*' }
4445
oyasumivr_macros = { path = "./macros" }
4546
prost = "0.13.5"

src-core/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod system_tray;
2020
mod telemetry;
2121
mod utils;
2222
mod vrc_log_parser;
23+
mod vrcx;
2324

2425
use std::{mem, sync::atomic::Ordering};
2526

@@ -420,5 +421,6 @@ fn configure_command_handlers() -> impl Fn(tauri::ipc::Invoke) -> bool {
420421
grpc::commands::get_core_grpc_port,
421422
grpc::commands::get_core_grpc_web_port,
422423
telemetry::commands::set_telemetry_enabled,
424+
vrcx::commands::vrcx_log,
423425
]
424426
}

src-core/src/vrcx/commands.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use log::debug;
2+
3+
use crate::vrcx::*;
4+
5+
#[tauri::command]
6+
#[oyasumivr_macros::command_profiling]
7+
pub async fn vrcx_log(msg: String) -> bool {
8+
let sender = &mut VRCX_NORITICATION_SENDER.lock().unwrap();
9+
if sender.sender.is_none() && sender.connect().is_err() {
10+
debug!("[VRCX] failed to connect to VRCX");
11+
return false;
12+
}
13+
if let Err(err) = sender.send_msg(msg.clone()) {
14+
match err {
15+
VrcxNotificationSenderError::NotConnected => {
16+
if sender.connect().is_err() {
17+
debug!("[VRCX] failed to connect to VRCX");
18+
return false;
19+
}
20+
if sender.send_msg(msg).is_err() {
21+
debug!("[VRCX] failed to send message to VRCX");
22+
return false;
23+
}
24+
}
25+
VrcxNotificationSenderError::SendFailed(_) => {
26+
debug!("[VRCX] failed to send message to VRCX");
27+
return false;
28+
}
29+
VrcxNotificationSenderError::UnableToConnect(_) => unreachable!(),
30+
};
31+
}
32+
return true;
33+
}

src-core/src/vrcx/mod.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#![allow(dead_code)]
2+
pub mod commands;
3+
pub mod models;
4+
use models::*;
5+
use std::{
6+
env,
7+
io::Write,
8+
sync::{LazyLock, Mutex},
9+
time::Duration,
10+
};
11+
12+
use log::warn;
13+
use named_pipe::PipeClient;
14+
pub static VRCX_NORITICATION_SENDER: LazyLock<Mutex<NotificationSender>> =
15+
LazyLock::new(Mutex::default);
16+
#[derive(Default)]
17+
pub struct NotificationSender {
18+
sender: Option<PipeClient>,
19+
}
20+
21+
impl NotificationSender {
22+
23+
pub fn connect(&mut self) -> Result<(), VrcxNotificationSenderError> {
24+
let mut sender = PipeClient::connect_ms(
25+
get_pipe_path(),
26+
Duration::from_millis(10).as_millis() as u32,
27+
)
28+
.map_err(VrcxNotificationSenderError::UnableToConnect)?;
29+
sender.set_write_timeout(Some(Duration::from_millis(100)));
30+
sender.set_read_timeout(Some(Duration::from_millis(100)));
31+
self.sender = Some(sender);
32+
Ok(())
33+
}
34+
35+
pub fn send_msg(&mut self, msg: String) -> Result<(), VrcxNotificationSenderError> {
36+
if let Some(sender) = &mut self.sender {
37+
let msg = VrcxMsg {
38+
type_: EventType::VrcxMessage,
39+
msg_type: Some(MessageType::External),
40+
data: Some(msg),
41+
notify: false,
42+
user_id: None,
43+
display_name: Some("OyasumiVR".to_string()),
44+
};
45+
if let Err(err) =
46+
sender.write(format!("{}\0", serde_json::to_string(&msg).unwrap()).as_bytes())
47+
{
48+
match err.kind() {
49+
std::io::ErrorKind::BrokenPipe => {
50+
self.sender = None;
51+
return Err(VrcxNotificationSenderError::NotConnected);
52+
}
53+
_ => return Err(VrcxNotificationSenderError::SendFailed(err)),
54+
};
55+
}
56+
Ok(())
57+
} else {
58+
Err(VrcxNotificationSenderError::NotConnected)
59+
}
60+
}
61+
62+
}
63+
pub fn init() {
64+
//try to connect
65+
VRCX_NORITICATION_SENDER.lock().unwrap().connect().ok();
66+
}
67+
68+
fn get_pipe_path() -> String {
69+
let username_env_name = {
70+
if cfg!(target_os = "windows") {
71+
"username".to_string()
72+
} else {
73+
"USER".to_string()
74+
}
75+
};
76+
let hash = env::var(&username_env_name)
77+
.unwrap_or_else(|err| {
78+
warn!(
79+
"[Core] failed getting '{}' enviroment variable: {:?}",
80+
username_env_name, err
81+
);
82+
"".to_string()
83+
})
84+
.chars()
85+
.map(|x| x as u32)
86+
.sum::<u32>();
87+
format!("\\\\.\\pipe\\vrcx-ipc-{}", hash)
88+
}

src-core/src/vrcx/models.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use serde::Serialize;
2+
pub enum VrcxNotificationSenderError {
3+
UnableToConnect(std::io::Error),
4+
SendFailed(std::io::Error),
5+
NotConnected,
6+
}
7+
#[derive(Serialize, Debug)]
8+
pub enum EventType {
9+
OnEvent,
10+
OnOperationResponse,
11+
OnOperationRequest,
12+
VRCEvent,
13+
Event7List,
14+
VrcxMessage,
15+
Ping,
16+
MsgPing,
17+
LaunchCommand,
18+
VRCXLaunch,
19+
}
20+
#[derive(Serialize, Debug)]
21+
pub enum MessageType {
22+
CustomTag,
23+
ClearCustomTag,
24+
Noty,
25+
External,
26+
}
27+
#[derive(Serialize, Debug)]
28+
pub struct VrcxMsg {
29+
#[serde(rename = "type")]
30+
pub type_: EventType,
31+
#[serde(rename = "MsgType")]
32+
pub msg_type: Option<MessageType>,
33+
#[serde(rename = "Data")]
34+
pub data: Option<String>,
35+
#[serde(rename = "UserId")]
36+
pub user_id: Option<String>,
37+
#[serde(rename = "DisplayName")]
38+
pub display_name: Option<String>,
39+
pub notify: bool,
40+
}

src-ui/app/app.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ import { LighthouseForceStatePopoverComponent } from './components/lighthouse-fo
260260
import { OyasumiVRSteamVRDevicePowerAutomationsService } from './services/power-automations/oyasumivr-steamvr-device-power-automations.service';
261261
import { SleepDevicePowerAutomationsService } from './services/power-automations/sleep-device-power-automations.service';
262262
import { TurnOffDevicesWhenChargingAutomationService } from './services/power-automations/turn-off-devices-when-charging-automation.service';
263+
import { VRCXService } from './services/vrcx.service';
263264

264265
[
265266
localeEN,
@@ -489,6 +490,7 @@ export class AppModule {
489490
private hotkeyService: HotkeyService,
490491
private hotkeyHandlerService: HotkeyHandlerService,
491492
private discordService: DiscordService,
493+
private vrcxService: VRCXService,
492494
private mqttService: MqttService,
493495
private mqttDiscoveryService: MqttDiscoveryService,
494496
private mqttIntegrationService: MqttIntegrationService,
@@ -646,6 +648,8 @@ export class AppModule {
646648
await this.logInit('Initializing Steam', this.steamService.init()),
647649
// Initialize Discord support
648650
await this.logInit('Initializing Discord', this.discordService.init()),
651+
// Initialize VRCX support
652+
await this.logInit('Initializing VRCX', this.vrcxService.init()),
649653
// Initialize IPC
650654
await this.logInit('Initializing IPC', this.ipcService.init()).then(async () => {
651655
await this.logInit('Initializing overlay services', this.overlayService.init());

src-ui/app/models/settings.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,12 @@ export interface AppSettings {
5959
bigscreenBeyondMaxBrightness: number; // User limit
6060
bigscreenBeyondUnsafeBrightness: boolean; // Allow brightness above 150%
6161
bigscreenBeyondBrightnessFanSafety: boolean; // Force fan to 100% if brightness is above 100%
62+
// VRCX
63+
vrcxLogsEnabled: VRCXEventLogType[];
6264
}
6365

66+
export type VRCXEventLogType = 'SleepMode';
67+
6468
export type DiscordActivityMode = 'ENABLED' | 'ONLY_ASLEEP' | 'DISABLED';
6569

6670
export type QuitWithSteamVRMode = 'DISABLED' | 'IMMEDIATELY' | 'AFTERDELAY';
@@ -143,6 +147,8 @@ export const APP_SETTINGS_DEFAULT: AppSettings = {
143147
bigscreenBeyondMaxBrightness: 150,
144148
bigscreenBeyondUnsafeBrightness: false,
145149
bigscreenBeyondBrightnessFanSafety: true,
150+
// VRCX
151+
vrcxLogsEnabled: ['SleepMode'],
146152
};
147153

148154
export type ExecutableReferenceStatus =

src-ui/app/services/vrcx.service.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Injectable } from '@angular/core';
2+
import { SleepService } from './sleep.service';
3+
import { distinctUntilChanged, skip } from 'rxjs';
4+
import { invoke } from '@tauri-apps/api/core';
5+
import { TranslateService } from '@ngx-translate/core';
6+
import { SleepPreparationService } from './sleep-preparation.service';
7+
import { AppSettingsService } from './app-settings.service';
8+
9+
@Injectable({
10+
providedIn: 'root',
11+
})
12+
export class VRCXService {
13+
constructor(
14+
private sleep: SleepService,
15+
private sleepPreparation: SleepPreparationService,
16+
private translate: TranslateService,
17+
private appSettingsService: AppSettingsService
18+
) {}
19+
20+
async init() {
21+
// Log sleep mode to VRCX
22+
this.sleep.mode.pipe(skip(1), distinctUntilChanged()).subscribe(async (sleepMode) => {
23+
if (this.appSettingsService.settingsSync.vrcxLogsEnabled.includes('SleepMode')) {
24+
await invoke<boolean>('vrcx_log', {
25+
msg: this.translate.instant(
26+
`settings.integrations.vrcx.logs.${sleepMode ? 'onSleepEnable' : 'onSleepDisable'}`
27+
),
28+
});
29+
}
30+
});
31+
// Log sleep preparation to VRCX
32+
this.sleepPreparation.onSleepPreparation.subscribe(async () => {
33+
if (this.appSettingsService.settingsSync.vrcxLogsEnabled.includes('SleepMode')) {
34+
await invoke<boolean>('vrcx_log', {
35+
msg: this.translate.instant('settings.integrations.vrcx.logs.onSleepPreparation'),
36+
});
37+
}
38+
});
39+
}
40+
}

src-ui/app/views/dashboard-view/views/about-view/about-view.component.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@
6565
<span translate>about.contributionType.programming</span></span
6666
>
6767
</div>
68+
<div class="contributor-list-entry">
69+
<span
70+
><a href="https://github.com/sofoxe1" target="_blank">sofoxe1</a> |
71+
<span translate>about.contributionType.programming</span></span
72+
>
73+
</div>
6874
<div class="contributor-list-entry">
6975
<span>spaecd | <span translate>about.contributionType.soundFx</span></span>
7076
</div>

0 commit comments

Comments
 (0)