fix: intercept Ctrl+B and app shortcuts in capture phase when terminal is focused

This commit is contained in:
2026-09-24 01:38:35 +08:00
parent 35452ef235
commit ebf454a250
2 changed files with 108 additions and 86 deletions
+17 -3
View File
@@ -217,17 +217,21 @@ class TerminalApp {
} }
initKeyboardShortcuts() { initKeyboardShortcuts() {
window.addEventListener('keydown', (e) => { window.addEventListener(
'keydown',
(e) => {
// Ctrl+Shift+P 或 F1: 命令面板 // Ctrl+Shift+P 或 F1: 命令面板
if ((e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'p') || e.key === 'F1') { if ((e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'p') || e.key === 'F1') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
this.commandPalette.toggle(); this.commandPalette.toggle();
return; return;
} }
// Ctrl+B: 切换左侧工作区侧边栏 // Ctrl+B: 切换左侧工作区侧边栏
if (e.ctrlKey && e.key.toLowerCase() === 'b') { if (e.ctrlKey && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'b') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
this.workspaceManager?.toggleSidebar(); this.workspaceManager?.toggleSidebar();
return; return;
} }
@@ -235,6 +239,7 @@ class TerminalApp {
// Ctrl+Shift+T: 新建当前工作区标签页 // Ctrl+Shift+T: 新建当前工作区标签页
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 't') { if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 't') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
const activeWs = this.workspaceManager ? this.workspaceManager.getActiveWorkspace() : null; const activeWs = this.workspaceManager ? this.workspaceManager.getActiveWorkspace() : null;
const spawnOpts = this.workspaceManager ? this.workspaceManager.getSpawnOptions(activeWs?.id) : {}; const spawnOpts = this.workspaceManager ? this.workspaceManager.getSpawnOptions(activeWs?.id) : {};
this.tabManager.createTab({ this.tabManager.createTab({
@@ -250,6 +255,7 @@ class TerminalApp {
// Ctrl+Shift+W: 关闭窗格 // Ctrl+Shift+W: 关闭窗格
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'w') { if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'w') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
const tab = this.tabManager.getActiveTab(); const tab = this.tabManager.getActiveTab();
if (tab && tab.splitTree.activeTerminal) { if (tab && tab.splitTree.activeTerminal) {
tab.splitTree.closeTerminal(tab.splitTree.activeTerminal.id); tab.splitTree.closeTerminal(tab.splitTree.activeTerminal.id);
@@ -260,6 +266,7 @@ class TerminalApp {
// Ctrl+Shift+E: 水平分屏 (左右) // Ctrl+Shift+E: 水平分屏 (左右)
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'e') { if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'e') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
const tab = this.tabManager.getActiveTab(); const tab = this.tabManager.getActiveTab();
if (tab) tab.splitTree.splitCurrent('horizontal'); if (tab) tab.splitTree.splitCurrent('horizontal');
return; return;
@@ -268,6 +275,7 @@ class TerminalApp {
// Ctrl+Shift+O: 垂直分屏 (上下) // Ctrl+Shift+O: 垂直分屏 (上下)
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'o') { if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'o') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
const tab = this.tabManager.getActiveTab(); const tab = this.tabManager.getActiveTab();
if (tab) tab.splitTree.splitCurrent('vertical'); if (tab) tab.splitTree.splitCurrent('vertical');
return; return;
@@ -276,6 +284,7 @@ class TerminalApp {
// Ctrl+Shift+F: 搜索 // Ctrl+Shift+F: 搜索
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'f') { if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'f') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
this.toggleSearch(); this.toggleSearch();
return; return;
} }
@@ -283,6 +292,7 @@ class TerminalApp {
// Ctrl+,: 设置 // Ctrl+,: 设置
if (e.ctrlKey && e.key === ',') { if (e.ctrlKey && e.key === ',') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
this.settings.open(); this.settings.open();
return; return;
} }
@@ -290,6 +300,7 @@ class TerminalApp {
// Ctrl+Tab / Ctrl+Shift+Tab: 标签页切换 // Ctrl+Tab / Ctrl+Shift+Tab: 标签页切换
if (e.ctrlKey && e.key === 'Tab') { if (e.ctrlKey && e.key === 'Tab') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
if (e.shiftKey) { if (e.shiftKey) {
this.tabManager.prevTab(); this.tabManager.prevTab();
} else { } else {
@@ -301,13 +312,16 @@ class TerminalApp {
// Alt+1 .. Alt+9: 快速直达标签页 // Alt+1 .. Alt+9: 快速直达标签页
if (e.altKey && e.key >= '1' && e.key <= '9') { if (e.altKey && e.key >= '1' && e.key <= '9') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
const index = parseInt(e.key, 10) - 1; const index = parseInt(e.key, 10) - 1;
const ids = Array.from(this.tabManager.tabs.keys()); const ids = Array.from(this.tabManager.tabs.keys());
if (ids[index]) { if (ids[index]) {
this.tabManager.switchTab(ids[index]); this.tabManager.switchTab(ids[index]);
} }
} }
}); },
true // 启用捕获阶段,确保终端即使在聚焦/输入状态下应用级快捷键也能被优先拦截响应
);
} }
async initStatusBar() { async initStatusBar() {
+8
View File
@@ -75,6 +75,14 @@ export class TerminalView {
console.warn('WebGL addon not supported, fallback to default Canvas renderer:', e); console.warn('WebGL addon not supported, fallback to default Canvas renderer:', e);
} }
// 自定义按键过滤:允许应用级全局快捷键(如 Ctrl+B 切换侧边栏)穿透
this.xterm.attachCustomKeyEventHandler((event) => {
if (event.ctrlKey && !event.shiftKey && !event.altKey && event.key.toLowerCase() === 'b') {
return false;
}
return true;
});
// 监听输入 // 监听输入
this.xterm.onData((data) => { this.xterm.onData((data) => {
window.terminalAPI.writePty(this.id, data); window.terminalAPI.writePty(this.id, data);