mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-18 22:33:50 +02:00
Electron support for Linux (#1074)
* init * SQLite changes * Move html folder, edit build scripts * AppApi interface * Build flags * AppApi inheritance * Finishing touches * Merge upstream changes * Test CI * Fix class inits * Rename AppApi * Merge upstream changes * Fix SQLiteLegacy on Linux, Add Linux interop, build tools * Linux specific localisation strings * Make it run * Bring back most of Linux functionality * Clean up * Fix TTS voices * Fix UI var * Changes * Electron minimise to tray * Remove separate toggle for WlxOverlay * Fixes * Touchups * Move csproj * Window zoom, Desktop Notifications, VR check on Linux * Fix desktop notifications, VR check spam * Fix building on Linux * Clean up * Fix WebApi headers * Rewrite VRCX updater * Clean up * Linux updater * Add Linux to build action * init * SQLite changes * Move html folder, edit build scripts * AppApi interface * Build flags * AppApi inheritance * Finishing touches * Merge upstream changes * Test CI * Fix class inits * Rename AppApi * Merge upstream changes * Fix SQLiteLegacy on Linux, Add Linux interop, build tools * Linux specific localisation strings * Make it run * Bring back most of Linux functionality * Clean up * Fix TTS voices * Changes * Electron minimise to tray * Remove separate toggle for WlxOverlay * Fixes * Touchups * Move csproj * Window zoom, Desktop Notifications, VR check on Linux * Fix desktop notifications, VR check spam * Fix building on Linux * Clean up * Fix WebApi headers * Rewrite VRCX updater * Clean up * Linux updater * Add Linux to build action * Test updater * Rebase and handle merge conflicts * Fix Linux updater * Fix Linux app restart * Fix friend order * Handle AppImageInstaller, show an install message on Linux * Updates to the AppImage installer * Fix Linux updater, fix set version, check for .NET, copy wine prefix * Handle random errors * Rotate tall prints * try fix Linux restart bug * Final --------- Co-authored-by: rs189 <35667100+rs189@users.noreply.github.com>
This commit is contained in:
37
src-electron/InteropApi.js
Normal file
37
src-electron/InteropApi.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const dotnet = require('node-api-dotnet/net8.0');
|
||||
|
||||
class InteropApi {
|
||||
constructor() {
|
||||
// Cache for .NET objects, might be problematic if we require a new instance every time
|
||||
this.createdObjects = {};
|
||||
}
|
||||
|
||||
getDotNetObject(className) {
|
||||
if (!this.createdObjects[className]) {
|
||||
console.log(`Creating new instance of ${className}`);
|
||||
this.createdObjects[className] = new dotnet.VRCX[className]();
|
||||
}
|
||||
return this.createdObjects[className];
|
||||
}
|
||||
|
||||
callMethod(className, methodName, args) {
|
||||
try {
|
||||
const obj = this.getDotNetObject(className);
|
||||
if (typeof obj[methodName] !== 'function') {
|
||||
throw new Error(
|
||||
`Method ${methodName} does not exist on class ${className}`
|
||||
);
|
||||
}
|
||||
return obj[methodName](...args);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
'Error calling .NET method',
|
||||
`${className}.${methodName}`,
|
||||
e
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InteropApi;
|
||||
538
src-electron/main.js
Normal file
538
src-electron/main.js
Normal file
@@ -0,0 +1,538 @@
|
||||
require('hazardous');
|
||||
const path = require('path');
|
||||
const {
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
app,
|
||||
Tray,
|
||||
Menu,
|
||||
dialog,
|
||||
Notification
|
||||
} = require('electron');
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
|
||||
if (!isDotNetInstalled()) {
|
||||
dialog.showErrorBox('VRCX', 'Please install .NET 8.0 Runtime to run VRCX.');
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
tryCopyFromWinePrefix();
|
||||
|
||||
const rootDir = app.getAppPath();
|
||||
require(path.join(rootDir, 'build/Electron/VRCX-Electron.cjs'));
|
||||
|
||||
const InteropApi = require('./InteropApi');
|
||||
const interopApi = new InteropApi();
|
||||
|
||||
const version = getVersion();
|
||||
interopApi.getDotNetObject('ProgramElectron').PreInit(version);
|
||||
interopApi.getDotNetObject('VRCXStorage').Load();
|
||||
interopApi.getDotNetObject('ProgramElectron').Init();
|
||||
interopApi.getDotNetObject('SQLiteLegacy').Init();
|
||||
interopApi.getDotNetObject('AppApiElectron').Init();
|
||||
interopApi.getDotNetObject('Discord').Init();
|
||||
interopApi.getDotNetObject('WebApi').Init();
|
||||
interopApi.getDotNetObject('LogWatcher').Init();
|
||||
|
||||
ipcMain.handle('callDotNetMethod', (event, className, methodName, args) => {
|
||||
return interopApi.callMethod(className, methodName, args);
|
||||
});
|
||||
|
||||
let mainWindow = undefined;
|
||||
|
||||
const VRCXStorage = interopApi.getDotNetObject('VRCXStorage');
|
||||
let isCloseToTray = VRCXStorage.Get('VRCX_CloseToTray') === 'true';
|
||||
let appImagePath = process.env.APPIMAGE;
|
||||
|
||||
ipcMain.handle('applyWindowSettings', (event, position, size, state) => {
|
||||
if (position) {
|
||||
mainWindow.setPosition(parseInt(position.x), parseInt(position.y));
|
||||
}
|
||||
if (size) {
|
||||
mainWindow.setSize(parseInt(size.width), parseInt(size.height));
|
||||
}
|
||||
if (state) {
|
||||
if (state === '0') {
|
||||
mainWindow.restore();
|
||||
} else if (state === '1') {
|
||||
mainWindow.restore();
|
||||
} else if (state === '2') {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('dialog:openFile', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
filters: [{ name: 'Images', extensions: ['png'] }]
|
||||
});
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths[0];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ipcMain.handle('dialog:openDirectory', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory']
|
||||
});
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths[0];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ipcMain.handle('notification:showNotification', (event, title, body, icon) => {
|
||||
const notification = {
|
||||
title,
|
||||
body,
|
||||
icon
|
||||
};
|
||||
new Notification(notification).show();
|
||||
});
|
||||
|
||||
ipcMain.handle('app:restart', () => {
|
||||
if (process.platform === 'linux') {
|
||||
const options = { args: process.argv.slice(1) };
|
||||
if (appImagePath) {
|
||||
options.execPath = appImagePath;
|
||||
options.args.unshift('--appimage-extract-and-run');
|
||||
}
|
||||
app.relaunch(options);
|
||||
app.exit(0);
|
||||
} else {
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
function createWindow() {
|
||||
app.commandLine.appendSwitch('enable-speech-dispatcher');
|
||||
|
||||
const x = parseInt(VRCXStorage.Get('VRCX_LocationX')) || 0;
|
||||
const y = parseInt(VRCXStorage.Get('VRCX_LocationY')) || 0;
|
||||
const width = parseInt(VRCXStorage.Get('VRCX_SizeWidth')) || 1920;
|
||||
const height = parseInt(VRCXStorage.Get('VRCX_SizeHeight')) || 1080;
|
||||
mainWindow = new BrowserWindow({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
icon: path.join(rootDir, 'VRCX.png'),
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
},
|
||||
webContents: {
|
||||
userAgent: version
|
||||
}
|
||||
});
|
||||
applyWindowState();
|
||||
const indexPath = path.join(rootDir, 'build/html/index.html');
|
||||
mainWindow.loadFile(indexPath, { userAgent: version });
|
||||
|
||||
// add proxy config
|
||||
// const proxy = VRCXStorage.Get('VRCX_Proxy');
|
||||
// if (proxy) {
|
||||
// session.setProxy(
|
||||
// { proxyRules: proxy.replaceAll('://', '=') },
|
||||
// function () {
|
||||
// mainWindow.loadFile(indexPath);
|
||||
// }
|
||||
// );
|
||||
// session.setProxy({
|
||||
// proxyRules: proxy.replaceAll('://', '=')
|
||||
// });
|
||||
// }
|
||||
|
||||
// Open the DevTools.
|
||||
// mainWindow.webContents.openDevTools()
|
||||
|
||||
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||
if (input.control && input.key === '=') {
|
||||
mainWindow.webContents.setZoomLevel(
|
||||
mainWindow.webContents.getZoomLevel() + 1
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('zoom-changed', (event, zoomDirection) => {
|
||||
const currentZoom = mainWindow.webContents.getZoomLevel();
|
||||
if (zoomDirection === 'in') {
|
||||
mainWindow.webContents.setZoomLevel(currentZoom + 1);
|
||||
} else {
|
||||
mainWindow.webContents.setZoomLevel(currentZoom - 1);
|
||||
}
|
||||
});
|
||||
mainWindow.webContents.setVisualZoomLevelLimits(1, 5);
|
||||
|
||||
mainWindow.on('close', (event) => {
|
||||
isCloseToTray = VRCXStorage.Get('VRCX_CloseToTray') === 'true';
|
||||
if (isCloseToTray && !app.isQuitting) {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on('resize', () => {
|
||||
const [width, height] = mainWindow
|
||||
.getSize()
|
||||
.map((size) => size.toString());
|
||||
mainWindow.webContents.send('setWindowSize', { width, height });
|
||||
});
|
||||
|
||||
mainWindow.on('move', () => {
|
||||
const [x, y] = mainWindow
|
||||
.getPosition()
|
||||
.map((coord) => coord.toString());
|
||||
mainWindow.webContents.send('setWindowPosition', { x, y });
|
||||
});
|
||||
|
||||
mainWindow.on('maximize', () => {
|
||||
mainWindow.webContents.send('setWindowState', '2');
|
||||
});
|
||||
|
||||
mainWindow.on('minimize', () => {
|
||||
mainWindow.webContents.send('setWindowState', '1');
|
||||
});
|
||||
|
||||
mainWindow.on('unmaximize', () => {
|
||||
mainWindow.webContents.send('setWindowState', '0');
|
||||
});
|
||||
|
||||
mainWindow.on('restore', () => {
|
||||
mainWindow.webContents.send('setWindowState', '0');
|
||||
});
|
||||
}
|
||||
|
||||
function createTray() {
|
||||
const tray = new Tray(path.join(rootDir, 'images/tray.png'));
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Open',
|
||||
type: 'normal',
|
||||
click: function () {
|
||||
BrowserWindow.getAllWindows().forEach(function (win) {
|
||||
win.show();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'DevTools',
|
||||
type: 'normal',
|
||||
click: function () {
|
||||
BrowserWindow.getAllWindows().forEach(function (win) {
|
||||
win.webContents.openDevTools();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Quit VRCX',
|
||||
type: 'normal',
|
||||
click: function () {
|
||||
app.isQuitting = true;
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
]);
|
||||
tray.setToolTip('VRCX');
|
||||
tray.setContextMenu(contextMenu);
|
||||
|
||||
tray.on('click', () => {
|
||||
BrowserWindow.getAllWindows().forEach(function (win) {
|
||||
win.show();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
async function installVRCXappImageLauncher() {
|
||||
const iconUrl =
|
||||
'https://raw.githubusercontent.com/vrcx-team/VRCX/master/VRCX.png';
|
||||
|
||||
let targetIconName;
|
||||
const desktopFiles = fs.readdirSync(
|
||||
path.join(app.getPath('home'), '.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('AppImage path:', appImagePath);
|
||||
if (!appImagePath) {
|
||||
console.error('AppImage path is not available!');
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
let appImageLauncherInstalled = false;
|
||||
if (fs.existsSync('/usr/bin/AppImageLauncher')) {
|
||||
appImageLauncherInstalled = true;
|
||||
}
|
||||
*/
|
||||
|
||||
if (
|
||||
appImagePath.startsWith(path.join(app.getPath('home'), '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);
|
||||
try {
|
||||
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.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
process.env.APPIMAGE.startsWith(
|
||||
path.join(app.getPath('home'), 'Applications')
|
||||
) &&
|
||||
path.basename(process.env.APPIMAGE) === 'VRCX.AppImage'
|
||||
) {
|
||||
interopApi.getDotNetObject('Update').Init(appImagePath);
|
||||
console.log('VRCX is already installed.');
|
||||
return;
|
||||
}
|
||||
|
||||
const targetPath = path.join(app.getPath('home'), '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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 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(
|
||||
app.getPath('home'),
|
||||
'.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}
|
||||
Icon=VRCX
|
||||
Type=Application
|
||||
Categories=Network;InstantMessaging;Game;
|
||||
Terminal=false
|
||||
StartupWMClass=VRCX
|
||||
`;
|
||||
|
||||
const desktopFilePath = path.join(
|
||||
app.getPath('home'),
|
||||
'.local/share/applications/VRCX.desktop'
|
||||
);
|
||||
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;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error downloading icon:', err);
|
||||
dialog.showErrorBox('VRCX', 'Failed to download the icon.');
|
||||
});
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: 'VRCX',
|
||||
message: 'VRCX has been installed successfully.',
|
||||
detail: 'You can now find VRCX in your ~/Applications folder.'
|
||||
});
|
||||
}
|
||||
|
||||
function downloadIcon(url, targetPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const file = fs.createWriteStream(targetPath);
|
||||
https
|
||||
.get(url, (response) => {
|
||||
if (response.statusCode !== 200) {
|
||||
reject(
|
||||
new Error(
|
||||
`Failed to download icon, status code: ${response.statusCode}`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
response.pipe(file);
|
||||
file.on('finish', () => {
|
||||
file.close(resolve);
|
||||
});
|
||||
})
|
||||
.on('error', (err) => {
|
||||
fs.unlink(targetPath, () => reject(err)); // Delete the file if error occurs
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getVRCXPath() {
|
||||
if (process.platform === 'win32') {
|
||||
return path.join(process.env.APPDATA, 'VRCX');
|
||||
} else if (process.platform === 'linux') {
|
||||
return path.join(process.env.HOME, '.config/VRCX');
|
||||
} else if (process.platform === 'darwin') {
|
||||
return path.join(process.env.HOME, 'Library/Application Support/VRCX');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getVersion() {
|
||||
let version = 'VRCX (Linux) Build';
|
||||
try {
|
||||
version = `VRCX (Linux) ${fs.readFileSync(path.join(rootDir, 'Version'), 'utf8').trim()}`;
|
||||
} catch (err) {
|
||||
console.error('Error reading Version:', err);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
function isDotNetInstalled() {
|
||||
const result = require('child_process').spawnSync(
|
||||
'dotnet',
|
||||
['--list-runtimes'],
|
||||
{
|
||||
encoding: 'utf-8'
|
||||
}
|
||||
);
|
||||
return result.stdout.includes('.NETCore.App 8.0');
|
||||
}
|
||||
|
||||
function tryCopyFromWinePrefix() {
|
||||
try {
|
||||
if (!fs.existsSync(getVRCXPath())) {
|
||||
// try copy from old wine path
|
||||
const userName = process.env.USER || process.env.USERNAME;
|
||||
const oldPath = path.join(
|
||||
app.getPath('home'),
|
||||
'.local/share/vrcx/drive_c/users',
|
||||
userName,
|
||||
'AppData/Roaming/VRCX'
|
||||
);
|
||||
const newPath = getVRCXPath();
|
||||
if (fs.existsSync(oldPath)) {
|
||||
fs.mkdirSync(newPath, { recursive: true });
|
||||
const files = fs.readdirSync(oldPath);
|
||||
for (const file of files) {
|
||||
const oldFilePath = path.join(oldPath, file);
|
||||
const newFilePath = path.join(newPath, file);
|
||||
if (fs.lstatSync(oldFilePath).isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
fs.copyFileSync(oldFilePath, newFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error copying from wine prefix:', err);
|
||||
dialog.showErrorBox(
|
||||
'VRCX',
|
||||
'Failed to copy database from wine prefix.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function applyWindowState() {
|
||||
if (VRCXStorage.Get('VRCX_StartAsMinimizedState') === 'true') {
|
||||
if (isCloseToTray) {
|
||||
mainWindow.hide();
|
||||
return;
|
||||
}
|
||||
mainWindow.minimize();
|
||||
return;
|
||||
}
|
||||
const windowState = parseInt(VRCXStorage.Get('VRCX_WindowState')) || -1;
|
||||
switch (windowState) {
|
||||
case -1:
|
||||
break;
|
||||
case 0:
|
||||
mainWindow.restore();
|
||||
break;
|
||||
case 1:
|
||||
mainWindow.minimize();
|
||||
break;
|
||||
case 2:
|
||||
mainWindow.maximize();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
createTray();
|
||||
|
||||
installVRCX();
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
});
|
||||
|
||||
// app.on('before-quit', function () {
|
||||
// mainWindow.webContents.send('windowClosed');
|
||||
// });
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit();
|
||||
});
|
||||
45
src-electron/patch-node-api-dotnet.js
Normal file
45
src-electron/patch-node-api-dotnet.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function patchFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`Error: ${filePath} does not exist.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
let fileContent = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
const regex =
|
||||
/const\s+managedHostPath\s*=\s*__dirname\s*\+\s*`\/\$\{targetFramework\}\/\$\{assemblyName\}\.DotNetHost\.dll`/;
|
||||
|
||||
const newContent = fileContent.replace(
|
||||
regex,
|
||||
`let managedHostPath = __dirname + \`/\${targetFramework}/\${assemblyName}.DotNetHost.dll\`;
|
||||
managedHostPath = managedHostPath.indexOf('app.asar.unpacked') < 0 ?
|
||||
managedHostPath.replace('app.asar', 'app.asar.unpacked') : managedHostPath;`
|
||||
);
|
||||
|
||||
if (fileContent !== newContent) {
|
||||
fs.writeFileSync(filePath, newContent, 'utf8');
|
||||
console.log(`Patched: ${filePath}`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(`No changes needed for: ${filePath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Paths to patch
|
||||
let platformName = 'linux';
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
platformName = 'win';
|
||||
break;
|
||||
}
|
||||
const postBuildPath = path.join(
|
||||
__dirname,
|
||||
`./../build/${platformName}-unpacked/resources/app.asar.unpacked/node_modules/node-api-dotnet/init.js`
|
||||
);
|
||||
console.log('Patching post-build init.js...');
|
||||
patchFile(postBuildPath);
|
||||
42
src-electron/patch-package-version.js
Normal file
42
src-electron/patch-package-version.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const rootDir = path.join(__dirname, '..');
|
||||
const versionFilePath = path.join(rootDir, 'Version');
|
||||
const packageJsonPath = path.join(rootDir, 'package.json');
|
||||
|
||||
let version = '';
|
||||
try {
|
||||
version = fs.readFileSync(versionFilePath, 'utf8').trim();
|
||||
var index = version.indexOf('T');
|
||||
if (index > 0) {
|
||||
// Remove time part from version
|
||||
version = version.substring(0, index).replaceAll('-', '.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error reading Version file:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let packageJson = {};
|
||||
try {
|
||||
const packageData = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
packageJson = JSON.parse(packageData);
|
||||
} catch (err) {
|
||||
console.error('Error reading package.json:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
packageJson.version = version;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
packageJsonPath,
|
||||
JSON.stringify(packageJson, null, 4),
|
||||
'utf8'
|
||||
);
|
||||
console.log(`Updated version in package.json to: ${version}`);
|
||||
} catch (err) {
|
||||
console.error('Error writing to package.json:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
34
src-electron/preload.js
Normal file
34
src-electron/preload.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* The preload script runs before `index.html` is loaded
|
||||
* in the renderer. It has access to web APIs as well as
|
||||
* Electron's renderer process modules and some polyfilled
|
||||
* Node.js functions.
|
||||
*
|
||||
* https://www.electronjs.org/docs/latest/tutorial/sandbox
|
||||
*/
|
||||
const { contextBridge, ipcRenderer, app } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('interopApi', {
|
||||
callDotNetMethod: (className, methodName, args) => {
|
||||
return ipcRenderer.invoke(
|
||||
'callDotNetMethod',
|
||||
className,
|
||||
methodName,
|
||||
args
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
openFileDialog: () => ipcRenderer.invoke('dialog:openFile'),
|
||||
openDirectoryDialog: () => ipcRenderer.invoke('dialog:openDirectory'),
|
||||
onWindowPositionChanged: (callback) =>
|
||||
ipcRenderer.on('setWindowPosition', callback),
|
||||
onWindowSizeChanged: (callback) =>
|
||||
ipcRenderer.on('setWindowSize', callback),
|
||||
onWindowStateChange: (callback) =>
|
||||
ipcRenderer.on('setWindowState', callback),
|
||||
desktopNotification: (title, body, icon) =>
|
||||
ipcRenderer.invoke('notification:showNotification', title, body, icon),
|
||||
restartApp: () => ipcRenderer.invoke('app:restart')
|
||||
});
|
||||
29
src-electron/rename-builds.js
Normal file
29
src-electron/rename-builds.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const rootDir = path.join(__dirname, '..');
|
||||
const versionFilePath = path.join(rootDir, 'Version');
|
||||
const buildDir = path.join(rootDir, 'build');
|
||||
|
||||
let version = '';
|
||||
try {
|
||||
version = fs.readFileSync(versionFilePath, 'utf8').trim();
|
||||
} catch (err) {
|
||||
console.error('Error reading Version file:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const oldAppImage = path.join(buildDir, `VRCX_Version.AppImage`);
|
||||
const newAppImage = path.join(buildDir, `VRCX_${version}.AppImage`);
|
||||
|
||||
try {
|
||||
if (fs.existsSync(oldAppImage)) {
|
||||
fs.renameSync(oldAppImage, newAppImage);
|
||||
console.log(`Renamed: ${oldAppImage} -> ${newAppImage}`);
|
||||
} else {
|
||||
console.log(`File not found: ${oldAppImage}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error renaming files:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user