All checks were successful
Queue Release Build / prepare (push) Successful in 14s
Deploy Web Apps / deploy (push) Successful in 14m39s
Queue Release Build / build-linux (push) Successful in 40m59s
Queue Release Build / build-windows (push) Successful in 28m59s
Queue Release Build / finalize (push) Successful in 1m58s
300 lines
6.7 KiB
TypeScript
300 lines
6.7 KiB
TypeScript
import {
|
|
app,
|
|
BrowserWindow,
|
|
desktopCapturer,
|
|
Menu,
|
|
session,
|
|
shell,
|
|
Tray
|
|
} from 'electron';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { readDesktopSettings } from '../desktop-settings';
|
|
|
|
let mainWindow: BrowserWindow | null = null;
|
|
let tray: Tray | null = null;
|
|
let closeToTrayEnabled = true;
|
|
let appQuitting = false;
|
|
|
|
const WINDOW_STATE_CHANGED_CHANNEL = 'window-state-changed';
|
|
|
|
function getAssetPath(...segments: string[]): string {
|
|
const basePath = app.isPackaged
|
|
? path.join(process.resourcesPath, 'images')
|
|
: path.join(__dirname, '..', '..', '..', 'images');
|
|
|
|
return path.join(basePath, ...segments);
|
|
}
|
|
|
|
function getExistingAssetPath(...segments: string[]): string | undefined {
|
|
const assetPath = getAssetPath(...segments);
|
|
|
|
return fs.existsSync(assetPath) ? assetPath : undefined;
|
|
}
|
|
|
|
function getWindowIconPath(): string | undefined {
|
|
if (process.platform === 'win32')
|
|
return getExistingAssetPath('windows', 'icon.ico');
|
|
|
|
if (process.platform === 'linux')
|
|
return getExistingAssetPath('icon.png');
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function getDockIconPath(): string | undefined {
|
|
return getExistingAssetPath('macos', '1024x1024.png');
|
|
}
|
|
|
|
function getTrayIconPath(): string | undefined {
|
|
if (process.platform === 'win32')
|
|
return getExistingAssetPath('windows', 'icon.ico');
|
|
|
|
return getExistingAssetPath('icon.png');
|
|
}
|
|
|
|
export { getWindowIconPath };
|
|
|
|
export function getMainWindow(): BrowserWindow | null {
|
|
return mainWindow;
|
|
}
|
|
|
|
function destroyTray(): void {
|
|
if (!tray) {
|
|
return;
|
|
}
|
|
|
|
tray.destroy();
|
|
tray = null;
|
|
}
|
|
|
|
function requestAppQuit(): void {
|
|
prepareWindowForAppQuit();
|
|
app.quit();
|
|
}
|
|
|
|
function ensureTray(): void {
|
|
if (tray) {
|
|
return;
|
|
}
|
|
|
|
const trayIconPath = getTrayIconPath();
|
|
|
|
if (!trayIconPath) {
|
|
return;
|
|
}
|
|
|
|
tray = new Tray(trayIconPath);
|
|
tray.setToolTip('MetoYou');
|
|
tray.setContextMenu(
|
|
Menu.buildFromTemplate([
|
|
{
|
|
label: 'Open MetoYou',
|
|
click: () => {
|
|
void showMainWindow();
|
|
}
|
|
},
|
|
{
|
|
type: 'separator'
|
|
},
|
|
{
|
|
label: 'Close MetoYou',
|
|
click: () => {
|
|
requestAppQuit();
|
|
}
|
|
}
|
|
])
|
|
);
|
|
|
|
tray.on('click', () => {
|
|
void showMainWindow();
|
|
});
|
|
}
|
|
|
|
function hideWindowToTray(): void {
|
|
if (!mainWindow || mainWindow.isDestroyed()) {
|
|
return;
|
|
}
|
|
|
|
mainWindow.hide();
|
|
emitWindowState();
|
|
}
|
|
|
|
export function updateCloseToTraySetting(enabled: boolean): void {
|
|
closeToTrayEnabled = enabled;
|
|
}
|
|
|
|
export function prepareWindowForAppQuit(): void {
|
|
appQuitting = true;
|
|
destroyTray();
|
|
}
|
|
|
|
export async function showMainWindow(): Promise<void> {
|
|
if (!mainWindow || mainWindow.isDestroyed()) {
|
|
await createWindow();
|
|
return;
|
|
}
|
|
|
|
if (mainWindow.isMinimized()) {
|
|
mainWindow.restore();
|
|
}
|
|
|
|
if (!mainWindow.isVisible()) {
|
|
mainWindow.show();
|
|
}
|
|
|
|
mainWindow.focus();
|
|
emitWindowState();
|
|
}
|
|
|
|
function emitWindowState(): void {
|
|
if (!mainWindow || mainWindow.isDestroyed()) {
|
|
return;
|
|
}
|
|
|
|
mainWindow.webContents.send(WINDOW_STATE_CHANGED_CHANNEL, {
|
|
isFocused: mainWindow.isFocused(),
|
|
isMinimized: mainWindow.isMinimized()
|
|
});
|
|
}
|
|
|
|
export async function createWindow(): Promise<void> {
|
|
const windowIconPath = getWindowIconPath();
|
|
|
|
closeToTrayEnabled = readDesktopSettings().closeToTray;
|
|
ensureTray();
|
|
|
|
mainWindow = new BrowserWindow({
|
|
width: 1400,
|
|
height: 900,
|
|
minWidth: 800,
|
|
minHeight: 600,
|
|
frame: false,
|
|
titleBarStyle: 'hidden',
|
|
backgroundColor: '#0a0a0f',
|
|
...(windowIconPath ? { icon: windowIconPath } : {}),
|
|
webPreferences: {
|
|
backgroundThrottling: false,
|
|
nodeIntegration: false,
|
|
contextIsolation: true,
|
|
preload: path.join(__dirname, '..', 'preload.js'),
|
|
webSecurity: true
|
|
}
|
|
});
|
|
|
|
if (process.platform === 'linux') {
|
|
session.defaultSession.setDisplayMediaRequestHandler(
|
|
async (_request, respond) => {
|
|
// On Linux/Wayland the system picker (useSystemPicker: true) handles
|
|
// the portal. This handler is only reached if the system picker is
|
|
// unavailable (e.g. X11 without a portal). Fall back to
|
|
// desktopCapturer so the user still gets something.
|
|
try {
|
|
const sources = await desktopCapturer.getSources({
|
|
types: ['window', 'screen'],
|
|
thumbnailSize: { width: 150, height: 150 }
|
|
});
|
|
const firstSource = sources[0];
|
|
|
|
if (firstSource) {
|
|
respond({ video: firstSource });
|
|
return;
|
|
}
|
|
} catch {
|
|
// desktopCapturer also unavailable
|
|
}
|
|
|
|
respond({});
|
|
},
|
|
{ useSystemPicker: true }
|
|
);
|
|
}
|
|
|
|
if (process.env['NODE_ENV'] === 'development') {
|
|
const devUrl = process.env['SSL'] === 'true'
|
|
? 'https://localhost:4200'
|
|
: 'http://localhost:4200';
|
|
|
|
await mainWindow.loadURL(devUrl);
|
|
|
|
if (process.env['DEBUG_DEVTOOLS'] === '1') {
|
|
mainWindow.webContents.openDevTools();
|
|
}
|
|
} else {
|
|
await mainWindow.loadFile(path.join(__dirname, '..', '..', 'client', 'browser', 'index.html'));
|
|
}
|
|
|
|
mainWindow.on('close', (event) => {
|
|
if (appQuitting || !closeToTrayEnabled) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
hideWindowToTray();
|
|
});
|
|
|
|
mainWindow.on('closed', () => {
|
|
mainWindow = null;
|
|
});
|
|
|
|
mainWindow.on('focus', () => {
|
|
mainWindow?.flashFrame(false);
|
|
emitWindowState();
|
|
});
|
|
|
|
mainWindow.on('blur', () => {
|
|
emitWindowState();
|
|
});
|
|
|
|
mainWindow.on('minimize', () => {
|
|
emitWindowState();
|
|
});
|
|
|
|
mainWindow.on('restore', () => {
|
|
emitWindowState();
|
|
});
|
|
|
|
mainWindow.on('show', () => {
|
|
emitWindowState();
|
|
});
|
|
|
|
mainWindow.on('hide', () => {
|
|
emitWindowState();
|
|
});
|
|
|
|
emitWindowState();
|
|
|
|
mainWindow.webContents.on('context-menu', (_event, params) => {
|
|
mainWindow?.webContents.send('show-context-menu', {
|
|
posX: params.x,
|
|
posY: params.y,
|
|
isEditable: params.isEditable,
|
|
selectionText: params.selectionText,
|
|
linkURL: params.linkURL,
|
|
mediaType: params.mediaType,
|
|
srcURL: params.srcURL,
|
|
editFlags: {
|
|
canCut: params.editFlags.canCut,
|
|
canCopy: params.editFlags.canCopy,
|
|
canPaste: params.editFlags.canPaste,
|
|
canSelectAll: params.editFlags.canSelectAll
|
|
}
|
|
});
|
|
});
|
|
|
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
shell.openExternal(url);
|
|
return { action: 'deny' };
|
|
});
|
|
|
|
mainWindow.webContents.on('will-navigate', (event, url) => {
|
|
const currentUrl = mainWindow?.webContents.getURL();
|
|
const isSameOrigin = new URL(url).origin === new URL(currentUrl || '').origin;
|
|
|
|
if (!isSameOrigin) {
|
|
event.preventDefault();
|
|
shell.openExternal(url);
|
|
}
|
|
});
|
|
}
|