diff --git a/src/electron-main.ts b/src/electron-main.ts index 4ee2b37e7..e356abc72 100644 --- a/src/electron-main.ts +++ b/src/electron-main.ts @@ -32,7 +32,6 @@ import path from "path"; import windowStateKeeper from 'electron-window-state'; import Store from 'electron-store'; import fs, { promises as afs } from "fs"; -import { URL } from "url"; import minimist from "minimist"; import "./ipc"; @@ -41,11 +40,12 @@ import "./seshat"; import "./settings"; import * as tray from "./tray"; import { buildMenuTemplate } from './vectormenu'; -import webContentsHandler from './webcontents-handler'; import * as updater from './updater'; import { getProfileFromDeeplink, protocolInit } from './protocol'; -import { _t, AppLocalization } from './language-helper'; -import Input = Electron.Input; +import { AppLocalization } from './language-helper'; +import { loadInstances } from "./instances"; +import { resize } from "./resizer"; +import { createMenuView } from "./menu-view"; const argv = minimist(process.argv, { alias: { help: "h" }, @@ -233,32 +233,6 @@ global.store = new Store({ name: "electron-config" }); global.appQuitting = false; -const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [ - (input, platform) => platform !== 'darwin' && input.alt && input.key.toUpperCase() === 'F4', - (input, platform) => platform !== 'darwin' && input.control && input.key.toUpperCase() === 'Q', - (input, platform) => platform === 'darwin' && input.meta && input.key.toUpperCase() === 'Q', -]; - -const warnBeforeExit = (event: Event, input: Input): void => { - const shouldWarnBeforeExit = global.store.get('warnBeforeExit', true); - const exitShortcutPressed = - input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform)); - - if (shouldWarnBeforeExit && exitShortcutPressed) { - const shouldCancelCloseRequest = dialog.showMessageBoxSync(global.mainWindow, { - type: "question", - buttons: [_t("Cancel"), _t("Close Element")], - message: _t("Are you sure you want to quit?"), - defaultId: 1, - cancelId: 0, - }) === 0; - - if (shouldCancelCloseRequest) { - event.preventDefault(); - } - } -}; - // handle uncaught errors otherwise it displays // stack traces in popup dialogs, which is terrible (which // it will do any time the auto update poke fails, and there's @@ -345,59 +319,6 @@ app.on('ready', async () => { console.log(e); } } - - protocol.registerFileProtocol('vector', (request, callback) => { - if (request.method !== 'GET') { - callback({ error: -322 }); // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h - return null; - } - - const parsedUrl = new URL(request.url); - if (parsedUrl.protocol !== 'vector:') { - callback({ error: -302 }); // UNKNOWN_URL_SCHEME - return; - } - if (parsedUrl.host !== 'vector') { - callback({ error: -105 }); // NAME_NOT_RESOLVED - return; - } - - const target = parsedUrl.pathname.split('/'); - - // path starts with a '/' - if (target[0] !== '') { - callback({ error: -6 }); // FILE_NOT_FOUND - return; - } - - if (target[target.length - 1] == '') { - target[target.length - 1] = 'index.html'; - } - - let baseDir: string; - if (target[1] === 'webapp') { - baseDir = asarPath; - } else { - callback({ error: -6 }); // FILE_NOT_FOUND - return; - } - - // Normalise the base dir and the target path separately, then make sure - // the target path isn't trying to back out beyond its root - baseDir = path.normalize(baseDir); - - const relTarget = path.normalize(path.join(...target.slice(2))); - if (relTarget.startsWith('..')) { - callback({ error: -6 }); // FILE_NOT_FOUND - return; - } - const absTarget = path.join(baseDir, relTarget); - - callback({ - path: absTarget, - }); - }); - if (argv['no-update']) { console.log('Auto update disabled via command line flag "--no-update"'); } else if (global.vectorConfig['update_base_url']) { @@ -420,7 +341,6 @@ app.on('ready', async () => { icon: iconPath, show: false, - autoHideMenuBar: global.store.get('autoHideMenuBar', true), x: mainWindowState.x, y: mainWindowState.y, @@ -434,16 +354,23 @@ app.on('ready', async () => { webgl: true, }, }); - global.mainWindow.loadURL('vector://vector/webapp/'); + global.mainWindow.setMenuBarVisibility(false); + + // load all instances stored in the store + loadInstances(global.store, asarPath, global.mainWindow); - // Handle spellchecker - // For some reason spellCheckerEnabled isn't persisted, so we have to use the store here - global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true)); + // create instance menu view + global.menuView = createMenuView(global.mainWindow); + + // manually resize tiles on window resize + global.mainWindow.on('resize', resize); + global.mainWindow.on('show', resize); + global.mainWindow.on('restore', resize); // Create trayIcon icon if (global.store.get('minimizeToTray', true)) tray.create(global.trayConfig); - global.mainWindow.once('ready-to-show', () => { + global.currentView.webContents.once('did-finish-load', () => { mainWindowState.manage(global.mainWindow); if (!argv['hidden']) { @@ -454,8 +381,6 @@ app.on('ready', async () => { } }); - global.mainWindow.webContents.on('before-input-event', warnBeforeExit); - global.mainWindow.on('closed', () => { global.mainWindow = null; }); @@ -482,16 +407,14 @@ app.on('ready', async () => { if (process.platform === 'win32') { // Handle forward/backward mouse buttons in Windows global.mainWindow.on('app-command', (e, cmd) => { - if (cmd === 'browser-backward' && global.mainWindow.webContents.canGoBack()) { - global.mainWindow.webContents.goBack(); - } else if (cmd === 'browser-forward' && global.mainWindow.webContents.canGoForward()) { - global.mainWindow.webContents.goForward(); + if (cmd === 'browser-backward' && global.currentView.webContents.canGoBack()) { + global.currentView.webContents.goBack(); + } else if (cmd === 'browser-forward' && global.currentView.webContents.canGoForward()) { + global.currentView.webContents.goForward(); } }); } - webContentsHandler(global.mainWindow.webContents); - global.appLocalization = new AppLocalization({ store: global.store, components: [ diff --git a/src/instances.ts b/src/instances.ts new file mode 100644 index 000000000..5b5f421a9 --- /dev/null +++ b/src/instances.ts @@ -0,0 +1,279 @@ +import { BrowserView, Event, Input, dialog, session, Session, ipcMain, BrowserWindow } from "electron"; +import * as path from 'path'; +import webContentsHandler from './webcontents-handler'; +import { _t } from './language-helper'; +import Store from 'electron-store'; +import { resize } from "./resizer"; +import { emitInstanceUpdate } from "./menu-view"; +import { Seshat } from "matrix-seshat"; + +/** + * The format stored in electron-config.json + */ +interface InstanceStoreData { + key: string; + matrixId: string; + avatar: string|null; + // isLegacyDefaultSession?: boolean; +} + +let lastAsarPath: string; + +/** + * All Element instances added to Electron + */ +const instances: ElementInstance[] = []; + +const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [ + (input, platform) => platform !== 'darwin' && input.alt && input.key.toUpperCase() === 'F4', + (input, platform) => platform !== 'darwin' && input.control && input.key.toUpperCase() === 'Q', + (input, platform) => platform === 'darwin' && input.meta && input.key.toUpperCase() === 'Q', +]; + +const warnBeforeExit = (event: Event, input: Input): void => { + const shouldWarnBeforeExit = global.store.get('warnBeforeExit', true); + const exitShortcutPressed = + input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform)); + + if (shouldWarnBeforeExit && exitShortcutPressed) { + const shouldCancelCloseRequest = dialog.showMessageBoxSync(global.mainWindow, { + type: "question", + buttons: [_t("Cancel"), _t("Close Element")], + message: _t("Are you sure you want to quit?"), + defaultId: 1, + cancelId: 0, + }) === 0; + + if (shouldCancelCloseRequest) { + event.preventDefault(); + } + } +}; + +const handleFileProtocolRequest = (asarPath: string) => ( + request: Electron.ProtocolRequest, + callback: (response: Electron.ProtocolResponse) => void, +) => { + if (request.method !== 'GET') { + callback({ error: -322 }); // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h + return null; + } + + const parsedUrl = new URL(request.url); + if (parsedUrl.protocol !== 'vector:') { + callback({ error: -302 }); // UNKNOWN_URL_SCHEME + return; + } + if (parsedUrl.host !== 'vector') { + callback({ error: -105 }); // NAME_NOT_RESOLVED + return; + } + + const target = parsedUrl.pathname.split('/'); + + // path starts with a '/' + if (target[0] !== '') { + callback({ error: -6 }); // FILE_NOT_FOUND + return; + } + + if (target[target.length - 1] == '') { + target[target.length - 1] = 'index.html'; + } + + let baseDir: string; + if (target[1] === 'webapp') { + baseDir = asarPath; + } else { + callback({ error: -6 }); // FILE_NOT_FOUND + return; + } + + // Normalise the base dir and the target path separately, then make sure + // the target path isn't trying to back out beyond its root + baseDir = path.normalize(baseDir); + + const relTarget = path.normalize(path.join(...target.slice(2))); + if (relTarget.startsWith('..')) { + callback({ error: -6 }); // FILE_NOT_FOUND + return; + } + const absTarget = path.join(baseDir, relTarget); + + callback({ + path: absTarget, + }); +}; + +export class ElementInstance { + key: string; + matrixId: string|null; + avatar: string|null; + view: BrowserView; + session: Session; + eventIndex: Seshat = null; + badgeCount = 0; + isLoaded = false; + constructor(key: string, matrixId: string, avatar: string, asarPath: string) { + this.key = key; + this.matrixId = matrixId; + this.avatar = avatar; + this.session = session.fromPartition('persist:'+key); + this.session.protocol.registerFileProtocol('vector', handleFileProtocolRequest(asarPath)); + + const preloadScript = path.normalize(`${__dirname}/preload.js`); + this.view = new BrowserView({ + webPreferences: { + preload: preloadScript, + nodeIntegration: false, + //sandbox: true, // We enable sandboxing from app.enableSandbox() above + contextIsolation: true, + webgl: true, + session: this.session, + }, + }); + + this.view.webContents.loadURL('vector://vector/webapp/'); + + // Handle spellchecker + // For some reason spellCheckerEnabled isn't persisted, so we have to use the store here + this.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true)); + + this.view.webContents.on('before-input-event', warnBeforeExit); + webContentsHandler(this.view.webContents); + // this.view.webContents.openDevTools({mode: 'detach'}) + + this.view.webContents.once('did-finish-load', () => { + this.isLoaded = true; + + // periodically pull current loggedin account + // TODO: get notified somehow instead of pulling + setInterval(( ) => { + this.updateAccountData(); + }, 3000); + }); + } + private async updateAccountData() { + let updated = false; + const isLoggedIn = await this.view.webContents.executeJavaScript('!!window.mxMatrixClientPeg.get()'); + if (isLoggedIn) { + const matrixId = await this.view.webContents.executeJavaScript( + 'window.mxMatrixClientPeg.get()?.getUserId() || null', + ); + if (this.matrixId !== matrixId) { + this.matrixId = matrixId || null; + updated = true; + } + + // get avatar as base64 + // TODO: is there a cleaner way (without triggering any requests)? + const avatar = await this.view.webContents.executeJavaScript(` + (function () { + let img = document.querySelector('img.mx_UserMenu_userAvatar_BaseAvatar') + if(!img) return null + img.setAttribute('crossOrigin', 'anonymous'); + let c = document.createElement('canvas'); + c.height = img.naturalHeight; + c.width = img.naturalWidth; + c.getContext('2d').drawImage(img, 0, 0, c.width, c.height); + return c.toDataURL() + })() + `); + if (this.avatar !== avatar && avatar !== 'data:,') { + this.avatar = avatar || null; + updated = true; + } + } else { + if (this.matrixId) updated = true; + this.matrixId = null; + this.avatar = null; + } + + if (updated) { + emitInstanceUpdate(); + const store = global.store as Store; + const storedInstances = store.get('instances') as InstanceStoreData[]; + const i = storedInstances.find(i => i.key === this.key); + i.matrixId = this.matrixId; + i.avatar = this.avatar; + store.set('instances', storedInstances); + } + } +} +/** + * creates a new instance and stores it in the electron-config.json + * called by the instance menu + */ +export function createInstance(key: string) { + createInstanceView(key, null, null, lastAsarPath, global.mainWindow); + selectInstance(key); + emitInstanceUpdate(); + const store = global.store as Store; + const storedInstances = store.get('instances') as InstanceStoreData[]; + storedInstances.push({ + key, + matrixId: null, + avatar: null, + }); + store.set('instances', storedInstances); +} + +/** + * creates a new ElementInstance containing an electron BrowserView + */ +export function createInstanceView( + key: string, matrixId: string, avatar: string, asarPath: string, window: BrowserWindow, +) { + // remeber for later use + lastAsarPath = asarPath; + + const ins = new ElementInstance(key, matrixId, avatar, asarPath); + instances.push(ins); + window.addBrowserView(ins.view); +} + +/** + * loads all instances stored in the electron-config.json + */ +export function loadInstances(store: Store, asarPath: string, window: BrowserWindow) { + if (!store.has('instances')) { + // TODO: find out, whether default session was already used + // -> start with partition directly or reuse the existing default session + store.set('instances', [ + { + key: 'test', + matrixId: '@test:livingutopia.org', + avatar: null, + // isLegacyDefaultSession: true, + }, + ]); + } + + const instanceList = store.get('instances') as InstanceStoreData[]; + if (!instanceList) { + console.log('no instance found'); + // TODO: automatically create a new instance + return; + } + const activeInstance = store.get('activeInstance', instanceList[0].key) as string|undefined; + for (const ins of instanceList) { + createInstanceView(ins.key, ins.matrixId, ins.avatar, asarPath, window); + } + selectInstance(activeInstance); + ipcMain.emit('instance-update'); +} + +export function selectInstance(key: string) { + const ins = instances.find(i => i.key === key); + if (!ins) { + console.error(`Instance '${key}' does not exist`); + return; + } + (global.mainWindow as BrowserWindow).setTopBrowserView(ins.view); + global.currentView = ins.view; + resize(); +} + +export function getInstances() { + return instances; +} \ No newline at end of file diff --git a/src/ipc.ts b/src/ipc.ts index 717a70172..dadec7303 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -27,6 +27,9 @@ ipcMain.on('setBadgeCount', function(_ev: IpcMainEvent, count: number): void { // only set badgeCount on Mac/Linux, the docs say that only those platforms support it but turns out Electron // has some Windows support too, and in some Windows environments this leads to two badges rendering atop // each other. See https://github.com/vector-im/element-web/issues/16942 + + // TODO: sum badge counts + // TODO: update instance selector badge app.badgeCount = count; } if (count === 0) { @@ -65,7 +68,7 @@ ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) { } }); -ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) { +ipcMain.on('ipcCall', async function(ev: IpcMainEvent, payload) { if (!global.mainWindow) return; const args = payload.args || []; @@ -106,19 +109,19 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) { ret = global.vectorConfig; break; case 'navigateBack': - if (global.mainWindow.webContents.canGoBack()) { - global.mainWindow.webContents.goBack(); + if (ev.sender.canGoBack()) { + ev.sender.goBack(); } break; case 'navigateForward': - if (global.mainWindow.webContents.canGoForward()) { - global.mainWindow.webContents.goForward(); + if (ev.sender.canGoForward()) { + ev.sender.goForward(); } break; case 'setSpellCheckEnabled': if (typeof args[0] !== 'boolean') return; - global.mainWindow.webContents.session.setSpellCheckerEnabled(args[0]); + ev.sender.session.setSpellCheckerEnabled(args[0]); global.store.set("spellCheckerEnabled", args[0]); break; @@ -128,17 +131,17 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) { case 'setSpellCheckLanguages': try { - global.mainWindow.webContents.session.setSpellCheckerLanguages(args[0]); + ev.sender.session.setSpellCheckerLanguages(args[0]); } catch (er) { console.log("There were problems setting the spellcheck languages", er); } break; case 'getSpellCheckLanguages': - ret = global.mainWindow.webContents.session.getSpellCheckerLanguages(); + ret = ev.sender.session.getSpellCheckerLanguages(); break; case 'getAvailableSpellCheckLanguages': - ret = global.mainWindow.webContents.session.availableSpellCheckerLanguages; + ret = ev.sender.session.availableSpellCheckerLanguages; break; case 'startSSOFlow': @@ -187,14 +190,14 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) { break; default: - global.mainWindow.webContents.send('ipcReply', { + ev.sender.send('ipcReply', { id: payload.id, error: "Unknown IPC Call: " + payload.name, }); return; } - global.mainWindow.webContents.send('ipcReply', { + ev.sender.send('ipcReply', { id: payload.id, reply: ret, }); diff --git a/src/menu-view.ts b/src/menu-view.ts new file mode 100644 index 000000000..2c9eb2ed8 --- /dev/null +++ b/src/menu-view.ts @@ -0,0 +1,51 @@ +import { BrowserView, BrowserWindow, ipcMain } from "electron"; +import * as path from 'path'; +import { selectInstance, getInstances, createInstance } from "./instances"; + +let menu: BrowserView; + +ipcMain.on('select-instance', (_ev, key) => { + selectInstance(key); +}); + +ipcMain.handle('get-instances', () => { + return getInstances().map(ins => ({ + key: ins.key, + matrixId: ins.matrixId, + avatar: ins.avatar, + })); +}); + +ipcMain.on('create-instance', () => { + console.log('create-instance'); + + // TODO: find better way to name instances + // is it possible to rename session partitions to matrixId later once loggedin? + const key = 'instance'+Math.random(); + createInstance(key); +}); + +/** + * let menu know, that something in the list of instances has changed + */ +export function emitInstanceUpdate() { + menu.webContents.send('instance-update'); +} + +export function createMenuView(window: BrowserWindow): BrowserView { + menu = new BrowserView({ + webPreferences: { + preload: path.normalize(`${__dirname}/menu/preload.js`), + }, + }); + menu.setBounds({ + x: 0, + y: 0, + height: 800, + width: 50, + }); + menu.webContents.loadFile('./src/menu/index.html'); + menu.webContents.openDevTools({ mode: 'detach' }); + window.addBrowserView(menu); + return menu; +} diff --git a/src/menu/index.html b/src/menu/index.html new file mode 100644 index 000000000..8b04c31a0 --- /dev/null +++ b/src/menu/index.html @@ -0,0 +1,109 @@ + + + + + + + + + + +
+ + + + + + + + + \ No newline at end of file diff --git a/src/menu/preload.ts b/src/menu/preload.ts new file mode 100644 index 000000000..d27951f94 --- /dev/null +++ b/src/menu/preload.ts @@ -0,0 +1,8 @@ +import { contextBridge, ipcRenderer } from 'electron'; + +contextBridge.exposeInMainWorld('electronAPI', { + getInstances: () => ipcRenderer.invoke('get-instances'), + onInstanceUpdate: (callback) => ipcRenderer.on('instance-update', callback), + selectInstance: (id: string) => ipcRenderer.send('select-instance', id), + createInstance: () => ipcRenderer.send('create-instance'), +}); diff --git a/src/protocol.ts b/src/protocol.ts index 7e3bd8b00..6554380a8 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -49,7 +49,8 @@ function processUrl(url: string): void { urlToLoad.hash = parsed.hash; console.log("Opening URL: ", urlToLoad.href); - global.mainWindow.loadURL(urlToLoad.href); + // TODO: ask for instance to open with + global.currentView.loadURL(urlToLoad.href); } function readStore(): object { diff --git a/src/resizer.ts b/src/resizer.ts new file mode 100644 index 000000000..782d5619f --- /dev/null +++ b/src/resizer.ts @@ -0,0 +1,19 @@ + +// TODO: can electron tile it in that way automatically? +export function resize() { + setTimeout( () => { + const contentBounds = global.mainWindow.getContentBounds(); + global.currentView.setBounds({ + x: 50, + y: 0, + width: contentBounds.width-50, + height: contentBounds.height, + }); + global.menuView.setBounds({ + x: 0, + y: 0, + width: 50, + height: contentBounds.height, + }); + }); +} \ No newline at end of file diff --git a/src/seshat.ts b/src/seshat.ts index 49d48d395..de71eca68 100644 --- a/src/seshat.ts +++ b/src/seshat.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { app, ipcMain } from "electron"; +import { ipcMain } from "electron"; import { promises as afs } from "fs"; import path from "path"; @@ -26,6 +26,7 @@ import type { import IpcMainEvent = Electron.IpcMainEvent; import { randomArray } from "./utils"; import { keytar } from "./keytar"; +import { getInstances } from "./instances"; let seshatSupported = false; let Seshat: typeof SeshatType; @@ -47,10 +48,6 @@ try { } } -const eventStorePath = path.join(app.getPath('userData'), 'EventStore'); - -let eventIndex: SeshatType = null; - const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE"; async function getOrCreatePassphrase(key: string): Promise { if (keytar) { @@ -78,15 +75,16 @@ const deleteContents = async (p: string): Promise => { } }; -ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { - if (!global.mainWindow) return; +ipcMain.on('seshat', async function(ev: IpcMainEvent, payload): Promise { + const instance = getInstances().find(i => i.session === ev.sender.session); + if (!instance) return; + const eventStorePath = path.join(instance.session.getStoragePath(), 'EventStore'); const sendError = (id, e) => { const error = { message: e.message, }; - - global.mainWindow.webContents.send('seshatReply', { + ev.sender.send('seshatReply', { id: id, error: error, }); @@ -101,7 +99,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'initEventIndex': - if (eventIndex === null) { + if (instance.eventIndex === null) { const userId = args[0]; const deviceId = args[1]; const passphraseKey = `seshat|${userId}|${deviceId}`; @@ -110,7 +108,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { try { await afs.mkdir(eventStorePath, { recursive: true }); - eventIndex = new Seshat(eventStorePath, { passphrase }); + instance.eventIndex = new Seshat(eventStorePath, { passphrase }); } catch (e) { if (e instanceof ReindexError) { // If this is a reindex error, the index schema @@ -136,7 +134,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { await recoveryIndex.reindex(); } - eventIndex = new Seshat(eventStorePath, { passphrase }); + instance.eventIndex = new Seshat(eventStorePath, { passphrase }); } else { sendError(payload.id, e); return; @@ -146,9 +144,9 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'closeEventIndex': - if (eventIndex !== null) { - const index = eventIndex; - eventIndex = null; + if (instance.eventIndex !== null) { + const index = instance.eventIndex; + instance.eventIndex = null; try { await index.shutdown(); @@ -169,18 +167,18 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { } case 'isEventIndexEmpty': - if (eventIndex === null) ret = true; - else ret = await eventIndex.isEmpty(); + if (instance.eventIndex === null) ret = true; + else ret = await instance.eventIndex.isEmpty(); break; case 'isRoomIndexed': - if (eventIndex === null) ret = false; - else ret = await eventIndex.isRoomIndexed(args[0]); + if (instance.eventIndex === null) ret = false; + else ret = await instance.eventIndex.isRoomIndexed(args[0]); break; case 'addEventToIndex': try { - eventIndex.addEvent(args[0], args[1]); + instance.eventIndex.addEvent(args[0], args[1]); } catch (e) { sendError(payload.id, e); return; @@ -189,7 +187,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { case 'deleteEvent': try { - ret = await eventIndex.deleteEvent(args[0]); + ret = await instance.eventIndex.deleteEvent(args[0]); } catch (e) { sendError(payload.id, e); return; @@ -198,7 +196,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { case 'commitLiveEvents': try { - ret = await eventIndex.commit(); + ret = await instance.eventIndex.commit(); } catch (e) { sendError(payload.id, e); return; @@ -207,7 +205,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { case 'searchEventIndex': try { - ret = await eventIndex.search(args[0]); + ret = await instance.eventIndex.search(args[0]); } catch (e) { sendError(payload.id, e); return; @@ -215,10 +213,10 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'addHistoricEvents': - if (eventIndex === null) ret = false; + if (instance.eventIndex === null) ret = false; else { try { - ret = await eventIndex.addHistoricEvents( + ret = await instance.eventIndex.addHistoricEvents( args[0], args[1], args[2]); } catch (e) { sendError(payload.id, e); @@ -228,10 +226,10 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'getStats': - if (eventIndex === null) ret = 0; + if (instance.eventIndex === null) ret = 0; else { try { - ret = await eventIndex.getStats(); + ret = await instance.eventIndex.getStats(); } catch (e) { sendError(payload.id, e); return; @@ -240,10 +238,10 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'removeCrawlerCheckpoint': - if (eventIndex === null) ret = false; + if (instance.eventIndex === null) ret = false; else { try { - ret = await eventIndex.removeCrawlerCheckpoint(args[0]); + ret = await instance.eventIndex.removeCrawlerCheckpoint(args[0]); } catch (e) { sendError(payload.id, e); return; @@ -252,10 +250,10 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'addCrawlerCheckpoint': - if (eventIndex === null) ret = false; + if (instance.eventIndex === null) ret = false; else { try { - ret = await eventIndex.addCrawlerCheckpoint(args[0]); + ret = await instance.eventIndex.addCrawlerCheckpoint(args[0]); } catch (e) { sendError(payload.id, e); return; @@ -264,10 +262,10 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'loadFileEvents': - if (eventIndex === null) ret = []; + if (instance.eventIndex === null) ret = []; else { try { - ret = await eventIndex.loadFileEvents(args[0]); + ret = await instance.eventIndex.loadFileEvents(args[0]); } catch (e) { sendError(payload.id, e); return; @@ -276,10 +274,10 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'loadCheckpoints': - if (eventIndex === null) ret = []; + if (instance.eventIndex === null) ret = []; else { try { - ret = await eventIndex.loadCheckpoints(); + ret = await instance.eventIndex.loadCheckpoints(); } catch (e) { ret = []; } @@ -287,10 +285,10 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'setUserVersion': - if (eventIndex === null) break; + if (instance.eventIndex === null) break; else { try { - await eventIndex.setUserVersion(args[0]); + await instance.eventIndex.setUserVersion(args[0]); } catch (e) { sendError(payload.id, e); return; @@ -299,10 +297,10 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; case 'getUserVersion': - if (eventIndex === null) ret = 0; + if (instance.eventIndex === null) ret = 0; else { try { - ret = await eventIndex.getUserVersion(); + ret = await instance.eventIndex.getUserVersion(); } catch (e) { sendError(payload.id, e); return; @@ -311,14 +309,14 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { break; default: - global.mainWindow.webContents.send('seshatReply', { + ev.sender.send('seshatReply', { id: payload.id, error: "Unknown IPC Call: " + payload.name, }); return; } - global.mainWindow.webContents.send('seshatReply', { + ev.sender.send('seshatReply', { id: payload.id, reply: ret, }); diff --git a/src/settings.ts b/src/settings.ts index 497f03fa2..ace184cd9 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -48,8 +48,8 @@ export const Settings: Record = { }, async write(value: any): Promise { global.store.set('autoHideMenuBar', !value); - global.mainWindow.autoHideMenuBar = !value; - global.mainWindow.setMenuBarVisibility(value); + // global.mainWindow.autoHideMenuBar = !value; + // global.mainWindow.setMenuBarVisibility(value); }, }, "Electron.showTrayIcon": { // not supported on macOS diff --git a/src/tray.ts b/src/tray.ts index 6d95d3052..6136a5dc2 100644 --- a/src/tray.ts +++ b/src/tray.ts @@ -61,40 +61,40 @@ export function create(config: IConfig): void { trayIcon.on('click', toggleWin); let lastFavicon = null; - global.mainWindow.webContents.on('page-favicon-updated', async function(ev, favicons) { - if (!favicons || favicons.length <= 0 || !favicons[0].startsWith('data:')) { - if (lastFavicon !== null) { - global.mainWindow.setIcon(defaultIcon); - trayIcon.setImage(defaultIcon); - lastFavicon = null; - } - return; - } - - // No need to change, shortcut - if (favicons[0] === lastFavicon) return; - lastFavicon = favicons[0]; - - let newFavicon = nativeImage.createFromDataURL(favicons[0]); - - // Windows likes ico's too much. - if (process.platform === 'win32') { - try { - const icoPath = path.join(app.getPath('temp'), 'win32_element_icon.ico'); - fs.writeFileSync(icoPath, await pngToIco(newFavicon.toPNG())); - newFavicon = nativeImage.createFromPath(icoPath); - } catch (e) { - console.error("Failed to make win32 ico", e); - } - } - - trayIcon.setImage(newFavicon); - global.mainWindow.setIcon(newFavicon); - }); - - global.mainWindow.webContents.on('page-title-updated', function(ev, title) { - trayIcon.setToolTip(title); - }); + // global.mainWindow.webContents.on('page-favicon-updated', async function(ev, favicons) { + // if (!favicons || favicons.length <= 0 || !favicons[0].startsWith('data:')) { + // if (lastFavicon !== null) { + // global.mainWindow.setIcon(defaultIcon); + // trayIcon.setImage(defaultIcon); + // lastFavicon = null; + // } + // return; + // } + + // // No need to change, shortcut + // if (favicons[0] === lastFavicon) return; + // lastFavicon = favicons[0]; + + // let newFavicon = nativeImage.createFromDataURL(favicons[0]); + + // // Windows likes ico's too much. + // if (process.platform === 'win32') { + // try { + // const icoPath = path.join(app.getPath('temp'), 'win32_element_icon.ico'); + // fs.writeFileSync(icoPath, await pngToIco(newFavicon.toPNG())); + // newFavicon = nativeImage.createFromPath(icoPath); + // } catch (e) { + // console.error("Failed to make win32 ico", e); + // } + // } + + // trayIcon.setImage(newFavicon); + // global.mainWindow.setIcon(newFavicon); + // }); + + // global.mainWindow.webContents.on('page-title-updated', function(ev, title) { + // trayIcon.setToolTip(title); + // }); } export function initApplicationMenu(): void { diff --git a/src/vectormenu.ts b/src/vectormenu.ts index 69c08ecc4..c539409c0 100644 --- a/src/vectormenu.ts +++ b/src/vectormenu.ts @@ -98,7 +98,7 @@ export function buildMenuTemplate(): Menu { // in macOS the Preferences menu item goes in the first menu ...(!isMac ? [{ label: _t('Preferences'), - click() { global.mainWindow.webContents.send('preferences'); }, + click() { global.currentView.webContents.send('preferences'); }, }] : []), { role: 'togglefullscreen', @@ -153,7 +153,7 @@ export function buildMenuTemplate(): Menu { { label: _t('Preferences') + '…', accelerator: 'Command+,', // Mac-only accelerator - click() { global.mainWindow.webContents.send('preferences'); }, + click() { global.currentView.webContents.send('preferences'); }, }, { type: 'separator' }, {