mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-12 20:31:52 +00:00
@@ -25,7 +25,7 @@ export { DockingService, Screen } from '../services/docking.service'
|
|||||||
export { Logger, ConsoleLogger, LogService } from '../services/log.service'
|
export { Logger, ConsoleLogger, LogService } from '../services/log.service'
|
||||||
export { HomeBaseService } from '../services/homeBase.service'
|
export { HomeBaseService } from '../services/homeBase.service'
|
||||||
export { HotkeysService } from '../services/hotkeys.service'
|
export { HotkeysService } from '../services/hotkeys.service'
|
||||||
export { KeyEventData, KeySequenceItem } from '../services/hotkeys.util'
|
export { KeyEventData, KeyName, Keystroke } from '../services/hotkeys.util'
|
||||||
export { NotificationsService } from '../services/notifications.service'
|
export { NotificationsService } from '../services/notifications.service'
|
||||||
export { ThemesService } from '../services/themes.service'
|
export { ThemesService } from '../services/themes.service'
|
||||||
export { ProfilesService } from '../services/profiles.service'
|
export { ProfilesService } from '../services/profiles.service'
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
|
import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
|
||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
||||||
import { stringifyKeySequence, KeyEventData, KeySequenceItem } from './hotkeys.util'
|
import { KeyEventData, getKeyName, Keystroke, KeyName, getKeystrokeName, metaKeyName, altKeyName } from './hotkeys.util'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
import { HostAppService, Platform } from '../api/hostApp'
|
import { HostAppService, Platform } from '../api/hostApp'
|
||||||
import { deprecate } from 'util'
|
import { deprecate } from 'util'
|
||||||
@@ -12,14 +12,17 @@ export interface PartialHotkeyMatch {
|
|||||||
matchedLength: number
|
matchedLength: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const KEY_TIMEOUT = 2000
|
interface PastKeystroke {
|
||||||
|
keystroke: Keystroke
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class HotkeysService {
|
export class HotkeysService {
|
||||||
|
/** @hidden @deprecated */
|
||||||
key = new EventEmitter<KeyboardEvent>()
|
key = new EventEmitter<KeyboardEvent>()
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden @deprecated */
|
||||||
matchedHotkey = new EventEmitter<string>()
|
matchedHotkey = new EventEmitter<string>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,19 +36,34 @@ export class HotkeysService {
|
|||||||
get hotkeyOff$ (): Observable<string> { return this._hotkeyOff }
|
get hotkeyOff$ (): Observable<string> { return this._hotkeyOff }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired for each recognized hotkey
|
* Fired for each singular key
|
||||||
*/
|
*/
|
||||||
get key$ (): Observable<KeyboardEvent> { return this._key }
|
get key$ (): Observable<KeyName> { return this._key }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired for each key event
|
||||||
|
*/
|
||||||
|
get keyEvent$ (): Observable<KeyboardEvent> { return this._keyEvent }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired for each singular key combination
|
||||||
|
*/
|
||||||
|
get keystroke$ (): Observable<Keystroke> { return this._keystroke }
|
||||||
|
|
||||||
private _hotkey = new Subject<string>()
|
private _hotkey = new Subject<string>()
|
||||||
private _hotkeyOff = new Subject<string>()
|
private _hotkeyOff = new Subject<string>()
|
||||||
private _key = new Subject<KeyboardEvent>()
|
private _keyEvent = new Subject<KeyboardEvent>()
|
||||||
private currentEvents: KeyEventData[] = []
|
private _key = new Subject<KeyName>()
|
||||||
|
private _keystroke = new Subject<Keystroke>()
|
||||||
private disabledLevel = 0
|
private disabledLevel = 0
|
||||||
private hotkeyDescriptions: HotkeyDescription[] = []
|
private hotkeyDescriptions: HotkeyDescription[] = []
|
||||||
|
|
||||||
|
private pressedKeys = new Set<KeyName>()
|
||||||
|
private pressedKeyTimestamps = new Map<KeyName, number>()
|
||||||
private pressedHotkey: string|null = null
|
private pressedHotkey: string|null = null
|
||||||
private lastMatchedHotkeyStartTime = performance.now()
|
private pressedKeystroke: Keystroke|null = null
|
||||||
private lastMatchedHotkeyEndTime = performance.now()
|
private lastKeystrokes: PastKeystroke[] = []
|
||||||
|
private shouldSaveNextKeystroke = true
|
||||||
|
|
||||||
private constructor (
|
private constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
@@ -56,9 +74,10 @@ export class HotkeysService {
|
|||||||
const events = ['keydown', 'keyup']
|
const events = ['keydown', 'keyup']
|
||||||
events.forEach(eventType => {
|
events.forEach(eventType => {
|
||||||
document.addEventListener(eventType, (nativeEvent: KeyboardEvent) => {
|
document.addEventListener(eventType, (nativeEvent: KeyboardEvent) => {
|
||||||
|
this._keyEvent.next(nativeEvent)
|
||||||
if (eventType === 'keyup' || document.querySelectorAll('input:focus').length === 0) {
|
if (eventType === 'keyup' || document.querySelectorAll('input:focus').length === 0) {
|
||||||
this.pushKeystroke(eventType, nativeEvent)
|
this.pushKeyEvent(eventType, nativeEvent)
|
||||||
if (hostApp.platform === Platform.Web) {
|
if (hostApp.platform === Platform.Web && this.matchActiveHotkey(true) !== null) {
|
||||||
nativeEvent.preventDefault()
|
nativeEvent.preventDefault()
|
||||||
nativeEvent.stopPropagation()
|
nativeEvent.stopPropagation()
|
||||||
}
|
}
|
||||||
@@ -73,144 +92,149 @@ export class HotkeysService {
|
|||||||
// deprecated
|
// deprecated
|
||||||
this.hotkey$.subscribe(h => this.matchedHotkey.emit(h))
|
this.hotkey$.subscribe(h => this.matchedHotkey.emit(h))
|
||||||
this.matchedHotkey.subscribe = deprecate(s => this.hotkey$.subscribe(s), 'matchedHotkey is deprecated, use hotkey$')
|
this.matchedHotkey.subscribe = deprecate(s => this.hotkey$.subscribe(s), 'matchedHotkey is deprecated, use hotkey$')
|
||||||
|
this.keyEvent$.subscribe(h => this.key.next(h))
|
||||||
this.key$.subscribe(e => this.key.emit(e))
|
this.key.subscribe = deprecate(s => this.keyEvent$.subscribe(s), 'key is deprecated, use keyEvent$')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new key event to the buffer
|
* Adds a new key event to the buffer
|
||||||
*
|
*
|
||||||
* @param name DOM event name
|
* @param eventName DOM event name
|
||||||
* @param nativeEvent event object
|
* @param nativeEvent event object
|
||||||
*/
|
*/
|
||||||
pushKeystroke (name: string, nativeEvent: KeyboardEvent): void {
|
pushKeyEvent (eventName: string, nativeEvent: KeyboardEvent): void {
|
||||||
nativeEvent['event'] = name
|
nativeEvent['event'] = eventName
|
||||||
if (nativeEvent.timeStamp && this.currentEvents.find(x => x.time === nativeEvent.timeStamp)) {
|
|
||||||
return
|
const eventData = {
|
||||||
}
|
|
||||||
this.currentEvents.push({
|
|
||||||
ctrlKey: nativeEvent.ctrlKey,
|
ctrlKey: nativeEvent.ctrlKey,
|
||||||
metaKey: nativeEvent.metaKey,
|
metaKey: nativeEvent.metaKey,
|
||||||
altKey: nativeEvent.altKey,
|
altKey: nativeEvent.altKey,
|
||||||
shiftKey: nativeEvent.shiftKey,
|
shiftKey: nativeEvent.shiftKey,
|
||||||
code: nativeEvent.code,
|
code: nativeEvent.code,
|
||||||
key: nativeEvent.key,
|
key: nativeEvent.key,
|
||||||
eventName: name,
|
eventName,
|
||||||
time: nativeEvent.timeStamp,
|
time: nativeEvent.timeStamp,
|
||||||
registrationTime: performance.now(),
|
registrationTime: performance.now(),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
for (const [key, time] of this.pressedKeyTimestamps.entries()) {
|
||||||
|
if (time < performance.now() - 5000) {
|
||||||
|
this.pressedKeys.delete(key)
|
||||||
|
this.pressedKeyTimestamps.delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyName = getKeyName(eventData)
|
||||||
|
if (eventName === 'keydown') {
|
||||||
|
this.pressedKeys.add(keyName)
|
||||||
|
this.pressedKeyTimestamps.set(keyName, eventData.registrationTime)
|
||||||
|
this.shouldSaveNextKeystroke = true
|
||||||
|
this.updateModifiers(eventData)
|
||||||
|
}
|
||||||
|
if (eventName === 'keyup') {
|
||||||
|
const keystroke = getKeystrokeName([...this.pressedKeys])
|
||||||
|
if (this.shouldSaveNextKeystroke) {
|
||||||
|
this._keystroke.next(keystroke)
|
||||||
|
this.lastKeystrokes.push({
|
||||||
|
keystroke,
|
||||||
|
time: performance.now(),
|
||||||
|
})
|
||||||
|
this.shouldSaveNextKeystroke = false
|
||||||
|
}
|
||||||
|
this.pressedKeys.delete(keyName)
|
||||||
|
this.pressedKeyTimestamps.delete(keyName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pressedKeys.size) {
|
||||||
|
this.pressedKeystroke = getKeystrokeName([...this.pressedKeys])
|
||||||
|
} else {
|
||||||
|
this.pressedKeystroke = null
|
||||||
|
}
|
||||||
|
|
||||||
this.processKeystrokes()
|
this.processKeystrokes()
|
||||||
this.emitKeyEvent(nativeEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the buffer for new complete keystrokes
|
|
||||||
*/
|
|
||||||
processKeystrokes (): void {
|
|
||||||
if (this.isEnabled()) {
|
|
||||||
this.zone.run(() => {
|
|
||||||
let fullMatches: {
|
|
||||||
id: string,
|
|
||||||
sequence: string[],
|
|
||||||
startTime: number,
|
|
||||||
endTime: number,
|
|
||||||
}[] = []
|
|
||||||
|
|
||||||
const currentSequence = this.getCurrentKeySequence()
|
|
||||||
const config = this.getHotkeysConfig()
|
|
||||||
for (const id in config) {
|
|
||||||
for (const sequence of config[id]) {
|
|
||||||
if (currentSequence.length < sequence.length) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (sequence.every(
|
|
||||||
(x: string, index: number) =>
|
|
||||||
x.toLowerCase() ===
|
|
||||||
currentSequence[currentSequence.length - sequence.length + index].value.toLowerCase()
|
|
||||||
)) {
|
|
||||||
fullMatches.push({
|
|
||||||
id: id,
|
|
||||||
sequence,
|
|
||||||
startTime: currentSequence[currentSequence.length - sequence.length].firstEvent.registrationTime,
|
|
||||||
endTime: currentSequence[currentSequence.length - 1].lastEvent.registrationTime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fullMatches.sort((a, b) => b.startTime - a.startTime + (b.sequence.length - a.sequence.length))
|
|
||||||
fullMatches = fullMatches.filter(x => x.startTime >= this.lastMatchedHotkeyStartTime)
|
|
||||||
fullMatches = fullMatches.filter(x => x.endTime > this.lastMatchedHotkeyEndTime)
|
|
||||||
|
|
||||||
const matched = fullMatches[0]?.id
|
|
||||||
if (matched) {
|
|
||||||
this.emitHotkeyOn(matched)
|
|
||||||
this.lastMatchedHotkeyStartTime = fullMatches[0].startTime
|
|
||||||
this.lastMatchedHotkeyEndTime = fullMatches[0].endTime
|
|
||||||
} else if (this.pressedHotkey) {
|
|
||||||
this.emitHotkeyOff(this.pressedHotkey)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitHotkeyOn (hotkey: string) {
|
|
||||||
if (this.pressedHotkey) {
|
|
||||||
this.emitHotkeyOff(this.pressedHotkey)
|
|
||||||
}
|
|
||||||
console.debug('Matched hotkey', hotkey)
|
|
||||||
this._hotkey.next(hotkey)
|
|
||||||
this.pressedHotkey = hotkey
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitHotkeyOff (hotkey: string) {
|
|
||||||
console.debug('Unmatched hotkey', hotkey)
|
|
||||||
this._hotkeyOff.next(hotkey)
|
|
||||||
this.pressedHotkey = null
|
|
||||||
}
|
|
||||||
|
|
||||||
emitKeyEvent (nativeEvent: KeyboardEvent): void {
|
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
this._key.next(nativeEvent)
|
this._key.next(getKeyName(eventData))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCurrentKeystrokes (): void {
|
private updateModifiers (event: KeyEventData) {
|
||||||
this.currentEvents = []
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentKeySequence (): KeySequenceItem[] {
|
getCurrentKeystrokes (): Keystroke[] {
|
||||||
this.currentEvents = this.currentEvents.filter(x => performance.now() - x.time < KEY_TIMEOUT && x.registrationTime >= this.lastMatchedHotkeyStartTime)
|
if (!this.pressedKeystroke) {
|
||||||
return stringifyKeySequence(this.currentEvents)
|
return []
|
||||||
|
}
|
||||||
|
return [...this.lastKeystrokes.map(x => x.keystroke), this.pressedKeystroke]
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentFullyMatchedHotkey (): string|null {
|
matchActiveHotkey (partial = false): string|null {
|
||||||
return this.pressedHotkey
|
if (!this.isEnabled() || !this.pressedKeystroke) {
|
||||||
}
|
return null
|
||||||
|
}
|
||||||
|
const matches: {
|
||||||
|
id: string,
|
||||||
|
sequence: string[],
|
||||||
|
}[] = []
|
||||||
|
|
||||||
|
const currentSequence = this.getCurrentKeystrokes()
|
||||||
|
|
||||||
getCurrentPartiallyMatchedHotkeys (): PartialHotkeyMatch[] {
|
|
||||||
const currentStrokes = this.getCurrentKeySequence().map(x => x.value)
|
|
||||||
const config = this.getHotkeysConfig()
|
const config = this.getHotkeysConfig()
|
||||||
const result: PartialHotkeyMatch[] = []
|
|
||||||
for (const id in config) {
|
for (const id in config) {
|
||||||
for (const sequence of config[id]) {
|
for (const sequence of config[id]) {
|
||||||
for (let matchLength = Math.min(currentStrokes.length, sequence.length); matchLength > 0; matchLength--) {
|
if (currentSequence.length < sequence.length) {
|
||||||
if (sequence.slice(0, matchLength).every(
|
continue
|
||||||
(x: string, index: number) =>
|
}
|
||||||
x.toLowerCase() ===
|
if (sequence[sequence.length - 1] !== this.pressedKeystroke) {
|
||||||
currentStrokes[currentStrokes.length - matchLength + index].toLowerCase()
|
continue
|
||||||
)) {
|
}
|
||||||
result.push({
|
|
||||||
matchedLength: matchLength,
|
let lastIndex = 0
|
||||||
id,
|
let matched = true
|
||||||
strokes: sequence,
|
for (const item of sequence) {
|
||||||
})
|
const nextOffset = currentSequence.slice(lastIndex).findIndex(
|
||||||
|
x => x.toLowerCase() === item.toLowerCase()
|
||||||
|
)
|
||||||
|
if (nextOffset === -1) {
|
||||||
|
matched = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
lastIndex += nextOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partial ? lastIndex > 0 : matched) {
|
||||||
|
matches.push({
|
||||||
|
id,
|
||||||
|
sequence,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
|
matches.sort((a, b) => b.sequence.length - a.sequence.length)
|
||||||
|
if (!matches.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return matches[0].id
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCurrentKeystrokes (): void {
|
||||||
|
this.lastKeystrokes = []
|
||||||
|
this.pressedKeys.clear()
|
||||||
|
this.pressedKeyTimestamps.clear()
|
||||||
|
this.pressedKeystroke = null
|
||||||
|
this.pressedHotkey = null
|
||||||
}
|
}
|
||||||
|
|
||||||
getHotkeyDescription (id: string): HotkeyDescription {
|
getHotkeyDescription (id: string): HotkeyDescription {
|
||||||
@@ -238,6 +262,32 @@ export class HotkeysService {
|
|||||||
).reduce((a, b) => a.concat(b))
|
).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 emitHotkeyOn (hotkey: string) {
|
||||||
|
if (this.pressedHotkey) {
|
||||||
|
this.emitHotkeyOff(this.pressedHotkey)
|
||||||
|
}
|
||||||
|
console.debug('Matched hotkey', hotkey)
|
||||||
|
this._hotkey.next(hotkey)
|
||||||
|
this.pressedHotkey = hotkey
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitHotkeyOff (hotkey: string) {
|
||||||
|
console.debug('Unmatched hotkey', hotkey)
|
||||||
|
this._hotkeyOff.next(hotkey)
|
||||||
|
this.pressedHotkey = null
|
||||||
|
}
|
||||||
|
|
||||||
private getHotkeysConfig () {
|
private getHotkeysConfig () {
|
||||||
return this.getHotkeysConfigRecursive(this.config.store.hotkeys)
|
return this.getHotkeysConfigRecursive(this.config.store.hotkeys)
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-type-alias */
|
||||||
export const metaKeyName = {
|
export const metaKeyName = {
|
||||||
darwin: '⌘',
|
darwin: '⌘',
|
||||||
win32: 'Win',
|
win32: 'Win',
|
||||||
@@ -24,88 +25,53 @@ export interface KeyEventData {
|
|||||||
|
|
||||||
const REGEX_LATIN_KEYNAME = /^[A-Za-z]$/
|
const REGEX_LATIN_KEYNAME = /^[A-Za-z]$/
|
||||||
|
|
||||||
export interface KeySequenceItem {
|
export type KeyName = string
|
||||||
value: string
|
export type Keystroke = string
|
||||||
firstEvent: KeyEventData
|
|
||||||
lastEvent: KeyEventData
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringifyKeySequence (events: KeyEventData[]): KeySequenceItem[] {
|
export function getKeyName (event: KeyEventData): KeyName {
|
||||||
const items: KeySequenceItem[] = []
|
// eslint-disable-next-line @typescript-eslint/init-declarations
|
||||||
let pressedKeys: KeySequenceItem[] = []
|
let key: string
|
||||||
events = events.slice()
|
if (event.key === 'Control') {
|
||||||
|
key = 'Ctrl'
|
||||||
const strictOrdering = ['Ctrl', metaKeyName, altKeyName, 'Shift']
|
} else if (event.key === 'Meta') {
|
||||||
|
key = metaKeyName
|
||||||
function flushPressedKeys () {
|
} else if (event.key === 'Alt') {
|
||||||
if (pressedKeys.length) {
|
key = altKeyName
|
||||||
const v = {
|
} else if (event.key === 'Shift') {
|
||||||
firstEvent: pressedKeys[0].firstEvent,
|
key = 'Shift'
|
||||||
lastEvent: pressedKeys[pressedKeys.length - 1].lastEvent,
|
} else {
|
||||||
}
|
key = event.code
|
||||||
pressedKeys = [
|
if (REGEX_LATIN_KEYNAME.test(event.key)) {
|
||||||
...strictOrdering.map(x => pressedKeys.find(p => p.value === x)).filter(x => !!x) as KeySequenceItem[],
|
// Handle Dvorak etc via the reported "character" instead of the scancode
|
||||||
...pressedKeys.filter(p => !strictOrdering.includes(p.value)),
|
key = event.key.toUpperCase()
|
||||||
]
|
|
||||||
items.push({
|
|
||||||
value: pressedKeys.map(x => x.value).join('-'),
|
|
||||||
...v,
|
|
||||||
})
|
|
||||||
pressedKeys = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (events.length > 0) {
|
|
||||||
const event = events.shift()!
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/init-declarations
|
|
||||||
let key: string
|
|
||||||
if (event.key === 'Control') {
|
|
||||||
key = 'Ctrl'
|
|
||||||
} else if (event.key === 'Meta') {
|
|
||||||
key = metaKeyName
|
|
||||||
} else if (event.key === 'Alt') {
|
|
||||||
key = altKeyName
|
|
||||||
} else if (event.key === 'Shift') {
|
|
||||||
key = 'Shift'
|
|
||||||
} else {
|
} else {
|
||||||
key = event.code
|
key = key.replace('Key', '')
|
||||||
if (REGEX_LATIN_KEYNAME.test(event.key)) {
|
key = key.replace('Arrow', '')
|
||||||
// Handle Dvorak etc via the reported "character" instead of the scancode
|
key = key.replace('Digit', '')
|
||||||
key = event.key.toUpperCase()
|
key = {
|
||||||
} else {
|
Comma: ',',
|
||||||
key = key.replace('Key', '')
|
Period: '.',
|
||||||
key = key.replace('Arrow', '')
|
Slash: '/',
|
||||||
key = key.replace('Digit', '')
|
Backslash: '\\',
|
||||||
key = {
|
IntlBackslash: '`',
|
||||||
Comma: ',',
|
Backquote: '~', // Electron says it's the tilde
|
||||||
Period: '.',
|
Minus: '-',
|
||||||
Slash: '/',
|
Equal: '=',
|
||||||
Backslash: '\\',
|
Semicolon: ';',
|
||||||
IntlBackslash: '`',
|
Quote: '\'',
|
||||||
Backquote: '~', // Electron says it's the tilde
|
BracketLeft: '[',
|
||||||
Minus: '-',
|
BracketRight: ']',
|
||||||
Equal: '=',
|
}[key] ?? key
|
||||||
Semicolon: ';',
|
|
||||||
Quote: '\'',
|
|
||||||
BracketLeft: '[',
|
|
||||||
BracketRight: ']',
|
|
||||||
}[key] ?? key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.eventName === 'keydown') {
|
|
||||||
pressedKeys.push({
|
|
||||||
value: key,
|
|
||||||
firstEvent: event,
|
|
||||||
lastEvent: event,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (event.eventName === 'keyup') {
|
|
||||||
flushPressedKeys()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return key
|
||||||
flushPressedKeys()
|
}
|
||||||
return items
|
|
||||||
|
export function getKeystrokeName (keys: KeyName[]): Keystroke {
|
||||||
|
const strictOrdering: KeyName[] = ['Ctrl', metaKeyName, altKeyName, 'Shift']
|
||||||
|
keys = [
|
||||||
|
...strictOrdering.map(x => keys.find(k => k === x)).filter(x => !!x) as KeyName[],
|
||||||
|
...keys.filter(k => !strictOrdering.includes(k)),
|
||||||
|
]
|
||||||
|
return keys.join('-')
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { trigger, transition, style, animate } from '@angular/animations'
|
import { trigger, transition, style, animate } from '@angular/animations'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { HotkeysService, BaseComponent } from 'tabby-core'
|
import { HotkeysService, BaseComponent, Keystroke } from 'tabby-core'
|
||||||
|
|
||||||
const INPUT_TIMEOUT = 1000
|
const INPUT_TIMEOUT = 1000
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ const INPUT_TIMEOUT = 1000
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class HotkeyInputModalComponent extends BaseComponent {
|
export class HotkeyInputModalComponent extends BaseComponent {
|
||||||
@Input() value: string[] = []
|
@Input() value: Keystroke[] = []
|
||||||
@Input() timeoutProgress = 0
|
@Input() timeoutProgress = 0
|
||||||
|
|
||||||
private lastKeyEvent: number|null = null
|
private lastKeyEvent: number|null = null
|
||||||
@@ -48,9 +48,9 @@ export class HotkeyInputModalComponent extends BaseComponent {
|
|||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.hotkeys.clearCurrentKeystrokes()
|
this.hotkeys.clearCurrentKeystrokes()
|
||||||
this.subscribeUntilDestroyed(hotkeys.key, (event) => {
|
this.subscribeUntilDestroyed(hotkeys.keystroke$, (keystroke) => {
|
||||||
this.lastKeyEvent = performance.now()
|
this.lastKeyEvent = performance.now()
|
||||||
this.value = this.hotkeys.getCurrentKeySequence().map(x => x.value)
|
this.value.push(keystroke)
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
})
|
})
|
||||||
|
@@ -85,9 +85,9 @@ export class XTermFrontend extends Frontend {
|
|||||||
this.xterm.unicode.activeVersion = '11'
|
this.xterm.unicode.activeVersion = '11'
|
||||||
|
|
||||||
const keyboardEventHandler = (name: string, event: KeyboardEvent) => {
|
const keyboardEventHandler = (name: string, event: KeyboardEvent) => {
|
||||||
this.hotkeysService.pushKeystroke(name, event)
|
this.hotkeysService.pushKeyEvent(name, event)
|
||||||
let ret = true
|
let ret = true
|
||||||
if (this.hotkeysService.getCurrentPartiallyMatchedHotkeys().length !== 0) {
|
if (this.hotkeysService.matchActiveHotkey(true) !== null) {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
ret = false
|
ret = false
|
||||||
|
@@ -100,15 +100,13 @@ export default class TerminalModule { // eslint-disable-line @typescript-eslint/
|
|||||||
events.forEach(event => {
|
events.forEach(event => {
|
||||||
const oldHandler = hterm.hterm.Keyboard.prototype[event.htermHandler]
|
const oldHandler = hterm.hterm.Keyboard.prototype[event.htermHandler]
|
||||||
hterm.hterm.Keyboard.prototype[event.htermHandler] = function (nativeEvent) {
|
hterm.hterm.Keyboard.prototype[event.htermHandler] = function (nativeEvent) {
|
||||||
hotkeys.pushKeystroke(event.name, nativeEvent)
|
hotkeys.pushKeyEvent(event.name, nativeEvent)
|
||||||
if (hotkeys.getCurrentPartiallyMatchedHotkeys().length === 0) {
|
if (hotkeys.matchActiveHotkey(true) !== null) {
|
||||||
oldHandler.bind(this)(nativeEvent)
|
oldHandler.bind(this)(nativeEvent)
|
||||||
} else {
|
} else {
|
||||||
nativeEvent.stopPropagation()
|
nativeEvent.stopPropagation()
|
||||||
nativeEvent.preventDefault()
|
nativeEvent.preventDefault()
|
||||||
}
|
}
|
||||||
hotkeys.processKeystrokes()
|
|
||||||
hotkeys.emitKeyEvent(nativeEvent)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user