From d923de412ae20e2b74692bcaa99ab30e27679400 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 11 Apr 2024 10:57:14 -0700 Subject: [PATCH] add submenu support, add signal submenu to line context menu (#572) --- src/app/line/linecomps.tsx | 17 +++++++++++++++++ src/electron/emain.ts | 24 ++++++++++++++++-------- src/models/commandrunner.ts | 4 ++++ src/models/contextmenu.ts | 12 ++++++++++-- src/types/custom.d.ts | 7 ++++--- 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/app/line/linecomps.tsx b/src/app/line/linecomps.tsx index 6d45777d..7c88f39f 100644 --- a/src/app/line/linecomps.tsx +++ b/src/app/line/linecomps.tsx @@ -672,6 +672,7 @@ class LineCmd extends React.Component< let { line, screen } = this.props; const containerType = screen.getContainerType(); const isMainContainer = containerType == appconst.LineContainer_Main; + const cmd = screen.getCmd(line); let menu: ContextMenuItem[] = [ { role: "copy", label: "Copy", type: "normal" }, { role: "paste", label: "Paste", type: "normal" }, @@ -697,6 +698,22 @@ class LineCmd extends React.Component< click: () => GlobalCommandRunner.lineMinimize(line.lineid, true, true), }); } + if (cmd?.isRunning()) { + menu.push({ type: "separator" }); + menu.push({ + label: "Send Signal", + type: "submenu", + submenu: [ + { label: "SIGINT", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGINT", true) }, + { label: "SIGTERM", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGTERM", true) }, + { label: "SIGKILL", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGKILL", true) }, + { type: "separator" }, + { label: "SIGUSR1", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGUSR1", true) }, + { label: "SIGUSR2", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGUSR2", true) }, + ], + }); + } + menu.push({ type: "separator" }); menu.push({ label: "Restart Line", click: () => GlobalCommandRunner.lineRestart(line.lineid, true) }); menu.push({ type: "separator" }); diff --git a/src/electron/emain.ts b/src/electron/emain.ts index 805d67e0..c98fac3e 100644 --- a/src/electron/emain.ts +++ b/src/electron/emain.ts @@ -470,23 +470,31 @@ electron.ipcMain.on("toggle-developer-tools", (event) => { event.returnValue = true; }); -electron.ipcMain.on("contextmenu-show", (event, menuDefArr, { x, y }) => { - if (menuDefArr == null || menuDefArr.length == 0) { - return; - } - const menu = new electron.Menu(); +function convertMenuDefArrToMenu(menuDefArr: ElectronContextMenuItem[]): electron.Menu { + const menuItems: electron.MenuItem[] = []; for (const menuDef of menuDefArr) { - const menuItemTemplate = { - role: menuDef.role, + const menuItemTemplate: electron.MenuItemConstructorOptions = { + role: menuDef.role as any, label: menuDef.label, type: menuDef.type, click: () => { MainWindow?.webContents.send("contextmenu-click", menuDef.id); }, }; + if (menuDef.submenu != null) { + menuItemTemplate.submenu = convertMenuDefArrToMenu(menuDef.submenu); + } const menuItem = new electron.MenuItem(menuItemTemplate); - menu.append(menuItem); + menuItems.push(menuItem); } + return electron.Menu.buildFromTemplate(menuItems); +} + +electron.ipcMain.on("contextmenu-show", (event, menuDefArr: ElectronContextMenuItem[], { x, y }) => { + if (menuDefArr == null || menuDefArr.length == 0) { + return; + } + const menu = convertMenuDefArrToMenu(menuDefArr); menu.popup({ x, y }); event.returnValue = true; }); diff --git a/src/models/commandrunner.ts b/src/models/commandrunner.ts index edd63f00..05488842 100644 --- a/src/models/commandrunner.ts +++ b/src/models/commandrunner.ts @@ -86,6 +86,10 @@ class CommandRunner { return GlobalModel.submitCommand("line", "restart", [lineArg], { nohist: "1" }, interactive); } + lineSignal(lineArg: string, signal: string, interactive: boolean): Promise { + return GlobalModel.submitCommand("signal", null, [lineArg, signal], { nohist: "1" }, interactive); + } + lineSet(lineArg: string, opts: { renderer?: string }): Promise { let kwargs = { nohist: "1" }; if ("renderer" in opts) { diff --git a/src/models/contextmenu.ts b/src/models/contextmenu.ts index 50264d6f..87fab753 100644 --- a/src/models/contextmenu.ts +++ b/src/models/contextmenu.ts @@ -20,8 +20,7 @@ class ContextMenuModel { } } - showContextMenu(menu: ContextMenuItem[], position: { x: number; y: number }): void { - this.handlers.clear(); + _convertAndRegisterMenu(menu: ContextMenuItem[]): ElectronContextMenuItem[] { let electronMenuItems: ElectronContextMenuItem[] = []; for (let item of menu) { let electronItem: ElectronContextMenuItem = { @@ -33,8 +32,17 @@ class ContextMenuModel { if (item.click) { this.handlers.set(electronItem.id, item.click); } + if (item.submenu) { + electronItem.submenu = this._convertAndRegisterMenu(item.submenu); + } electronMenuItems.push(electronItem); } + return electronMenuItems; + } + + showContextMenu(menu: ContextMenuItem[], position: { x: number; y: number }): void { + this.handlers.clear(); + const electronMenuItems = this._convertAndRegisterMenu(menu); this.globalModel.getElectronApi().showContextMenu(electronMenuItems, position); } } diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index b5c0d1a6..0559fa68 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -954,15 +954,16 @@ declare global { id: string; // unique id, used for communication label: string; role?: string; // electron role (optional) - type?: "separator" | "normal"; + type?: "separator" | "normal" | "submenu"; + submenu?: ElectronContextMenuItem[]; }; - // possible to add support for submenus when needed type ContextMenuItem = { label?: string; - type?: "separator" | "normal"; + type?: "separator" | "normal" | "submenu"; role?: string; // electron role (optional) click?: () => void; // not required if role is set + submenu?: ContextMenuItem[]; }; }