Linux: Change how AppImage moving is handled, add support for vrcx:\\

This commit is contained in:
Natsumi
2025-02-28 09:53:43 +13:00
parent 641dba2fe9
commit 137608b705
9 changed files with 167 additions and 161 deletions

View File

@@ -9,7 +9,7 @@ const {
dialog,
Notification
} = require('electron');
const { spawn } = require('child_process');
const { spawn, spawnSync } = require('child_process');
const fs = require('fs');
const https = require('https');
@@ -23,10 +23,12 @@ if (!isDotNetInstalled()) {
}
// get launch arguments
let appImagePath = process.env.APPIMAGE;
const args = process.argv.slice(1);
const noInstall = args.some((val) => val === '--no-install');
const x11 = args.some((val) => val === '--x11');
const noInstall = args.includes('--no-install');
const x11 = args.includes('--x11');
const homePath = getHomePath();
tryRelaunchWithArgs(args);
tryCopyFromWinePrefix();
const rootDir = app.getAppPath();
@@ -52,8 +54,9 @@ ipcMain.handle('callDotNetMethod', (event, className, methodName, args) => {
let mainWindow = undefined;
const VRCXStorage = interopApi.getDotNetObject('VRCXStorage');
const hasAskedToMoveAppImage =
VRCXStorage.Get('VRCX_HasAskedToMoveAppImage') === 'true';
let isCloseToTray = VRCXStorage.Get('VRCX_CloseToTray') === 'true';
let appImagePath = process.env.APPIMAGE;
ipcMain.handle('applyWindowSettings', (event, position, size, state) => {
if (position) {
@@ -107,10 +110,15 @@ ipcMain.handle('notification:showNotification', (event, title, body, icon) => {
ipcMain.handle('app:restart', () => {
if (process.platform === 'linux') {
const options = { args: process.argv.slice(1) };
const options = {
execPath: process.execPath,
args: process.argv.slice(1)
};
if (appImagePath) {
options.execPath = appImagePath;
options.args.unshift('--appimage-extract-and-run');
if (!x11 && !options.args.includes('--appimage-extract-and-run')) {
options.args.unshift('--appimage-extract-and-run');
}
}
app.relaunch(options);
app.exit(0);
@@ -120,9 +128,12 @@ ipcMain.handle('app:restart', () => {
}
});
function relaunchWithArgs(args) {
if (process.argv.includes('--ozone-platform-hint=auto')) {
console.log('Already running with correct arguments');
function tryRelaunchWithArgs(args) {
if (
process.platform !== 'linux' ||
x11 ||
args.includes('--ozone-platform-hint=auto')
) {
return;
}
@@ -148,14 +159,6 @@ function relaunchWithArgs(args) {
}
function createWindow() {
if (
process.platform === 'linux' &&
!process.argv.includes('--ozone-platform-hint=auto') &&
!x11
) {
relaunchWithArgs(process.argv.slice(1));
}
app.commandLine.appendSwitch('enable-speech-dispatcher');
const x = parseInt(VRCXStorage.Get('VRCX_LocationX')) || 0;
@@ -180,7 +183,7 @@ function createWindow() {
const indexPath = path.join(rootDir, 'build/html/index.html');
mainWindow.loadFile(indexPath, { userAgent: version });
// add proxy config
// add proxy config, doesn't work, thanks electron
// const proxy = VRCXStorage.Get('VRCX_Proxy');
// if (proxy) {
// session.setProxy(
@@ -294,35 +297,6 @@ function createTray() {
});
}
/*
async function installVRCXappImageLauncher() {
const iconUrl =
'https://raw.githubusercontent.com/vrcx-team/VRCX/master/VRCX.png';
let targetIconName;
const desktopFiles = fs.readdirSync(
path.join(homePath, '.local/share/applications')
);
for (const file of desktopFiles) {
if (file.includes('appimagekit_') && file.includes('VRCX')) {
console.log('AppImageLauncher shortcut found:', file);
targetIconName = file.replace('.desktop', '.png');
targetIconName = targetIconName.replace('-', '_');
try {
} catch (err) {
console.error('Error deleting shortcut:', err);
return;
}
}
}
const iconPath = '~/.local/share/icons/' + targetIconName;
const expandedPath = iconPath.replace('~', process.env.HOME);
const targetIconPath = path.join(expandedPath);
await downloadIcon(iconUrl, targetIconPath);
}
*/
async function installVRCX() {
console.log('Home path:', homePath);
console.log('AppImage path:', appImagePath);
@@ -331,121 +305,144 @@ async function installVRCX() {
return;
}
if (noInstall) {
interopApi.getDotNetObject('Update').Init(appImagePath);
console.log('Skipping installation.');
return;
}
/*
let appImageLauncherInstalled = false;
if (fs.existsSync('/usr/bin/AppImageLauncher')) {
appImageLauncherInstalled = true;
}
*/
if (appImagePath.startsWith(path.join(homePath, 'Applications'))) {
/*
if (appImageLauncherInstalled) {
installVRCXappImageLauncher();
}
*/
interopApi.getDotNetObject('Update').Init(appImagePath);
console.log('VRCX is already installed.');
return;
}
let currentName = path.basename(appImagePath);
let newName = 'VRCX.AppImage';
if (currentName !== newName) {
const newPath = path.join(path.dirname(appImagePath), newName);
// rename AppImage to VRCX.AppImage
const currentName = path.basename(appImagePath);
const expectedName = 'VRCX.AppImage';
if (currentName !== expectedName) {
const newPath = path.join(path.dirname(appImagePath), expectedName);
try {
// remove existing VRCX.AppImage
if (fs.existsSync(newPath)) {
fs.unlinkSync(newPath);
}
fs.renameSync(appImagePath, newPath);
console.log('AppImage renamed to:', newPath);
appImagePath = newPath;
} catch (err) {
console.error('Error renaming AppImage:', err);
dialog.showErrorBox('VRCX', 'Failed to rename AppImage.');
console.error(`Error renaming AppImage ${newPath}`, err);
dialog.showErrorBox('VRCX', `Failed to rename AppImage ${newPath}`);
return;
}
}
if (
process.env.APPIMAGE.startsWith(path.join(homePath, 'Applications')) &&
path.basename(process.env.APPIMAGE) === 'VRCX.AppImage'
) {
interopApi.getDotNetObject('Update').Init(appImagePath);
console.log('VRCX is already installed.');
return;
}
const targetPath = path.join(homePath, 'Applications');
console.log('Target Path:', targetPath);
// Create target directory if it doesn't exist
if (!fs.existsSync(targetPath)) {
fs.mkdirSync(targetPath);
}
const targetAppImagePath = path.join(targetPath, 'VRCX.AppImage');
// Move the AppImage to the target directory
try {
if (fs.existsSync(targetAppImagePath)) {
fs.unlinkSync(targetAppImagePath);
// ask to move AppImage to ~/Applications
const appImageHomePath = `${homePath}/Applications/VRCX.AppImage`;
if (!hasAskedToMoveAppImage && appImagePath !== appImageHomePath) {
const result = dialog.showMessageBoxSync(mainWindow, {
type: 'question',
title: 'VRCX',
message: 'Do you want to install VRCX?',
detail: 'VRCX will be moved to your ~/Applications folder.',
buttons: ['No', 'Yes']
});
if (result === 0) {
console.log('Cancel AppImage move to ~/Applications');
// don't ask again
VRCXStorage.Set('VRCX_HasAskedToMoveAppImage', 'true');
VRCXStorage.Save();
}
if (result === 1) {
console.log('Moving AppImage to ~/Applications');
try {
const applicationsPath = path.join(homePath, 'Applications');
// create ~/Applications if it doesn't exist
if (!fs.existsSync(applicationsPath)) {
fs.mkdirSync(applicationsPath);
}
// remove existing VRCX.AppImage
if (fs.existsSync(appImageHomePath)) {
fs.unlinkSync(appImageHomePath);
}
fs.renameSync(appImagePath, appImageHomePath);
appImagePath = appImageHomePath;
console.log('AppImage moved to:', appImageHomePath);
} catch (err) {
console.error(`Error moving AppImage ${appImageHomePath}`, err);
dialog.showErrorBox(
'VRCX',
`Failed to move AppImage ${appImageHomePath}`
);
return;
}
}
fs.renameSync(appImagePath, targetAppImagePath);
appImagePath = targetAppImagePath;
console.log('AppImage moved to:', targetAppImagePath);
} catch (err) {
console.error('Error moving AppImage:', err);
dialog.showErrorBox('VRCX', 'Failed to move AppImage.');
return;
}
// inform .NET side about AppImage path
interopApi.getDotNetObject('Update').Init(appImagePath);
await createDesktopFile();
dialog.showMessageBox({
type: 'info',
title: 'VRCX',
message: 'VRCX has been installed successfully.',
detail: 'You can now find VRCX in your ~/Applications folder.'
});
}
async function createDesktopFile() {
// Download the icon and save it to the target directory
const iconUrl =
'https://raw.githubusercontent.com/vrcx-team/VRCX/master/VRCX.png';
const iconPath = path.join(homePath, '.local/share/icons/VRCX.png');
await downloadIcon(iconUrl, iconPath)
.then(() => {
console.log('Icon downloaded and saved to:', iconPath);
const desktopFile = `[Desktop Entry]
Name=VRCX
Comment=Friendship management tool for VRChat
Exec=${appImagePath} --ozone-platform-hint=auto
Icon=VRCX
Type=Application
Categories=Network;InstantMessaging;Game;
Terminal=false
StartupWMClass=VRCX
`;
if (!fs.existsSync(iconPath)) {
const iconUrl =
'https://raw.githubusercontent.com/vrcx-team/VRCX/master/VRCX.png';
await downloadIcon(iconUrl, iconPath)
.then(() => {
console.log('Icon downloaded and saved to:', iconPath);
})
.catch((err) => {
console.error('Error downloading icon:', err);
dialog.showErrorBox('VRCX', 'Failed to download the icon.');
});
}
const desktopFilePath = path.join(
homePath,
'.local/share/applications/VRCX.desktop'
// Create the desktop file
const desktopFilePath = path.join(
homePath,
'.local/share/applications/VRCX.desktop'
);
const dotDesktop = {
Name: 'VRCX',
Comment: 'Friendship management tool for VRChat',
Exec: `${appImagePath} --ozone-platform-hint=auto %U`,
Icon: 'VRCX',
Type: 'Application',
Categories: 'Network;InstantMessaging;Game;',
Terminal: 'false',
StartupWMClass: 'VRCX',
MimeType: 'x-scheme-handler/vrcx;'
};
const desktopFile =
'[Desktop Entry]\n' +
Object.entries(dotDesktop)
.map(([key, value]) => `${key}=${value}`)
.join('\n');
try {
// create/update the desktop file when needed
let existingDesktopFile = '';
if (fs.existsSync(desktopFilePath)) {
existingDesktopFile = fs.readFileSync(desktopFilePath, 'utf8');
}
if (existingDesktopFile !== desktopFile) {
fs.writeFileSync(desktopFilePath, desktopFile);
console.log('Desktop file created at:', desktopFilePath);
const result = spawnSync(
'xdg-mime',
['default', 'VRCX.desktop', 'x-scheme-handler/vrcx'],
{
encoding: 'utf-8'
}
);
try {
fs.writeFileSync(desktopFilePath, desktopFile);
console.log('Desktop file created at:', desktopFilePath);
} catch (err) {
console.error('Error creating desktop file:', err);
dialog.showErrorBox('VRCX', 'Failed to create desktop entry.');
return;
if (result.error) {
console.error('Error setting MIME type:', result.error);
} else {
console.log('MIME type set x-scheme-handler/vrcx');
}
})
.catch((err) => {
console.error('Error downloading icon:', err);
dialog.showErrorBox('VRCX', 'Failed to download the icon.');
});
}
} catch (err) {
console.error('Error creating desktop file:', err);
dialog.showErrorBox('VRCX', 'Failed to create desktop entry.');
return;
}
}
function downloadIcon(url, targetPath) {
@@ -501,7 +498,7 @@ function getVersion() {
// look for trailing git hash "-22bcd96" to indicate nightly build
var version = versionFile.split('-');
console.log('Version:', version);
console.log('Version:', versionFile);
if (version.length > 0 && version[version.length - 1].length == 7) {
return `VRCX (Linux) Nightly ${versionFile}`;
} else {
@@ -518,13 +515,9 @@ function isDotNetInstalled() {
// Assume .NET is already installed on macOS
return true;
}
const result = require('child_process').spawnSync(
'dotnet',
['--list-runtimes'],
{
encoding: 'utf-8'
}
);
const result = spawnSync('dotnet', ['--list-runtimes'], {
encoding: 'utf-8'
});
return result.stdout?.includes('.NETCore.App 9.0');
}