@@ -212,11 +212,29 @@ const keysPressed = new Map<string, number>()
212212// allowing common modifier combos like Ctrl+C to cancel before recording begins).
213213const HOLD_TO_RECORD_DELAY_MS = 250
214214
215+ // Helper to check if a key is a modifier key
216+ const isModifierKey = ( key : string ) : boolean => {
217+ return (
218+ key === "ControlLeft" ||
219+ key === "ControlRight" ||
220+ key === "ShiftLeft" ||
221+ key === "ShiftRight" ||
222+ key === "Alt" ||
223+ key === "AltLeft" ||
224+ key === "AltRight" ||
225+ key === "MetaLeft" ||
226+ key === "MetaRight"
227+ )
228+ }
229+
215230const hasRecentKeyPress = ( ) => {
216231 if ( keysPressed . size === 0 ) return false
217232
218233 const now = Date . now ( ) / 1000
219- return [ ...keysPressed . values ( ) ] . some ( ( time ) => {
234+ return [ ...keysPressed . entries ( ) ] . some ( ( [ key , time ] ) => {
235+ // Exclude modifier keys from the check - they should not block shortcuts
236+ // that use only modifier key combinations (like toggle-ctrl-alt)
237+ if ( isModifierKey ( key ) ) return false
220238 // 10 seconds
221239 // for some weird reasons sometime KeyRelease event is missing for some keys
222240 // so they stay in the map
@@ -308,12 +326,45 @@ export function listenToKeyboardEvents() {
308326 } , HOLD_TO_RECORD_DELAY_MS )
309327 }
310328
329+ const tryToggleMcpIfEligible = ( ) => {
330+ const config = configStore . get ( )
331+ if ( config . mcpToolsShortcut !== "toggle-ctrl-alt" ) {
332+ return
333+ }
334+
335+ // Both modifiers must be down
336+ if ( ! isPressedCtrlKey || ! isPressedAltKey ) return
337+
338+ // Guard against recent non-modifier presses
339+ if ( hasRecentKeyPress ( ) ) return
340+
341+ // Cancel regular recording timer since MCP is prioritized
342+ cancelRecordingTimer ( )
343+
344+ if ( isDebugKeybinds ( ) ) {
345+ logKeybinds ( "MCP tools triggered: Ctrl+Alt (toggle mode)" )
346+ }
347+
348+ // Set state.isRecordingMcpMode BEFORE sending the message
349+ // This ensures the key release handlers know we're in MCP toggle mode
350+ // and won't prematurely close the panel when the user releases Ctrl/Alt
351+ if ( ! state . isRecording ) {
352+ // Starting MCP recording - set the flag now so key release handlers know
353+ state . isRecordingMcpMode = true
354+ }
355+ // Note: When stopping, the recordEvent handler will set isRecordingMcpMode = false
356+
357+ // Toggle MCP recording on/off
358+ getWindowRendererHandlers ( "panel" ) ?. startOrFinishMcpRecording . send ( )
359+ }
360+
311361
312362 const handleEvent = ( e : RdevEvent ) => {
313363 if ( e . event_type === "KeyPress" ) {
314364 if ( e . data . key === "ControlLeft" || e . data . key === "ControlRight" ) {
315365 isPressedCtrlKey = true
316366 tryStartMcpHoldIfEligible ( )
367+ tryToggleMcpIfEligible ( )
317368 if ( isDebugKeybinds ( ) ) {
318369 logKeybinds ( "Ctrl key pressed, isPressedCtrlKey =" , isPressedCtrlKey )
319370 }
@@ -333,6 +384,7 @@ export function listenToKeyboardEvents() {
333384 isPressedAltKey = true
334385 isPressedCtrlAltKey = isPressedCtrlKey && isPressedAltKey
335386 tryStartMcpHoldIfEligible ( )
387+ tryToggleMcpIfEligible ( )
336388 if ( isDebugKeybinds ( ) ) {
337389 logKeybinds (
338390 "Alt key pressed, isPressedAltKey =" ,
@@ -1126,31 +1178,15 @@ export function listenToKeyboardEvents() {
11261178 cancelCustomMcpTimer ( )
11271179 }
11281180
1129- // Skip built-in hold mode handling for toggle mode shortcuts
1130- if (
1131- ( currentConfig . shortcut === "ctrl-slash" ) ||
1132- ( currentConfig . shortcut === "custom" && currentConfig . customShortcutMode === "toggle" )
1133- )
1134- return
1135-
1136- cancelRecordingTimer ( )
1181+ // Always handle MCP hold-ctrl-alt key releases, regardless of recording shortcut mode
1182+ // This must happen before the toggle mode early return below
11371183 cancelMcpRecordingTimer ( )
11381184
1139- // Finish MCP hold on either modifier release
11401185 if ( e . data . key === "ControlLeft" || e . data . key === "ControlRight" ) {
11411186 if ( isHoldingCtrlAltKey ) {
11421187 const panelHandlers = getWindowRendererHandlers ( "panel" )
11431188 panelHandlers ?. finishMcpRecording . send ( )
11441189 isHoldingCtrlAltKey = false
1145- } else {
1146- if ( isHoldingCtrlKey ) {
1147- getWindowRendererHandlers ( "panel" ) ?. finishRecording . send ( )
1148- } else if ( ! state . isTextInputActive ) {
1149- // Only close panel if we're not in text input mode
1150- stopRecordingAndHidePanelWindow ( )
1151- }
1152-
1153- isHoldingCtrlKey = false
11541190 }
11551191 }
11561192
@@ -1159,8 +1195,36 @@ export function listenToKeyboardEvents() {
11591195 const panelHandlers = getWindowRendererHandlers ( "panel" )
11601196 panelHandlers ?. finishMcpRecording . send ( )
11611197 isHoldingCtrlAltKey = false
1162- } else if ( ! state . isTextInputActive ) {
1163- // Only close panel if we're not in text input mode
1198+ }
1199+ }
1200+
1201+ // Skip built-in hold mode handling for toggle mode shortcuts
1202+ // (only applies to regular recording, not MCP agent mode which is handled above)
1203+ if (
1204+ ( currentConfig . shortcut === "ctrl-slash" ) ||
1205+ ( currentConfig . shortcut === "custom" && currentConfig . customShortcutMode === "toggle" )
1206+ )
1207+ return
1208+
1209+ cancelRecordingTimer ( )
1210+
1211+ // Finish regular hold-ctrl recording on Ctrl release
1212+ if ( e . data . key === "ControlLeft" || e . data . key === "ControlRight" ) {
1213+ if ( isHoldingCtrlKey ) {
1214+ getWindowRendererHandlers ( "panel" ) ?. finishRecording . send ( )
1215+ } else if ( ! state . isTextInputActive && ! state . isRecordingMcpMode ) {
1216+ // Only close panel if we're not in text input mode and not in MCP recording mode
1217+ // (MCP toggle mode should not close panel on key release)
1218+ stopRecordingAndHidePanelWindow ( )
1219+ }
1220+ isHoldingCtrlKey = false
1221+ }
1222+
1223+ // Close panel on Alt release if not in text input mode (and not in MCP mode, which is handled above)
1224+ if ( e . data . key === "Alt" || e . data . key === "AltLeft" || e . data . key === "AltRight" ) {
1225+ if ( ! state . isTextInputActive && ! state . isRecordingMcpMode ) {
1226+ // Only close panel if we're not in text input mode and not in MCP recording mode
1227+ // (MCP toggle mode should not close panel on key release)
11641228 stopRecordingAndHidePanelWindow ( )
11651229 }
11661230 }
0 commit comments