Linux: SteamVR overlay support (#1299)

* fix: open folder and select item on linux

* feat: linux wrist overlay

* feat: linux hmd overlay

* feat: replace unix sockets with shm on linux

* fix: reduce linux wrist overlay fps

* fix: hide electron offscreen windows

* fix: destroy electron offscreen windows when not in use

* fix: open folder and select item on linux

* feat: cpu, uptime and device monitoring on linux

* feat: native wayland gl context with x11 fallback on linux

* fix: use platform agnostic wording for common folders

* fix: crash dumps folder button on linux

* fix: enable missing VR notification options on linux

* fix: update cef, eslint config to include updated AppApiVr names

* merge: rebase linux VR changes to upstream

* Clean up

* Load custom file contents rather than path

Fixes loading custom file in debug mode

* fix: call SetVR on linux as well

* fix: AppApiVrElectron init, properly create and dispose of shm

* Handle avatar history error

* Lint

* Change overlay dispose logic

* macOS DOTNET_ROOT

* Remove moving dotnet bin

* Fix

* fix: init overlay on SteamVR restart

* Fix fetching empty instance, fix user dialog not fetching

* Trim direct access inputs

* Make icon higher res, because mac build would fail 😂

* macOS fixes

* will it build? that's the question

* fix: ensure offscreen windows are ready before vrinit

* will it build? that's the question

* will it build? that's the question

* meow

* one, more, time

* Fix crash and overlay ellipsis

* a

---------

Co-authored-by: Natsumi <cmcooper123@hotmail.com>
This commit is contained in:
rs189
2025-07-19 09:07:43 +09:00
committed by GitHub
parent 53723d37b0
commit a2dc6ba9a4
53 changed files with 10555 additions and 7865 deletions
+22 -18
View File
@@ -385,31 +385,35 @@ export const useAvatarStore = defineStore('Avatar', () => {
}
/**
* aka: `$app.methods.addAvatarToHistory`
* @param {string} avatarId
*/
function addAvatarToHistory(avatarId) {
avatarRequest.getAvatar({ avatarId }).then((args) => {
const ref = applyAvatar(args.json);
avatarRequest
.getAvatar({ avatarId })
.then((args) => {
const ref = applyAvatar(args.json);
database.addAvatarToCache(ref);
database.addAvatarToHistory(ref.id);
database.addAvatarToCache(ref);
database.addAvatarToHistory(ref.id);
if (ref.authorId === userStore.currentUser.id) {
return;
}
const historyArray = state.avatarHistoryArray;
for (let i = 0; i < historyArray.length; ++i) {
if (historyArray[i].id === ref.id) {
historyArray.splice(i, 1);
if (ref.authorId === userStore.currentUser.id) {
return;
}
}
state.avatarHistoryArray.unshift(ref);
state.avatarHistory.delete(ref.id);
state.avatarHistory.add(ref.id);
});
const historyArray = state.avatarHistoryArray;
for (let i = 0; i < historyArray.length; ++i) {
if (historyArray[i].id === ref.id) {
historyArray.splice(i, 1);
}
}
state.avatarHistoryArray.unshift(ref);
state.avatarHistory.delete(ref.id);
state.avatarHistory.add(ref.id);
})
.catch((err) => {
console.error('Failed to add avatar to history:', err);
});
}
function clearAvatarHistory() {
+3 -3
View File
@@ -531,10 +531,10 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
state.folderSelectorDialogVisible = true;
let newFolder = '';
if (LINUX) {
newFolder = await window.electron.openDirectoryDialog();
if (WINDOWS) {
newFolder = await AppApi.OpenFolderSelectorDialog(oldPath);
} else {
newFolder = await AppApi.OpenFolderSelectorDialog(oldPath);
newFolder = await window.electron.openDirectoryDialog();
}
state.folderSelectorDialogVisible = false;
+6 -6
View File
@@ -362,10 +362,10 @@ export const useNotificationsSettingsStore = defineStore(
function getTTSVoiceName() {
let voices;
if (LINUX) {
voices = state.TTSvoices;
} else {
if (WINDOWS) {
voices = speechSynthesis.getVoices();
} else {
voices = state.TTSvoices;
}
if (voices.length === 0) {
return '';
@@ -379,10 +379,10 @@ export const useNotificationsSettingsStore = defineStore(
async function changeTTSVoice(index) {
setNotificationTTSVoice(index);
let voices;
if (LINUX) {
voices = state.TTSvoices;
} else {
if (WINDOWS) {
voices = speechSynthesis.getVoices();
} else {
voices = state.TTSvoices;
}
if (voices.length === 0) {
return;
+4 -4
View File
@@ -133,16 +133,16 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
(vrcxStore.isRunningUnderWine || LINUX) &&
--state.nextGameRunningCheck <= 0
) {
if (LINUX) {
if (WINDOWS) {
state.nextGameRunningCheck = 3;
AppApi.CheckGameRunning();
} else {
state.nextGameRunningCheck = 1;
gameStore.updateIsGameRunning(
await AppApi.IsGameRunning(),
await AppApi.IsSteamVRRunning(),
false
);
} else {
state.nextGameRunningCheck = 3;
AppApi.CheckGameRunning();
}
}
if (--state.nextDatabaseOptimize <= 0) {
+1 -6
View File
@@ -851,12 +851,7 @@ export const useUserStore = defineStore('User', () => {
}
applyUserDialogLocation(true);
if (
args.cache &&
args.ref.$lastFetch < Date.now() - 10000 // 10 seconds
) {
userRequest.getUser(args.params);
}
userRequest.getUser(args.params);
let inCurrentWorld = false;
if (
locationStore.lastLocation.playerList.has(D.ref.id)
+47 -10
View File
@@ -27,7 +27,9 @@ export const useVrStore = defineStore('Vr', () => {
const userStore = useUserStore();
const sharedFeedStore = useSharedFeedStore();
const state = reactive({});
const state = reactive({
overlayActive: false
});
watch(
() => watchState.isFriendsLoaded,
@@ -48,6 +50,8 @@ export const useVrStore = defineStore('Vr', () => {
sharedFeedStore.updateSharedFeed(true);
friendStore.onlineFriendCount = 0; // force an update
friendStore.updateOnlineFriendCoutner();
state.overlayActive = true;
}
async function saveOpenVROption() {
@@ -74,7 +78,7 @@ export const useVrStore = defineStore('Vr', () => {
}
}
}
let onlineFor = '';
let onlineFor = null;
if (!wristOverlaySettingsStore.hideUptimeFromFeed) {
onlineFor = userStore.currentUser.$online_for;
}
@@ -125,6 +129,13 @@ export const useVrStore = defineStore('Vr', () => {
}
function updateOpenVR() {
let newState = {
active: false,
hmdOverlay: false,
wristOverlay: false,
menuButton: false,
overlayHand: 0
};
if (
notificationsSettingsStore.openVR &&
gameStore.isSteamVRRunning &&
@@ -140,16 +151,42 @@ export const useVrStore = defineStore('Vr', () => {
) {
hmdOverlay = true;
}
// active, hmdOverlay, wristOverlay, menuButton, overlayHand
AppApi.SetVR(
true,
newState = {
active: true,
hmdOverlay,
wristOverlaySettingsStore.overlayWrist,
wristOverlaySettingsStore.overlaybutton,
wristOverlaySettingsStore.overlayHand
wristOverlay: wristOverlaySettingsStore.overlayWrist,
menuButton: wristOverlaySettingsStore.overlaybutton,
overlayHand: wristOverlaySettingsStore.overlayHand
};
}
AppApi.SetVR(
newState.active,
newState.hmdOverlay,
newState.wristOverlay,
newState.menuButton,
newState.overlayHand
);
if (LINUX) {
window.electron.updateVr(
newState.active,
newState.hmdOverlay,
newState.wristOverlay,
newState.menuButton,
newState.overlayHand
);
} else {
AppApi.SetVR(false, false, false, false, 0);
if (state.overlayActive !== newState.active) {
if (
window.electron.getWristOverlayWindow() ||
window.electron.getHmdOverlayWindow()
) {
vrInit();
state.overlayActive = newState.active;
}
setTimeout(() => vrInit(), 1000); // give the overlay time to load
}
}
}
+3 -3
View File
@@ -652,11 +652,11 @@ export const useVrcxStore = defineStore('Vrcx', () => {
async function backupVrcRegistry(name) {
let regJson;
if (LINUX) {
if (WINDOWS) {
regJson = await AppApi.GetVRChatRegistry();
} else {
regJson = await AppApi.GetVRChatRegistryJson();
regJson = JSON.parse(regJson);
} else {
regJson = await AppApi.GetVRChatRegistry();
}
const newBackup = {
name,