Skip to content
Merged

Steam #1763

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions gui/electron/main/cli.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { program } from "commander";
import { Option, program } from "commander";

program
.option('-p --path <path>', 'set launch path')
.option('-p, --path <path>', 'set launch path')
.option('-s, --steam', 'steam mode')
.option('-i, --install', 'run the driver installer')
.option(
'--skip-server-if-running',
'gui will not launch the server if it is already running'
)
.allowUnknownOption();

if (process.platform === "linux") {
const noUdevOption = new Option('--no-udev', 'disable udev warning');
noUdevOption.negate = false;
program.addOption(noUdevOption)
}

program.parse(process.argv);
export const options = program.opts();
14 changes: 13 additions & 1 deletion gui/electron/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { getPlatform, handleIpc, isPortAvailable } from './utils';
import {
findServerJar,
findSystemJRE,
getExeFolder,
getGuiDataFolder,
getLogsFolder,
getServerDataFolder,
Expand Down Expand Up @@ -182,6 +183,8 @@ handleIpc(IPC_CHANNELS.GET_FOLDER, (e, folder) => {
return getGuiDataFolder();
case 'logs':
return getLogsFolder();
case 'exe':
return getExeFolder();
}
});

Expand Down Expand Up @@ -394,7 +397,16 @@ const spawnServer = async () => {
logger.info({ javaBin, serverJar }, 'Found Java and server jar');
const platform = getPlatform();
const serverWorkdir = getServerDataFolder()
const serverProcess = spawn(javaBin, ['-Xmx128M', '-jar', serverJar, 'run'], {

const serverArgs = ['-Xmx128M', '-jar', serverJar]
if (options.steam) serverArgs.push(`--steam`)
if (options.install) serverArgs.push(`--install`)
if (options.noUdev) serverArgs.push(`--no-udev`)

serverArgs.push('run')


const serverProcess = spawn(javaBin, serverArgs, {
cwd: serverWorkdir,
shell: false,
env:
Expand Down
16 changes: 14 additions & 2 deletions gui/electron/main/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,21 @@ export const getLogsFolder = () => {
return join(getGuiDataFolder(), 'logs');
};

export const getExeFolder = () => {
return path.dirname(app.getPath('exe'));
};

export const getWindowStateFile = () =>
join(getServerDataFolder(), '.window-state.json');

const localJavaBin = (sharedDir: string) => {
const jre = join(sharedDir, 'jre/bin', javaBin);
return jre;
const platform = getPlatform();
switch (platform) {
case 'macos':
return join(sharedDir, '../../../../jre/Contents/Home/bin', javaBin);
default:
return join(sharedDir, 'jre/bin', javaBin);
}
};

const javaHomeBin = () => {
Expand Down Expand Up @@ -112,6 +121,9 @@ export const findServerJar = () => {
// For flatpack container
path.resolve('/app/share/slimevr/'),
path.resolve('/usr/share/slimevr/'),

// For macos on steam
path.resolve(`${app.getPath('exe')}/../../../../`),
];
return paths
.filter((p) => !!p)
Expand Down
3 changes: 2 additions & 1 deletion gui/electron/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
openLogsFolder: async () => ipcRenderer.invoke(IPC_CHANNELS.OPEN_FILE, await ipcRenderer.invoke(IPC_CHANNELS.GET_FOLDER, 'logs')),
openFile: (path) => ipcRenderer.invoke(IPC_CHANNELS.OPEN_FILE, path),
ghGet: (req) => ipcRenderer.invoke(IPC_CHANNELS.GH_FETCH, req),
setPresence: (options) => ipcRenderer.invoke(IPC_CHANNELS.DISCORD_PRESENCE, options)
setPresence: (options) => ipcRenderer.invoke(IPC_CHANNELS.DISCORD_PRESENCE, options),
getInstallDir: () => ipcRenderer.invoke(IPC_CHANNELS.GET_FOLDER, 'exe')
} satisfies IElectronAPI);
1 change: 1 addition & 0 deletions gui/electron/preload/interface.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface IElectronAPI {
openFile: (path: string) => void;
ghGet: <T extends GHGet>(options: T) => Promise<GHReturn[T['type']]>;
setPresence: (options: DiscordPresence) => void;
getInstallDir: () => Promise<string>;
}

declare global {
Expand Down
2 changes: 1 addition & 1 deletion gui/electron/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export interface IpcInvokeMap {
value?: unknown;
}) => Promise<unknown>;
[IPC_CHANNELS.OPEN_FILE]: (path: string) => void;
[IPC_CHANNELS.GET_FOLDER]: (folder: 'config' | 'logs') => string;
[IPC_CHANNELS.GET_FOLDER]: (folder: 'config' | 'logs' | 'exe') => string;
[IPC_CHANNELS.GH_FETCH]: <T extends GHGet>(
options: T
) => Promise<GHReturn[T['type']]>;
Expand Down
5 changes: 5 additions & 0 deletions gui/public/i18n/en/translation.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,11 @@ onboarding-reset_tutorial-2 = Tap the highlighted tracker { $taps } times to tri

You need to be in a pose like you are skiing as shown in the Automatic Mounting wizard, and you have a 3 second delay (configurable) before it gets triggered.

## Install info
install-info_udev-rules_modal_title = Hardware udev access rules not found
install-info_udev-rules_warning = Access rules via udev are required for serial console access & dongle connection. Paste the following command into your terminal to add the udev rules.
install-info_udev-rules_modal_button = Close
install-info_udev-rules_modal-dont-show-again_checkbox = Don't show again
## Setup start
onboarding-home = Welcome to SlimeVR
onboarding-home-start = Let's get set up!
Expand Down
7 changes: 7 additions & 0 deletions gui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { AutomaticProportionsPage } from './components/onboarding/pages/body-pro
import { ManualProportionsPage } from './components/onboarding/pages/body-proportions/ManualProportions';
import { ConnectTrackersPage } from './components/onboarding/pages/ConnectTracker';
import { HomePage } from './components/onboarding/pages/Home';
import { ErrorCollectingConsentPage } from './components/onboarding/pages/ErrorCollectingConsent';
import { AutomaticMountingPage } from './components/onboarding/pages/mounting/AutomaticMounting';
import { ManualMountingPage } from './components/onboarding/pages/mounting/ManualMounting';
import { TrackersAssignPage } from './components/onboarding/pages/trackers-assign/TrackerAssignment';
Expand Down Expand Up @@ -58,6 +59,7 @@ import { QuizMocapPosQuestion } from './components/onboarding/pages/quiz/MocapPr
import { ElectronContextC, provideElectron } from './hooks/electron';
import { AppLocalizationProvider } from './i18n/config';
import { openUrl } from './hooks/crossplatform';
import { UdevRulesModal } from './components/onboarding/UdevRulesModal';

export const GH_REPO = 'SlimeVR/SlimeVR-Server';
export const VersionContext = createContext('');
Expand All @@ -75,6 +77,7 @@ function Layout() {
<SerialDetectionModal />
<VersionUpdateModal />
<UnknownDeviceModal />
<UdevRulesModal />
<SentryRoutes>
<Route element={<AppLayout />}>
<Route
Expand Down Expand Up @@ -152,6 +155,10 @@ function Layout() {
}
>
<Route path="home" element={<HomePage />} />
<Route
path="error-collecting-consent"
element={<ErrorCollectingConsentPage />}
/>
<Route path="wifi-creds" element={<WifiCredsPage />} />
<Route path="quiz/slime-set" element={<QuizSlimeSetQuestion />} />
<Route path="quiz/usage" element={<QuizUsageQuestion />} />
Expand Down
11 changes: 8 additions & 3 deletions gui/src/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useLayoutEffect } from 'react';
import { useConfig } from './hooks/config';
import { Outlet, useNavigate } from 'react-router-dom';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';

export function AppLayout() {
const { config } = useConfig();
const { pathname } = useLocation();
const navigate = useNavigate();

useLayoutEffect(() => {
Expand All @@ -28,10 +29,14 @@ export function AppLayout() {
}, [config]);

useLayoutEffect(() => {
if (config && !config.doneOnboarding) {
if (
config &&
!config.doneOnboarding &&
!pathname.startsWith('/onboarding/')
) {
navigate('/onboarding/home');
}
}, [config?.doneOnboarding]);
}, [config]);

return (
<>
Expand Down
60 changes: 0 additions & 60 deletions gui/src/components/ErrorConsentModal.tsx

This file was deleted.

7 changes: 1 addition & 6 deletions gui/src/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { GearIcon } from './commons/icon/GearIcon';
import { TrackersStillOnModal } from './TrackersStillOnModal';
import { useConfig } from '@/hooks/config';
import { TrayOrExitModal } from './TrayOrExitModal';
import { ErrorConsentModal } from './ErrorConsentModal';
import { useAtomValue } from 'jotai';
import { connectedIMUTrackersAtom } from '@/store/app-store';
import { useElectron } from '@/hooks/electron';
Expand Down Expand Up @@ -72,6 +71,7 @@ export function TopBar({
await saveConfig();
electron.api.close();
};

const tryCloseApp = async (dontTray = false) => {
if (!electron.isElectron) throw 'no electron';

Expand Down Expand Up @@ -286,11 +286,6 @@ export function TopBar({
setConnectedTrackerWarning(false);
}}
/>
<ErrorConsentModal
isOpen={config?.errorTracking === null}
accept={() => setConfig({ errorTracking: true })}
cancel={() => setConfig({ errorTracking: false })}
/>
</>
);
}
113 changes: 113 additions & 0 deletions gui/src/components/onboarding/UdevRulesModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useState, useEffect } from 'react';
import { Button } from '@/components/commons/Button';
import { BaseModal } from '@/components/commons/BaseModal';
import { CheckboxInternal } from '@/components/commons/Checkbox';
import { Typography } from '@/components/commons/Typography';
import { useElectron } from '@/hooks/electron';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { RpcMessage, InstalledInfoResponseT } from 'solarxr-protocol';
import { useConfig } from '@/hooks/config';
import { useLocalization } from '@fluent/react';

export function UdevRulesModal() {
const { config, setConfig } = useConfig();
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
const electron = useElectron();
const [udevContent, setUdevContent] = useState('');
const [isUdevInstalledResponse, setIsUdevInstalledResponse] = useState(true);
const [showUdevWarning, setShowUdevWarning] = useState(false);
const [dontShowThisSession, setDontShowThisSession] = useState(false);
Comment thread
HannahPadd marked this conversation as resolved.
const [dontShowAgain, setDontShowAgain] = useState(false);
const { l10n } = useLocalization();

const handleUdevContent = async () => {
if (electron.isElectron) {
const dir = await electron.api.getInstallDir();
const rulesPath = `${dir}/69-slimevr-devices.rules`;
setUdevContent(
`cat ${rulesPath} | sudo sh -c 'tee /etc/udev/rules.d/69-slimevr-devices.rules >/dev/null && udevadm control --reload-rules && udevadm trigger'`
);
}
};

useEffect(() => {
handleUdevContent();
}, []);

useEffect(() => {
if (!config) throw 'Invalid state!';
if (electron.isElectron) {
const isLinux = electron.data().os.type === 'linux';
const udevMissing = !isUdevInstalledResponse;
const notHiddenGlobally = !config.dontShowUdevModal;
const notHiddenThisSession = !dontShowThisSession;
const shouldShow =
isLinux && udevMissing && notHiddenGlobally && notHiddenThisSession;
setShowUdevWarning(shouldShow);
}
}, [config, isUdevInstalledResponse, dontShowThisSession]);

useEffect(() => {
sendRPCPacket(
RpcMessage.InstalledInfoRequest,
new InstalledInfoResponseT()
);
}, []);

useRPCPacket(
RpcMessage.InstalledInfoResponse,
({ isUdevInstalled }: InstalledInfoResponseT) => {
setIsUdevInstalledResponse(isUdevInstalled);
}
);

const handleModalClose = () => {
if (!config) throw 'Invalid State!';
setConfig({ dontShowUdevModal: dontShowAgain });
setDontShowThisSession(true);
};

const copyToClipboard = () => {
navigator.clipboard.writeText(udevContent);
};

return (
<BaseModal isOpen={showUdevWarning} appendClasses={'w-full max-w-2xl'}>
<div className="flex w-full h-full flex-col gap-4">
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-2">
<Typography
variant="main-title"
id="install-info_udev-rules_modal_title"
/>
<Typography id="install-info_udev-rules_warning" />
</div>
<div className="relative w-full max-w-2xl">
<div className="absolute right-2 top-2">
<Button variant="secondary" onClick={copyToClipboard}>
Copy
</Button>
</div>
<div className="bg-background-80 rounded-lg overflow-auto p-2 w-full h-[300px]">
<pre className="text-wrap">{udevContent}</pre>
</div>
</div>
</div>
<div className="flex justify-between gap-2">
<CheckboxInternal
label={l10n.getString(
'install-info_udev-rules_modal-dont-show-again_checkbox'
)}
name="dismiss-udev-rules-checkbox"
onChange={(e) => setDontShowAgain(e.currentTarget.checked)}
/>
<Button
variant="primary"
onClick={handleModalClose}
id="install-info_udev-rules_modal_button"
/>
</div>
</div>
</BaseModal>
);
}
Loading