diff --git a/tabby-core/src/services/hotkeys.service.ts b/tabby-core/src/services/hotkeys.service.ts index 605cd590..fba550a4 100644 --- a/tabby-core/src/services/hotkeys.service.ts +++ b/tabby-core/src/services/hotkeys.service.ts @@ -64,6 +64,7 @@ export class HotkeysService { private pressedKeystroke: Keystroke|null = null private lastKeystrokes: PastKeystroke[] = [] private shouldSaveNextKeystroke = true + private lastEventTimestamp = 0 private constructor ( private zone: NgZone, @@ -75,12 +76,10 @@ export class HotkeysService { events.forEach(eventType => { document.addEventListener(eventType, (nativeEvent: KeyboardEvent) => { this._keyEvent.next(nativeEvent) - if (eventType === 'keyup' || document.querySelectorAll('input:focus').length === 0) { - this.pushKeyEvent(eventType, nativeEvent) - if (hostApp.platform === Platform.Web && this.matchActiveHotkey(true) !== null) { - nativeEvent.preventDefault() - nativeEvent.stopPropagation() - } + this.pushKeyEvent(eventType, nativeEvent) + if (hostApp.platform === Platform.Web && this.matchActiveHotkey(true) !== null) { + nativeEvent.preventDefault() + nativeEvent.stopPropagation() } }) }) @@ -103,6 +102,10 @@ export class HotkeysService { * @param nativeEvent event object */ pushKeyEvent (eventName: string, nativeEvent: KeyboardEvent): void { + if (nativeEvent.timeStamp === this.lastEventTimestamp) { + return + } + nativeEvent['event'] = eventName const eventData = { @@ -119,15 +122,13 @@ export class HotkeysService { for (const [key, time] of this.pressedKeyTimestamps.entries()) { if (time < performance.now() - 5000) { - this.pressedKeys.delete(key) - this.pressedKeyTimestamps.delete(key) + this.removePressedKey(key) } } const keyName = getKeyName(eventData) if (eventName === 'keydown') { - this.pressedKeys.add(keyName) - this.pressedKeyTimestamps.set(keyName, eventData.registrationTime) + this.addPressedKey(eventData) this.shouldSaveNextKeystroke = true this.updateModifiers(eventData) } @@ -141,8 +142,7 @@ export class HotkeysService { }) this.shouldSaveNextKeystroke = false } - this.pressedKeys.delete(keyName) - this.pressedKeyTimestamps.delete(keyName) + this.removePressedKey(keyName) } if (this.pressedKeys.size) { @@ -151,25 +151,25 @@ export class HotkeysService { this.pressedKeystroke = null } - this.processKeystrokes() + const matched = this.matchActiveHotkey() + this.zone.run(() => { + if (matched) { + this.emitHotkeyOn(matched) + } else if (this.pressedHotkey) { + this.emitHotkeyOff(this.pressedHotkey) + } + }) this.zone.run(() => { this._key.next(getKeyName(eventData)) }) - } - private updateModifiers (event: KeyEventData) { - for (const [prop, key] of Object.entries({ - ctrlKey: 'Ctrl', - metaKey: metaKeyName, - altKey: altKeyName, - shiftKey: 'Shift', - })) { - if (!event[prop] && this.pressedKeys.has(key)) { - this.pressedKeys.delete(key) - this.pressedKeyTimestamps.delete(key) - } + if (process.platform === 'darwin' && eventData.metaKey && eventName === 'keydown' && !['Ctrl', 'Shift', altKeyName, metaKeyName].includes(keyName)) { + // macOS will swallow non-modified keyups if Cmd is held down + this.pushKeyEvent('keyup', nativeEvent) } + + this.lastEventTimestamp = nativeEvent.timeStamp } getCurrentKeystrokes (): Keystroke[] { @@ -262,24 +262,31 @@ export class HotkeysService { ).reduce((a, b) => a.concat(b)) } - private processKeystrokes () { - const matched = this.matchActiveHotkey() - this.zone.run(() => { - if (matched) { - this.emitHotkeyOn(matched) - } else if (this.pressedHotkey) { - this.emitHotkeyOff(this.pressedHotkey) + private updateModifiers (event: KeyEventData) { + for (const [prop, key] of Object.entries({ + ctrlKey: 'Ctrl', + metaKey: metaKeyName, + altKey: altKeyName, + shiftKey: 'Shift', + })) { + if (!event[prop] && this.pressedKeys.has(key)) { + this.removePressedKey(key) } - }) + } } private emitHotkeyOn (hotkey: string) { if (this.pressedHotkey) { + if (this.pressedHotkey === hotkey) { + return + } this.emitHotkeyOff(this.pressedHotkey) } - console.debug('Matched hotkey', hotkey) - this._hotkey.next(hotkey) - this.pressedHotkey = hotkey + if (document.querySelectorAll('input:focus').length === 0) { + console.debug('Matched hotkey', hotkey) + this._hotkey.next(hotkey) + this.pressedHotkey = hotkey + } } private emitHotkeyOff (hotkey: string) { @@ -316,4 +323,15 @@ export class HotkeysService { } return keys } + + private addPressedKey (eventData: KeyEventData) { + const keyName = getKeyName(eventData) + this.pressedKeys.add(keyName) + this.pressedKeyTimestamps.set(keyName, eventData.registrationTime) + } + + private removePressedKey (key: KeyName) { + this.pressedKeys.delete(key) + this.pressedKeyTimestamps.delete(key) + } }