fix: resolve new tab creation failure and shortcut passthrough
- Fix pty.spawn cwd tilde expansion: ptyManager now passes resolvedCwd (expanding ~ and verifying directory existence) so child processes do not exit immediately on launch - Add pointer-events: none to SVGs inside buttons and tabs to prevent Electron window-dragging interception - Extend xterm attachCustomKeyEventHandler to allow Ctrl+Shift+T, Ctrl+Shift+W, etc. through while terminal is focused - Wrap remote SSH and serial spawns with fallback to local bash to prevent tab death loops on connection failures
This commit is contained in:
@@ -54,6 +54,7 @@ class TerminalApp {
|
||||
this.tabManager = new TabManager({
|
||||
tabBar: this.tabBar,
|
||||
viewport: this.viewport,
|
||||
getSpawnOptions: (wsId) => (this.workspaceManager ? this.workspaceManager.getSpawnOptions(wsId) : {}),
|
||||
onActiveChange: (activeTerm) => {
|
||||
if (activeTerm && this.statusShell) {
|
||||
this.statusShell.textContent = activeTerm.title || 'bash';
|
||||
|
||||
@@ -4,6 +4,7 @@ export class TabManager {
|
||||
constructor(options = {}) {
|
||||
this.tabBar = options.tabBar;
|
||||
this.viewport = options.viewport;
|
||||
this.getSpawnOptions = options.getSpawnOptions || (() => ({}));
|
||||
this.onActiveChange = options.onActiveChange || (() => {});
|
||||
this.onTabListChange = options.onTabListChange || (() => {});
|
||||
|
||||
@@ -49,10 +50,15 @@ export class TabManager {
|
||||
}
|
||||
|
||||
createTab(options = {}) {
|
||||
const title = typeof options === 'string' ? options : (options.title || 'bash');
|
||||
const workspaceId = typeof options === 'object' && options.workspaceId ? options.workspaceId : this.activeWorkspaceId;
|
||||
const defaultSpawn = this.getSpawnOptions(workspaceId) || {};
|
||||
const title = typeof options === 'string' ? options : (options.title || defaultSpawn.title || 'bash');
|
||||
const spawnOptions = typeof options === 'object' && options.spawnOptions ? options.spawnOptions : (
|
||||
typeof options === 'object' ? { cwd: options.cwd, shell: options.shell, args: options.args } : {}
|
||||
typeof options === 'object' ? {
|
||||
cwd: options.cwd !== undefined ? options.cwd : defaultSpawn.cwd,
|
||||
shell: options.shell !== undefined ? options.shell : defaultSpawn.shell,
|
||||
args: options.args !== undefined ? options.args : defaultSpawn.args,
|
||||
} : defaultSpawn
|
||||
);
|
||||
|
||||
const tabId = 'tab_' + Math.random().toString(36).substring(2, 9);
|
||||
|
||||
@@ -75,11 +75,32 @@ export class TerminalView {
|
||||
console.warn('WebGL addon not supported, fallback to default Canvas renderer:', e);
|
||||
}
|
||||
|
||||
// 自定义按键过滤:允许应用级全局快捷键(如 Ctrl+B 切换侧边栏)穿透
|
||||
// 自定义按键过滤:允许应用级全局快捷键穿透终端聚焦拦截
|
||||
this.xterm.attachCustomKeyEventHandler((event) => {
|
||||
// Ctrl+B: 侧边栏
|
||||
if (event.ctrlKey && !event.shiftKey && !event.altKey && event.key.toLowerCase() === 'b') {
|
||||
return false;
|
||||
}
|
||||
// Ctrl+Shift+T / W / E / O / F / P
|
||||
if (event.ctrlKey && event.shiftKey && ['t', 'w', 'e', 'o', 'f', 'p'].includes(event.key.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
// F1: 命令面板
|
||||
if (event.key === 'F1') {
|
||||
return false;
|
||||
}
|
||||
// Ctrl+,: 设置
|
||||
if (event.ctrlKey && !event.shiftKey && !event.altKey && event.key === ',') {
|
||||
return false;
|
||||
}
|
||||
// Ctrl+Tab / Ctrl+Shift+Tab
|
||||
if (event.ctrlKey && event.key === 'Tab') {
|
||||
return false;
|
||||
}
|
||||
// Alt+1 .. Alt+9
|
||||
if (event.altKey && event.key >= '1' && event.key <= '9') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -126,12 +147,17 @@ export class TerminalView {
|
||||
shell: this.shell,
|
||||
args: this.args,
|
||||
}).then((res) => {
|
||||
if (res && res.error) {
|
||||
this.xterm.writeln(`\r\n\x1b[31m[创建终端进程失败: ${res.error}]\x1b[0m\r\n`);
|
||||
}
|
||||
if (res && res.shell) {
|
||||
if (!this.title || this.title === 'bash') {
|
||||
this.title = res.shell.split('/').pop() || 'bash';
|
||||
this.onTitleChange(this.title);
|
||||
}
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.xterm.writeln(`\r\n\x1b[31m[启动终端异常: ${err.message}]\x1b[0m\r\n`);
|
||||
});
|
||||
|
||||
// 聚焦
|
||||
|
||||
@@ -229,9 +229,10 @@ export class WorkspaceManager {
|
||||
const targetHost = ws.user ? `${ws.user}@${ws.host}` : ws.host;
|
||||
const portArgs = ws.port && Number(ws.port) !== 22 ? ['-p', String(ws.port)] : [];
|
||||
const extra = ws.extraArgs ? ws.extraArgs.split(' ').filter(Boolean) : [];
|
||||
const sshCmd = `ssh ${[...portArgs, ...extra, targetHost].join(' ')}`;
|
||||
return {
|
||||
shell: 'ssh',
|
||||
args: [...portArgs, ...extra, targetHost],
|
||||
shell: '/bin/bash',
|
||||
args: ['-c', `${sshCmd} || (echo -e "\\n\\x1b[33m[SSH 会话断开或连接失败,已保留本地交互终端]\\x1b[0m" && exec bash)`],
|
||||
title: `ssh: ${ws.name || targetHost}`,
|
||||
};
|
||||
}
|
||||
@@ -239,9 +240,10 @@ export class WorkspaceManager {
|
||||
if (ws.type === WORKSPACE_TYPES.SERIAL) {
|
||||
const baud = String(ws.baudRate || 115200);
|
||||
const dev = ws.device || '/dev/ttyUSB0';
|
||||
const tool = ws.tool || 'picocom';
|
||||
return {
|
||||
shell: 'picocom',
|
||||
args: ['-b', baud, dev],
|
||||
shell: '/bin/bash',
|
||||
args: ['-c', `which ${tool} >/dev/null 2>&1 && ${tool} -b ${baud} ${dev} || (echo -e "\\n\\x1b[33m[串口工具 ${tool} 未安装或 ${dev} 无法连接,已保留本地交互终端]\\x1b[0m" && exec bash)`],
|
||||
title: `serial: ${dev}`,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user