Merge overlays, move overlay to separate process (#44)

* refactor: merge two overlay offScreenBrowser into one

* Electron support for shared overlay

* Separate overlay into its own process

* fix: invalid overlay texture size

* Handle duplicate processes

* Remove logging

---------

Co-authored-by: pa <maplenagisa@gmail.com>
Co-authored-by: rs189 <35667100+rs189@users.noreply.github.com>
This commit is contained in:
Natsumi
2026-01-12 11:31:10 +13:00
committed by Natsumi
parent 9135adf6d1
commit d2fd205476
41 changed files with 1122 additions and 2108 deletions

View File

@@ -853,11 +853,11 @@ export const useFriendStore = defineStore('Friend', () => {
reconnectWebSocket();
}
function updateOnlineFriendCounter() {
function updateOnlineFriendCounter(forceUpdate = false) {
const onlineFriendCounts =
vipFriends.value.length + onlineFriends.value.length;
if (onlineFriendCounts !== onlineFriendCount.value) {
AppApi.ExecuteVrFeedFunction(
if (onlineFriendCounts !== onlineFriendCount.value || forceUpdate) {
AppApi.ExecuteVrOverlayFunction(
'updateOnlineFriendCount',
`${onlineFriendCounts}`
);

View File

@@ -146,11 +146,7 @@ export const useGameStore = defineStore('Game', () => {
}
// use in C#
async function updateIsGameRunning(
isGameRunningArg,
isSteamVRRunningArg,
isHmdAfkArg
) {
async function updateIsGameRunning(isGameRunningArg, isSteamVRRunningArg) {
const avatarStore = useAvatarStore();
if (advancedSettingsStore.gameLogDisabled) {
return;
@@ -186,11 +182,15 @@ export const useGameStore = defineStore('Game', () => {
isSteamVRRunning.value = isSteamVRRunningArg;
console.log('isSteamVRRunning:', isSteamVRRunningArg);
}
vrStore.updateOpenVR();
}
// use in C#
function updateIsHmdAfk(isHmdAfkArg) {
if (isHmdAfkArg !== isHmdAfk.value) {
isHmdAfk.value = isHmdAfkArg;
console.log('isHmdAfk:', isHmdAfkArg);
console.log('isHmdAfk', isHmdAfkArg);
}
vrStore.updateOpenVR();
}
async function checkVRChatDebugLogging() {
@@ -258,6 +258,7 @@ export const useGameStore = defineStore('Game', () => {
getVRChatCacheSize,
updateIsGameRunning,
getVRChatRegistryKey,
checkVRChatDebugLogging
checkVRChatDebugLogging,
updateIsHmdAfk
};
});

View File

@@ -233,7 +233,7 @@ export const useSharedFeedStore = defineStore('SharedFeed', () => {
}
}
}
AppApi.ExecuteVrFeedFunction(
AppApi.ExecuteVrOverlayFunction(
'wristFeedUpdate',
JSON.stringify(wristFeed)
);

View File

@@ -131,8 +131,7 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
state.nextGameRunningCheck = 1;
gameStore.updateIsGameRunning(
await AppApi.IsGameRunning(),
await AppApi.IsSteamVRRunning(),
false
await AppApi.IsSteamVRRunning()
);
vrStore.vrInit(); // TODO: make this event based
}

View File

@@ -45,8 +45,7 @@ export const useVrStore = defineStore('Vr', () => {
updateVrNowPlaying();
// run these methods again to send data to the overlay
sharedFeedStore.updateSharedFeed(true);
friendStore.onlineFriendCount = 0; // force an update
friendStore.updateOnlineFriendCounter();
friendStore.updateOnlineFriendCounter(true); // force an update
}
async function saveOpenVROption() {
@@ -59,7 +58,6 @@ export const useVrStore = defineStore('Vr', () => {
function updateVrNowPlaying() {
const json = JSON.stringify(gameLogStore.nowPlaying);
AppApi.ExecuteVrFeedFunction('nowPlayingUpdate', json);
AppApi.ExecuteVrOverlayFunction('nowPlayingUpdate', json);
}
@@ -91,7 +89,6 @@ export const useVrStore = defineStore('Vr', () => {
onlineFor
};
const json = JSON.stringify(lastLocation);
AppApi.ExecuteVrFeedFunction('lastLocationUpdate', json);
AppApi.ExecuteVrOverlayFunction('lastLocationUpdate', json);
}
@@ -142,7 +139,6 @@ export const useVrStore = defineStore('Vr', () => {
/** @type {string} */
const json = JSON.stringify(VRConfigVars);
AppApi.ExecuteVrFeedFunction('configUpdate', json);
AppApi.ExecuteVrOverlayFunction('configUpdate', json);
}
@@ -185,6 +181,9 @@ export const useVrStore = defineStore('Vr', () => {
newState.menuButton,
newState.overlayHand
);
if (!newState.active) {
gameStore.updateIsHmdAfk(false);
}
if (LINUX) {
window.electron.updateVr(

View File

@@ -65,8 +65,7 @@ declare global {
) => void;
onBrowserFocus: (Function: (event: any) => void) => void;
restartApp: () => Promise<void>;
getWristOverlayWindow: () => Promise<boolean>;
getHmdOverlayWindow: () => Promise<boolean>;
getOverlayWindow: () => Promise<boolean>;
updateVr: (
active: bool,
hmdOverlay: bool,
@@ -168,8 +167,6 @@ declare global {
menuButton: boolean,
overlayHand: number
): Promise<void>;
RefreshVR(): Promise<void>;
RestartVR(): Promise<void>;
SetZoom(zoomLevel: number): Promise<void>;
GetZoom(): Promise<number>;
DesktopNotification(
@@ -179,7 +176,6 @@ declare global {
): Promise<void>;
RestartApplication(isUpgrade: boolean): Promise<void>;
CheckForUpdateExe(): Promise<boolean>;
ExecuteVrFeedFunction(key: string, json: string): Promise<void>;
ExecuteVrOverlayFunction(key: string, json: string): Promise<void>;
FocusWindow(): Promise<void>;
ChangeTheme(value: number): Promise<void>;
@@ -367,7 +363,6 @@ declare global {
GetUptime(): Promise<number>;
CurrentCulture(): Promise<string>;
CustomVrScript(): Promise<string>;
GetExecuteVrFeedFunctionQueue(): Promise<Map<string, string>>;
GetExecuteVrOverlayFunctionQueue(): Promise<Map<string, string>>;
};

View File

@@ -1,9 +1,6 @@
<template>
<div
id="x-app"
class="x-app x-app-type"
:class="{ background: appType === 'wrist' && config && config.backgroundEnabled }">
<template v-if="appType === 'wrist' && !vrState.isWristDisabled">
<div id="x-app" class="x-app x-app-type">
<div class="wrist" :class="{ background: config && config.backgroundEnabled }">
<div class="x-container" style="flex: 1">
<div class="x-friend-list" ref="list" style="color: #aaa">
<template v-if="config && config.minimalFeed">
@@ -1283,9 +1280,9 @@
<span style="display: inline-block">{{ t('vr.status.online') }} {{ onlineFriendCount }}</span>
<span style="display: inline-block; margin-left: 5px">{{ customInfo }}</span>
</div>
</template>
</div>
<!-- HMD Overlay -->
<template v-else-if="appType === 'hmd' && !vrState.isHmdDisabled">
<div class="hmd">
<svg class="np-progress-circle">
<circle
class="np-progress-circle-stroke"
@@ -1406,7 +1403,7 @@
d="M102.9,75l11.3-11.3c10.3-10.3,11.5-26.1,3.8-37.8l17.4-17.4L126.9,0l-17.4,17.4C97.9,9.7,82,11,71.8,21.2L60.5,32.5C102,74,60.8,32.9,102.9,75z"></path>
</svg>
</div>
</template>
</div>
</div>
</template>
@@ -1437,7 +1434,6 @@
const { t, locale } = useI18n();
const vrState = reactive({
appType: new URLSearchParams(window.location.search).has('wrist') ? 'wrist' : 'hmd',
appLanguage: 'en',
currentCulture: 'en-gb',
currentTime: new Date().toJSON(),
@@ -1513,10 +1509,8 @@
if (LINUX) {
updateVrElectronLoop();
}
if (vrState.appType === 'wrist') {
refreshCustomScript();
updateStatsLoop();
}
refreshCustomScript();
updateStatsLoop();
setDatetimeFormat();
nextTick(() => {
@@ -1614,25 +1608,17 @@
function nowPlayingUpdate(json) {
vrState.nowPlaying = JSON.parse(json);
if (vrState.appType === 'hmd') {
const circle = /** @type {SVGCircleElement | null} */ (
document.querySelector('.np-progress-circle-stroke')
);
const circle = /** @type {SVGCircleElement} */ (document.querySelector('.np-progress-circle-stroke'));
if (!circle) {
return;
}
if (vrState.lastLocation.progressPie && vrState.nowPlaying.percentage !== 0) {
circle.style.opacity = (0.5).toString();
const circumference = circle.getTotalLength();
circle.style.strokeDashoffset = (
circumference -
(vrState.nowPlaying.percentage / 100) * circumference
).toString();
} else {
circle.style.opacity = '0';
}
if (vrState.lastLocation.progressPie && vrState.nowPlaying.percentage !== 0) {
circle.style.opacity = (0.5).toString();
const circumference = circle.getTotalLength();
circle.style.strokeDashoffset = (
circumference -
(vrState.nowPlaying.percentage / 100) * circumference
).toString();
} else {
circle.style.opacity = '0';
}
updateFeedLength();
}
@@ -1647,7 +1633,7 @@
}
function updateFeedLength() {
if (vrState.appType === 'hmd' || vrState.wristFeed.length === 0) {
if (vrState.wristFeed.length === 0) {
return;
}
let length = 16;
@@ -1787,36 +1773,19 @@
async function updateVrElectronLoop() {
try {
if (vrState.appType === 'wrist') {
const wristOverlayQueue = await AppApiVr.GetExecuteVrFeedFunctionQueue();
if (wristOverlayQueue) {
wristOverlayQueue.forEach((item) => {
// item[0] is the function name, item[1] is already an object
const fullFunctionName = item[0];
const jsonArg = item[1];
const overlayQueue = await AppApiVr.GetExecuteVrOverlayFunctionQueue();
if (overlayQueue) {
overlayQueue.forEach((item) => {
// item[0] is the function name, item[1] is already an object
const fullFunctionName = item[0];
const jsonArg = item[1];
if (typeof window.$vr === 'object' && typeof window.$vr[fullFunctionName] === 'function') {
window.$vr[fullFunctionName](jsonArg);
} else {
console.error(`$vr.${fullFunctionName} is not defined or is not a function`);
}
});
}
} else {
const hmdOverlayQueue = await AppApiVr.GetExecuteVrOverlayFunctionQueue();
if (hmdOverlayQueue) {
hmdOverlayQueue.forEach((item) => {
// item[0] is the function name, item[1] is already an object
const fullFunctionName = item[0];
const jsonArg = item[1];
if (typeof window.$vr === 'object' && typeof window.$vr[fullFunctionName] === 'function') {
window.$vr[fullFunctionName](jsonArg);
} else {
console.error(`$vr.${fullFunctionName} is not defined or is not a function`);
}
});
}
if (typeof window.$vr === 'object' && typeof window.$vr[fullFunctionName] === 'function') {
window.$vr[fullFunctionName](jsonArg);
} else {
console.error(`$vr.${fullFunctionName} is not defined or is not a function`);
}
});
}
} catch (err) {
console.error(err);
@@ -2126,7 +2095,6 @@
}
const {
appType,
config,
wristFeed,
devices,

View File

@@ -240,6 +240,30 @@ button {
.x-app-type {
color: #fff;
width: 1024px;
height: 1536px;
position: relative;
overflow: hidden;
}
.wrist {
position: absolute;
top: 0px;
left: 0;
width: 512px;
height: 512px;
z-index: 20;
display: flex;
flex-direction: column;
}
.hmd {
position: absolute;
top: 512px;
left: 0;
width: 1024px;
height: 1024px;
z-index: 10;
}
.background {