diff --git a/emain/emain-events.ts b/emain/emain-events.ts new file mode 100644 index 000000000..08d13cd4f --- /dev/null +++ b/emain/emain-events.ts @@ -0,0 +1,30 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { EventEmitter } from "events"; + +interface GlobalEvents { + "windows-updated": () => void; // emitted whenever a window is opened/closed +} + +class GlobalEventEmitter extends EventEmitter { + emit(event: K, ...args: Parameters): boolean { + return super.emit(event, ...args); + } + + on(event: K, listener: GlobalEvents[K]): this { + return super.on(event, listener); + } + + once(event: K, listener: GlobalEvents[K]): this { + return super.once(event, listener); + } + + off(event: K, listener: GlobalEvents[K]): this { + return super.off(event, listener); + } +} + +const globalEvents = new GlobalEventEmitter(); + +export { globalEvents }; diff --git a/emain/emain-window.ts b/emain/emain-window.ts index 4f95313e1..f3121e5e5 100644 --- a/emain/emain-window.ts +++ b/emain/emain-window.ts @@ -5,6 +5,7 @@ import { ClientService, ObjectService, WindowService, WorkspaceService } from "@ import { RpcApi } from "@/app/store/wshclientapi"; import { fireAndForget } from "@/util/util"; import { BaseWindow, BaseWindowConstructorOptions, dialog, globalShortcut, ipcMain, screen } from "electron"; +import { globalEvents } from "emain/emain-events"; import path from "path"; import { debounce } from "throttle-debounce"; import { @@ -293,6 +294,7 @@ export class WaveBrowserWindow extends BaseWindow { console.log("win quitting or updating", this.waveWindowId); return; } + setTimeout(() => globalEvents.emit("windows-updated"), 50); waveWindowMap.delete(this.waveWindowId); if (focusedWaveWindow == this) { focusedWaveWindow = null; @@ -309,6 +311,7 @@ export class WaveBrowserWindow extends BaseWindow { } }); waveWindowMap.set(waveWindow.oid, this); + setTimeout(() => globalEvents.emit("windows-updated"), 50); } private removeAllChildViews() { diff --git a/emain/emain.ts b/emain/emain.ts index 90425ebb9..41ea190c6 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -3,6 +3,7 @@ import { RpcApi } from "@/app/store/wshclientapi"; import * as electron from "electron"; +import { globalEvents } from "emain/emain-events"; import { FastAverageColor } from "fast-average-color"; import fs from "fs"; import * as child_process from "node:child_process"; @@ -572,6 +573,17 @@ process.on("uncaughtException", (error) => { electronApp.quit(); }); +let lastWaveWindowCount = 0; +globalEvents.on("windows-updated", () => { + const wwCount = getAllWaveWindows().length; + if (wwCount == lastWaveWindowCount) { + return; + } + lastWaveWindowCount = wwCount; + console.log("windows-updated", wwCount); + makeAppMenu(); +}); + async function appMain() { // Set disableHardwareAcceleration as early as possible, if required. const launchSettings = getLaunchSettings(); diff --git a/emain/menu.ts b/emain/menu.ts index 3fdfbc274..2a3964eb5 100644 --- a/emain/menu.ts +++ b/emain/menu.ts @@ -10,6 +10,7 @@ import { createNewWaveWindow, createWorkspace, focusedWaveWindow, + getAllWaveWindows, getWaveWindowByWorkspaceId, relaunchBrowserWindows, WaveBrowserWindow, @@ -67,7 +68,11 @@ async function getWorkspaceMenu(ww?: WaveBrowserWindow): Promise { +async function getAppMenu( + numWaveWindows: number, + callbacks: AppMenuCallbacks, + workspaceId?: string +): Promise { const ww = workspaceId && getWaveWindowByWorkspaceId(workspaceId); const fileMenu: Electron.MenuItemConstructorOptions[] = [ { @@ -83,6 +88,22 @@ async function getAppMenu(callbacks: AppMenuCallbacks, workspaceId?: string): Pr }, }, ]; + if (numWaveWindows == 0) { + fileMenu.push({ + label: "New Window (hidden-1)", + accelerator: unamePlatform === "darwin" ? "Command+N" : "Alt+N", + acceleratorWorksWhenHidden: true, + visible: false, + click: () => fireAndForget(callbacks.createNewWaveWindow), + }); + fileMenu.push({ + label: "New Window (hidden-2)", + accelerator: unamePlatform === "darwin" ? "Command+T" : "Alt+T", + acceleratorWorksWhenHidden: true, + visible: false, + click: () => fireAndForget(callbacks.createNewWaveWindow), + }); + } const appMenu: Electron.MenuItemConstructorOptions[] = [ { label: "About Wave Terminal", @@ -299,8 +320,9 @@ async function getAppMenu(callbacks: AppMenuCallbacks, workspaceId?: string): Pr return electron.Menu.buildFromTemplate(menuTemplate); } -export function instantiateAppMenu(workspaceId?: string): Promise { +export function instantiateAppMenu(numWindows: number, workspaceId?: string): Promise { return getAppMenu( + numWindows, { createNewWaveWindow, relaunchBrowserWindows, @@ -311,7 +333,8 @@ export function instantiateAppMenu(workspaceId?: string): Promise export function makeAppMenu() { fireAndForget(async () => { - const menu = await instantiateAppMenu(); + const wwCount = getAllWaveWindows().length; + const menu = await instantiateAppMenu(wwCount); electron.Menu.setApplicationMenu(menu); }); } @@ -351,10 +374,11 @@ electron.ipcMain.on("contextmenu-show", (event, workspaceId: string, menuDefArr? if (menuDefArr?.length === 0) { return; } + const wwCount = getAllWaveWindows().length; fireAndForget(async () => { const menu = menuDefArr ? convertMenuDefArrToMenu(workspaceId, menuDefArr) - : await instantiateAppMenu(workspaceId); + : await instantiateAppMenu(wwCount, workspaceId); menu.popup(); }); event.returnValue = true;