mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-05 01:10:49 +00:00
Init AppImage support
This commit is contained in:
@@ -90,6 +90,7 @@ const NODE_PACKAGES = new Set([
|
|||||||
'@signalapp/mock-server',
|
'@signalapp/mock-server',
|
||||||
'@tailwindcss/cli',
|
'@tailwindcss/cli',
|
||||||
'@tailwindcss/postcss',
|
'@tailwindcss/postcss',
|
||||||
|
'better-blockmap',
|
||||||
'chokidar-cli',
|
'chokidar-cli',
|
||||||
'cross-env',
|
'cross-env',
|
||||||
'electron-builder',
|
'electron-builder',
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ import { getOwn } from '../ts/util/getOwn.std.js';
|
|||||||
import { safeParseLoose, safeParseUnknown } from '../ts/util/schemas.std.js';
|
import { safeParseLoose, safeParseUnknown } from '../ts/util/schemas.std.js';
|
||||||
import { getAppErrorIcon } from '../ts/util/getAppErrorIcon.node.js';
|
import { getAppErrorIcon } from '../ts/util/getAppErrorIcon.node.js';
|
||||||
import { promptOSAuth } from '../ts/util/os/promptOSAuthMain.main.js';
|
import { promptOSAuth } from '../ts/util/os/promptOSAuthMain.main.js';
|
||||||
|
import { appRelaunch } from '../ts/util/relaunch.main.js';
|
||||||
import { sendDummyKeystroke } from './WindowsNotifications.main.js';
|
import { sendDummyKeystroke } from './WindowsNotifications.main.js';
|
||||||
|
|
||||||
const { chmod, realpath, writeFile } = fsExtra;
|
const { chmod, realpath, writeFile } = fsExtra;
|
||||||
@@ -1935,7 +1936,7 @@ const onDatabaseInitializationError = async (error: Error) => {
|
|||||||
log.error(
|
log.error(
|
||||||
'onDatabaseInitializationError: Requesting immediate restart after quit'
|
'onDatabaseInitializationError: Requesting immediate restart after quit'
|
||||||
);
|
);
|
||||||
app.relaunch();
|
appRelaunch();
|
||||||
}
|
}
|
||||||
} else if (buttonIndex === goToSupportPageButtonIndex) {
|
} else if (buttonIndex === goToSupportPageButtonIndex) {
|
||||||
drop(
|
drop(
|
||||||
@@ -2343,6 +2344,11 @@ app.on('ready', async () => {
|
|||||||
|
|
||||||
function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
|
function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
|
||||||
const { platform } = process;
|
const { platform } = process;
|
||||||
|
const platformForMenu =
|
||||||
|
platform === 'linux' && process.env.APPIMAGE != null
|
||||||
|
? 'linux-appimage'
|
||||||
|
: platform;
|
||||||
|
|
||||||
const version = app.getVersion();
|
const version = app.getVersion();
|
||||||
menuOptions = {
|
menuOptions = {
|
||||||
// options
|
// options
|
||||||
@@ -2351,7 +2357,7 @@ function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
|
|||||||
includeSetup: false,
|
includeSetup: false,
|
||||||
isNightly: isNightly(version),
|
isNightly: isNightly(version),
|
||||||
isProduction: isProduction(version),
|
isProduction: isProduction(version),
|
||||||
platform,
|
platform: platformForMenu,
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
forceUpdate,
|
forceUpdate,
|
||||||
@@ -2630,7 +2636,7 @@ ipc.on('draw-attention', () => {
|
|||||||
|
|
||||||
ipc.on('restart', () => {
|
ipc.on('restart', () => {
|
||||||
log.info('Relaunching application');
|
log.info('Relaunching application');
|
||||||
app.relaunch();
|
appRelaunch();
|
||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
ipc.on('shutdown', () => {
|
ipc.on('shutdown', () => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"updatesUrl": "https://updates2.signal.org/desktop",
|
"updatesUrl": "https://updates2.signal.org/desktop",
|
||||||
"resourcesUrl": "https://updates2.signal.org",
|
"resourcesUrl": "https://updates2.signal.org",
|
||||||
"updatesPublicKey": "05fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401",
|
"updatesPublicKey": "05fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401",
|
||||||
|
"appImageUpdatesPublicKey": "05dc20ac83f663c517718b6057f2fe34e273b88455f2149513912f24a9a380cc63",
|
||||||
"sfuUrl": "https://sfu.staging.voip.signal.org/",
|
"sfuUrl": "https://sfu.staging.voip.signal.org/",
|
||||||
"challengeUrl": "https://signalcaptchas.org/staging/challenge/generate.html",
|
"challengeUrl": "https://signalcaptchas.org/staging/challenge/generate.html",
|
||||||
"registrationChallengeUrl": "https://signalcaptchas.org/staging/registration/generate.html",
|
"registrationChallengeUrl": "https://signalcaptchas.org/staging/registration/generate.html",
|
||||||
|
|||||||
@@ -301,6 +301,7 @@
|
|||||||
"babel-core": "7.0.0-bridge.0",
|
"babel-core": "7.0.0-bridge.0",
|
||||||
"babel-loader": "9.2.1",
|
"babel-loader": "9.2.1",
|
||||||
"babel-plugin-lodash": "3.3.4",
|
"babel-plugin-lodash": "3.3.4",
|
||||||
|
"better-blockmap": "1.0.2",
|
||||||
"casual": "1.6.2",
|
"casual": "1.6.2",
|
||||||
"chai": "4.4.1",
|
"chai": "4.4.1",
|
||||||
"chai-as-promised": "7.1.1",
|
"chai-as-promised": "7.1.1",
|
||||||
@@ -526,11 +527,17 @@
|
|||||||
"StartupWMClass": "signal"
|
"StartupWMClass": "signal"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"artifactName": "${name}_${version}_${arch}.${ext}",
|
||||||
"target": [
|
"target": [
|
||||||
"deb"
|
"deb"
|
||||||
],
|
],
|
||||||
"icon": "build/icons/png",
|
"icon": "build/icons/png",
|
||||||
"publish": [],
|
"publish": [
|
||||||
|
{
|
||||||
|
"provider": "generic",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
{
|
{
|
||||||
"from": "build",
|
"from": "build",
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -622,6 +622,9 @@ importers:
|
|||||||
babel-plugin-lodash:
|
babel-plugin-lodash:
|
||||||
specifier: 3.3.4
|
specifier: 3.3.4
|
||||||
version: 3.3.4
|
version: 3.3.4
|
||||||
|
better-blockmap:
|
||||||
|
specifier: 1.0.2
|
||||||
|
version: 1.0.2
|
||||||
casual:
|
casual:
|
||||||
specifier: 1.6.2
|
specifier: 1.6.2
|
||||||
version: 1.6.2(patch_hash=b88b5052437cbdc1882137778b76ca5037f71b2a030ae9ef39dc97f51670d599)
|
version: 1.6.2(patch_hash=b88b5052437cbdc1882137778b76ca5037f71b2a030ae9ef39dc97f51670d599)
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { mkdtemp, rm, rename, stat } from 'node:fs/promises';
|
|
||||||
|
import { mkdtemp, rm, rename, stat, writeFile } from 'node:fs/promises';
|
||||||
import { createReadStream } from 'node:fs';
|
import { createReadStream } from 'node:fs';
|
||||||
import { pipeline } from 'node:stream/promises';
|
import { pipeline } from 'node:stream/promises';
|
||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import type { ArtifactCreated } from 'electron-builder';
|
import type { ArtifactCreated } from 'electron-builder';
|
||||||
|
import { BlockMap } from 'better-blockmap';
|
||||||
|
|
||||||
export async function artifactBuildCompleted({
|
export async function artifactBuildCompleted({
|
||||||
target,
|
target,
|
||||||
@@ -15,6 +17,14 @@ export async function artifactBuildCompleted({
|
|||||||
packager,
|
packager,
|
||||||
updateInfo,
|
updateInfo,
|
||||||
}: ArtifactCreated): Promise<void> {
|
}: ArtifactCreated): Promise<void> {
|
||||||
|
if (packager.platform.name === 'linux' && file.endsWith('.AppImage')) {
|
||||||
|
const blockMapPath = `${file}.blockmap`;
|
||||||
|
console.log(`Generating blockmap ${blockMapPath}`);
|
||||||
|
const blockMapGenerator = new BlockMap({ detectZipBoundary: true });
|
||||||
|
await pipeline(createReadStream(file), blockMapGenerator);
|
||||||
|
await writeFile(blockMapPath, blockMapGenerator.compress());
|
||||||
|
}
|
||||||
|
|
||||||
if (packager.platform.name !== 'mac') {
|
if (packager.platform.name !== 'mac') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
15
ts/scripts/better-blockmap.d.ts
vendored
Normal file
15
ts/scripts/better-blockmap.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
declare module 'better-blockmap' {
|
||||||
|
import { Writable } from 'node:stream';
|
||||||
|
|
||||||
|
type BlockMapOptions = {
|
||||||
|
detectZipBoundary?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class BlockMap extends Writable {
|
||||||
|
constructor(options?: BlockMapOptions);
|
||||||
|
compress(compression?: 'gzip' | 'deflate'): Buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,7 +69,7 @@ export const isAutoDownloadUpdatesSupported = (
|
|||||||
if (isNotUpdatable(appVersion)) {
|
if (isNotUpdatable(appVersion)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return OS.isWindows() || OS.isMacOS();
|
return OS.isWindows() || OS.isMacOS() || OS.isLinuxAppImage();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const shouldHideExpiringMessageBody = (
|
export const shouldHideExpiringMessageBody = (
|
||||||
|
|||||||
@@ -282,6 +282,10 @@ export abstract class Updater {
|
|||||||
markShouldQuit();
|
markShouldQuit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getUpdatesPublicKey(): Buffer {
|
||||||
|
return hexToBinary(config.get('updatesPublicKey'));
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Private methods
|
// Private methods
|
||||||
//
|
//
|
||||||
@@ -390,12 +394,11 @@ export abstract class Updater {
|
|||||||
|
|
||||||
const { updateFilePath, signature } = downloadResult;
|
const { updateFilePath, signature } = downloadResult;
|
||||||
|
|
||||||
const publicKey = hexToBinary(config.get('updatesPublicKey'));
|
|
||||||
const verified = await verifySignature(
|
const verified = await verifySignature(
|
||||||
updateFilePath,
|
updateFilePath,
|
||||||
this.version,
|
this.version,
|
||||||
signature,
|
signature,
|
||||||
publicKey
|
this.getUpdatesPublicKey()
|
||||||
);
|
);
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
// Note: We don't delete the cache here, because we don't want to continually
|
// Note: We don't delete the cache here, because we don't want to continually
|
||||||
@@ -989,6 +992,10 @@ export function getUpdatesFileName(): string {
|
|||||||
return `${prefix}-mac.yml`;
|
return `${prefix}-mac.yml`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
return `${prefix}-linux.yml`;
|
||||||
|
}
|
||||||
|
|
||||||
return `${prefix}.yml`;
|
return `${prefix}.yml`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1020,7 +1027,7 @@ export function getVersion(info: JSONUpdateSchema): string | null {
|
|||||||
return info && info.version;
|
return info && info.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validFile = /^[A-Za-z0-9.-]+$/;
|
const validFile = /^[A-Za-z0-9._-]+$/;
|
||||||
export function isUpdateFileNameValid(name: string): boolean {
|
export function isUpdateFileNameValid(name: string): boolean {
|
||||||
return validFile.test(name);
|
return validFile.test(name);
|
||||||
}
|
}
|
||||||
@@ -1041,6 +1048,8 @@ export function getUpdateFileName(
|
|||||||
fileFilter = ({ url }) => url.includes(arch) && url.endsWith('.zip');
|
fileFilter = ({ url }) => url.includes(arch) && url.endsWith('.zip');
|
||||||
} else if (platform === 'win32') {
|
} else if (platform === 'win32') {
|
||||||
fileFilter = ({ url }) => url.includes(arch) && url.endsWith('.exe');
|
fileFilter = ({ url }) => url.includes(arch) && url.endsWith('.exe');
|
||||||
|
} else if (platform === 'linux' && process.env.APPIMAGE != null) {
|
||||||
|
fileFilter = ({ url }) => url.endsWith('.AppImage');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileFilter) {
|
if (fileFilter) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { app } from 'electron';
|
|||||||
import type { Updater, UpdaterOptionsType } from './common.main.js';
|
import type { Updater, UpdaterOptionsType } from './common.main.js';
|
||||||
import { MacOSUpdater } from './macos.main.js';
|
import { MacOSUpdater } from './macos.main.js';
|
||||||
import { WindowsUpdater } from './windows.main.js';
|
import { WindowsUpdater } from './windows.main.js';
|
||||||
|
import { LinuxAppImageUpdater } from './linuxAppImage.main.js';
|
||||||
import { initLinux } from './linux.main.js';
|
import { initLinux } from './linux.main.js';
|
||||||
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
@@ -38,7 +39,11 @@ export async function start(options: UpdaterOptionsType): Promise<void> {
|
|||||||
} else if (platform === 'darwin') {
|
} else if (platform === 'darwin') {
|
||||||
updater = new MacOSUpdater(options);
|
updater = new MacOSUpdater(options);
|
||||||
} else if (platform === 'linux') {
|
} else if (platform === 'linux') {
|
||||||
initLinux(options);
|
if (process.env.APPIMAGE != null) {
|
||||||
|
updater = new LinuxAppImageUpdater(options);
|
||||||
|
} else {
|
||||||
|
initLinux(options);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`updater/start: Unsupported platform ${platform}`);
|
throw new Error(`updater/start: Unsupported platform ${platform}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type { LoggerType } from '../types/Logging.std.js';
|
|||||||
import { DialogType } from '../types/Dialogs.std.js';
|
import { DialogType } from '../types/Dialogs.std.js';
|
||||||
import * as Errors from '../types/errors.std.js';
|
import * as Errors from '../types/errors.std.js';
|
||||||
import type { UpdaterOptionsType } from './common.main.js';
|
import type { UpdaterOptionsType } from './common.main.js';
|
||||||
|
import { appRelaunch } from '../util/relaunch.main.js';
|
||||||
|
|
||||||
const MIN_UBUNTU_VERSION = '22.04';
|
const MIN_UBUNTU_VERSION = '22.04';
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ export function initLinux({ logger, getMainWindow }: UpdaterOptionsType): void {
|
|||||||
ipcMain.handle('start-update', () => {
|
ipcMain.handle('start-update', () => {
|
||||||
logger?.info('updater/linux: restarting');
|
logger?.info('updater/linux: restarting');
|
||||||
markShouldQuit();
|
markShouldQuit();
|
||||||
app.relaunch();
|
appRelaunch();
|
||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
74
ts/updater/linuxAppImage.main.ts
Normal file
74
ts/updater/linuxAppImage.main.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { copyFile, unlink } from 'node:fs/promises';
|
||||||
|
import { chmod } from 'fs-extra';
|
||||||
|
|
||||||
|
import config from 'config';
|
||||||
|
import { app } from 'electron';
|
||||||
|
|
||||||
|
import { Updater } from './common.main.js';
|
||||||
|
import { appRelaunch } from '../util/relaunch.main.js';
|
||||||
|
import { hexToBinary } from './signature.node.js';
|
||||||
|
|
||||||
|
export class LinuxAppImageUpdater extends Updater {
|
||||||
|
#installing = false;
|
||||||
|
|
||||||
|
protected async deletePreviousInstallers(): Promise<void> {
|
||||||
|
// No installers are cached beyond the most recent one
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async installUpdate(
|
||||||
|
updateFilePath: string
|
||||||
|
): Promise<() => Promise<void>> {
|
||||||
|
const { logger } = this;
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
logger.info('downloadAndInstall: installing...');
|
||||||
|
try {
|
||||||
|
await this.#install(updateFilePath);
|
||||||
|
this.#installing = true;
|
||||||
|
} catch (error) {
|
||||||
|
this.markCannotUpdate(error);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If interrupted at this point, we only want to restart (not reattempt install)
|
||||||
|
this.setUpdateListener(this.restart);
|
||||||
|
this.restart();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected restart(): void {
|
||||||
|
this.logger.info('downloadAndInstall: restarting...');
|
||||||
|
|
||||||
|
this.markRestarting();
|
||||||
|
appRelaunch();
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
override getUpdatesPublicKey(): Buffer {
|
||||||
|
return hexToBinary(config.get('appImageUpdatesPublicKey'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async #install(updateFilePath: string): Promise<void> {
|
||||||
|
if (this.#installing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { logger } = this;
|
||||||
|
|
||||||
|
logger.info('linuxAppImage/install: installing package...');
|
||||||
|
|
||||||
|
const appImageFile = process.env.APPIMAGE;
|
||||||
|
if (appImageFile == null) {
|
||||||
|
throw new Error('APPIMAGE env is not defined!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/1712051/1910191
|
||||||
|
await unlink(appImageFile);
|
||||||
|
await copyFile(updateFilePath, appImageFile);
|
||||||
|
await chmod(appImageFile, 0o700);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,17 @@ function getLinuxName(): string | undefined {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return match[1];
|
const name = match[1];
|
||||||
|
if (isAppImage()) {
|
||||||
|
return `${name} (AppImage)`;
|
||||||
|
}
|
||||||
|
// Flatpak is noted already in /etc/os-release
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAppImage(): boolean {
|
||||||
|
return process.platform === 'linux' && process.env.APPIMAGE != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isFlatpak(): boolean {
|
function isFlatpak(): boolean {
|
||||||
@@ -45,6 +55,7 @@ function isLinuxUsingKDE(): boolean {
|
|||||||
const OS = {
|
const OS = {
|
||||||
...getOSFunctions(os.release()),
|
...getOSFunctions(os.release()),
|
||||||
getLinuxName,
|
getLinuxName,
|
||||||
|
isAppImage,
|
||||||
isFlatpak,
|
isFlatpak,
|
||||||
isLinuxUsingKDE,
|
isLinuxUsingKDE,
|
||||||
isWaylandEnabled,
|
isWaylandEnabled,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type OSType = {
|
|||||||
getClassName: () => string;
|
getClassName: () => string;
|
||||||
getName: () => string;
|
getName: () => string;
|
||||||
isLinux: (minVersion?: string) => boolean;
|
isLinux: (minVersion?: string) => boolean;
|
||||||
|
isLinuxAppImage: () => boolean;
|
||||||
isMacOS: (minVersion?: string) => boolean;
|
isMacOS: (minVersion?: string) => boolean;
|
||||||
isWindows: (minVersion?: string) => boolean;
|
isWindows: (minVersion?: string) => boolean;
|
||||||
};
|
};
|
||||||
@@ -32,6 +33,10 @@ export function getOSFunctions(osRelease: string): OSType {
|
|||||||
const isLinux = createIsPlatform('linux', osRelease);
|
const isLinux = createIsPlatform('linux', osRelease);
|
||||||
const isWindows = createIsPlatform('win32', osRelease);
|
const isWindows = createIsPlatform('win32', osRelease);
|
||||||
|
|
||||||
|
const isLinuxAppImage = (): boolean => {
|
||||||
|
return process.platform === 'linux' && process.env.APPIMAGE != null;
|
||||||
|
};
|
||||||
|
|
||||||
const getName = (): string => {
|
const getName = (): string => {
|
||||||
if (isMacOS()) {
|
if (isMacOS()) {
|
||||||
return 'macOS';
|
return 'macOS';
|
||||||
@@ -56,6 +61,7 @@ export function getOSFunctions(osRelease: string): OSType {
|
|||||||
getClassName,
|
getClassName,
|
||||||
getName,
|
getName,
|
||||||
isLinux,
|
isLinux,
|
||||||
|
isLinuxAppImage,
|
||||||
isMacOS,
|
isMacOS,
|
||||||
isWindows,
|
isWindows,
|
||||||
};
|
};
|
||||||
|
|||||||
21
ts/util/relaunch.main.ts
Normal file
21
ts/util/relaunch.main.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { app } from 'electron';
|
||||||
|
import type { RelaunchOptions } from 'electron';
|
||||||
|
|
||||||
|
import OS from './os/osMain.node.js';
|
||||||
|
|
||||||
|
// app.relaunch() doesn't work in AppImage, so this is a workaround
|
||||||
|
export function appRelaunch(): void {
|
||||||
|
if (!OS.isAppImage()) {
|
||||||
|
app.relaunch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: RelaunchOptions = {
|
||||||
|
args: ['--appimage-extract-and-run', ...process.argv],
|
||||||
|
execPath: process.env.APPIMAGE,
|
||||||
|
};
|
||||||
|
app.relaunch(options);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user