feat: add configurable VDO.Ninja overview and floating window focus

This commit is contained in:
2026-09-22 17:34:35 +09:00
parent 33aac68f16
commit 36806e4dc1
14 changed files with 304 additions and 16 deletions
+24 -4
View File
@@ -1,5 +1,6 @@
import { app, BrowserWindow, dialog, Menu, globalShortcut, ipcMain, screen, shell } from 'electron'
import { checkDockerStatus } from './dockerStatus'
import { resolveContainerTerminal } from './containerTerminal'
import { yahboomDevicePreflight, yahboomModuleCases, yahboomRuntimeImage, yahboomRuntimeArtifacts, yahboomSimulation } from './yahboomRuntime'
import { visualizationProgress, reportVisualizationProgress } from './visualizationProgress'
import { moduleProgress, reportModuleProgress, createModuleProgressParser } from './moduleProgress'
@@ -5964,17 +5965,31 @@ function terminalEnvironment(cols: number, rows: number): Record<string, string>
}
function attachTerminalProcess(sender: WebContents, sessionId: string, terminalKey: string, proc: IPty): void {
intentionalTerminalStops.delete(terminalKey)
terminalProcesses.set(terminalKey, proc)
proc.onData((data) => {
if (!sender.isDestroyed()) sender.send('terminal-data', sessionId, data)
})
proc.onExit(({ exitCode }) => {
if (terminalProcesses.get(terminalKey) !== proc) return
terminalProcesses.delete(terminalKey)
if (intentionalTerminalStops.delete(terminalKey)) return
if (!sender.isDestroyed()) sender.send('terminal-data', sessionId, `\r\n[terminal exited: ${exitCode}]\r\n`)
})
}
async function containerTerminalCommand(moduleRoot: string, moduleName: string | undefined, cols: number, rows: number, target?: ReturnType<typeof normalizeRemoteTarget>): Promise<string> {
const info = parseModuleDockerInfo(basename(moduleRoot))
if (!info || !isAllowedContainerName(info.containerKey)) throw new Error('Invalid module container name')
const output = target
? await runSshOutput(target, `docker inspect ${quoteShellSingle(info.containerKey)}`, 10000)
: await runCommandOutput('docker', ['inspect', info.containerKey], undefined, undefined, 10000)
const container = resolveContainerTerminal(output, moduleRoot)
const shell = buildWorkspaceShellCommand(moduleName, container.cwd, cols, rows, false)
const init = 'if [ -n "$ROS_DISTRO" ] && [ -f "/opt/ros/$ROS_DISTRO/setup.bash" ]; then source "/opt/ros/$ROS_DISTRO/setup.bash"; fi; if [ -f ./install/setup.bash ]; then source ./install/setup.bash; fi; exec ' + shell
return `exec docker exec -it -e TERM=xterm-256color -w ${quoteShellSingle(container.cwd)} ${quoteShellSingle(container.id)} /bin/bash --noprofile --norc -c ${quoteShellSingle(init)}`
}
function registerTerminalHandlers(): void {
ipcMain.handle('terminal-start', async (event, moduleName?: string, cols?: number, rows?: number, sessionId = 'default', workspaceRoot?: string | null, folderName?: string | null) => {
const webContentsId = event.sender.id
@@ -6006,7 +6021,7 @@ function registerTerminalHandlers(): void {
event.sender.once('destroyed', () => stopTerminalsForWebContents(webContentsId))
return true
})
ipcMain.handle('local-workspace:terminal:start', async (event, moduleName?: string, cols?: number, rows?: number, sessionId = 'default', workspaceRoot?: string | null, folderName?: string | null) => {
ipcMain.handle('local-workspace:terminal:start', async (event, moduleName?: string, cols?: number, rows?: number, sessionId = 'default', workspaceRoot?: string | null, folderName?: string | null, mode: 'host' | 'container' = 'host') => {
const webContentsId = event.sender.id
const terminalKey = getTerminalKey(webContentsId, sessionId)
cancelStopTerminal(terminalKey)
@@ -6024,7 +6039,10 @@ function registerTerminalHandlers(): void {
const safeCols = Math.max(20, Math.min(300, Math.floor(cols ?? 80)))
const safeRows = Math.max(5, Math.min(120, Math.floor(rows ?? 24)))
const proc = pty.spawn('/bin/bash', ['--noprofile', '--norc', '-c', buildWorkspaceShellCommand(moduleName, terminalCwd, safeCols, safeRows, false)], {
const command = mode === 'container'
? await containerTerminalCommand(terminalCwd, moduleName, safeCols, safeRows)
: buildWorkspaceShellCommand(moduleName, terminalCwd, safeCols, safeRows, false)
const proc = pty.spawn('/bin/bash', ['--noprofile', '--norc', '-c', command], {
cwd: terminalCwd,
name: 'xterm-256color',
cols: safeCols,
@@ -6036,7 +6054,7 @@ function registerTerminalHandlers(): void {
event.sender.once('destroyed', () => stopTerminalsForWebContents(webContentsId))
return true
})
ipcMain.handle('remote-workspace:terminal:start', async (event, targetInput: RemoteWorkspaceTarget, moduleName?: string, cols?: number, rows?: number, sessionId = 'default', folderName?: string | null) => {
ipcMain.handle('remote-workspace:terminal:start', async (event, targetInput: RemoteWorkspaceTarget, moduleName?: string, cols?: number, rows?: number, sessionId = 'default', folderName?: string | null, mode: 'host' | 'container' = 'host') => {
const webContentsId = event.sender.id
const terminalKey = getTerminalKey(webContentsId, sessionId)
cancelStopTerminal(terminalKey)
@@ -6068,7 +6086,9 @@ function registerTerminalHandlers(): void {
'-i',
target.identityFile,
`${target.user}@${target.host}`,
buildWorkspaceShellCommand(moduleName, resolved.moduleRoot, safeCols, safeRows, true)
mode === 'container'
? await containerTerminalCommand(resolved.moduleRoot, moduleName, safeCols, safeRows, target)
: buildWorkspaceShellCommand(moduleName, resolved.moduleRoot, safeCols, safeRows, true)
], {
name: 'xterm-256color',
cols: safeCols,