282 lines
6.0 KiB
Go
282 lines
6.0 KiB
Go
package input
|
|
|
|
import (
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/charmbracelet/x/ansi"
|
|
)
|
|
|
|
// KittyKeyboardEvent represents Kitty keyboard progressive enhancement flags.
|
|
type KittyKeyboardEvent int
|
|
|
|
// IsDisambiguateEscapeCodes returns true if the DisambiguateEscapeCodes flag is set.
|
|
func (e KittyKeyboardEvent) IsDisambiguateEscapeCodes() bool {
|
|
return e&ansi.KittyDisambiguateEscapeCodes != 0
|
|
}
|
|
|
|
// IsReportEventTypes returns true if the ReportEventTypes flag is set.
|
|
func (e KittyKeyboardEvent) IsReportEventTypes() bool {
|
|
return e&ansi.KittyReportEventTypes != 0
|
|
}
|
|
|
|
// IsReportAlternateKeys returns true if the ReportAlternateKeys flag is set.
|
|
func (e KittyKeyboardEvent) IsReportAlternateKeys() bool {
|
|
return e&ansi.KittyReportAlternateKeys != 0
|
|
}
|
|
|
|
// IsReportAllKeys returns true if the ReportAllKeys flag is set.
|
|
func (e KittyKeyboardEvent) IsReportAllKeys() bool {
|
|
return e&ansi.KittyReportAllKeys != 0
|
|
}
|
|
|
|
// IsReportAssociatedKeys returns true if the ReportAssociatedKeys flag is set.
|
|
func (e KittyKeyboardEvent) IsReportAssociatedKeys() bool {
|
|
return e&ansi.KittyReportAssociatedKeys != 0
|
|
}
|
|
|
|
// Kitty Clipboard Control Sequences
|
|
var kittyKeyMap = map[int]KeySym{
|
|
ansi.BS: KeyBackspace,
|
|
ansi.HT: KeyTab,
|
|
ansi.CR: KeyEnter,
|
|
ansi.ESC: KeyEscape,
|
|
ansi.DEL: KeyBackspace,
|
|
|
|
57344: KeyEscape,
|
|
57345: KeyEnter,
|
|
57346: KeyTab,
|
|
57347: KeyBackspace,
|
|
57348: KeyInsert,
|
|
57349: KeyDelete,
|
|
57350: KeyLeft,
|
|
57351: KeyRight,
|
|
57352: KeyUp,
|
|
57353: KeyDown,
|
|
57354: KeyPgUp,
|
|
57355: KeyPgDown,
|
|
57356: KeyHome,
|
|
57357: KeyEnd,
|
|
57358: KeyCapsLock,
|
|
57359: KeyScrollLock,
|
|
57360: KeyNumLock,
|
|
57361: KeyPrintScreen,
|
|
57362: KeyPause,
|
|
57363: KeyMenu,
|
|
57364: KeyF1,
|
|
57365: KeyF2,
|
|
57366: KeyF3,
|
|
57367: KeyF4,
|
|
57368: KeyF5,
|
|
57369: KeyF6,
|
|
57370: KeyF7,
|
|
57371: KeyF8,
|
|
57372: KeyF9,
|
|
57373: KeyF10,
|
|
57374: KeyF11,
|
|
57375: KeyF12,
|
|
57376: KeyF13,
|
|
57377: KeyF14,
|
|
57378: KeyF15,
|
|
57379: KeyF16,
|
|
57380: KeyF17,
|
|
57381: KeyF18,
|
|
57382: KeyF19,
|
|
57383: KeyF20,
|
|
57384: KeyF21,
|
|
57385: KeyF22,
|
|
57386: KeyF23,
|
|
57387: KeyF24,
|
|
57388: KeyF25,
|
|
57389: KeyF26,
|
|
57390: KeyF27,
|
|
57391: KeyF28,
|
|
57392: KeyF29,
|
|
57393: KeyF30,
|
|
57394: KeyF31,
|
|
57395: KeyF32,
|
|
57396: KeyF33,
|
|
57397: KeyF34,
|
|
57398: KeyF35,
|
|
57399: KeyKp0,
|
|
57400: KeyKp1,
|
|
57401: KeyKp2,
|
|
57402: KeyKp3,
|
|
57403: KeyKp4,
|
|
57404: KeyKp5,
|
|
57405: KeyKp6,
|
|
57406: KeyKp7,
|
|
57407: KeyKp8,
|
|
57408: KeyKp9,
|
|
57409: KeyKpDecimal,
|
|
57410: KeyKpDivide,
|
|
57411: KeyKpMultiply,
|
|
57412: KeyKpMinus,
|
|
57413: KeyKpPlus,
|
|
57414: KeyKpEnter,
|
|
57415: KeyKpEqual,
|
|
57416: KeyKpSep,
|
|
57417: KeyKpLeft,
|
|
57418: KeyKpRight,
|
|
57419: KeyKpUp,
|
|
57420: KeyKpDown,
|
|
57421: KeyKpPgUp,
|
|
57422: KeyKpPgDown,
|
|
57423: KeyKpHome,
|
|
57424: KeyKpEnd,
|
|
57425: KeyKpInsert,
|
|
57426: KeyKpDelete,
|
|
57427: KeyKpBegin,
|
|
57428: KeyMediaPlay,
|
|
57429: KeyMediaPause,
|
|
57430: KeyMediaPlayPause,
|
|
57431: KeyMediaReverse,
|
|
57432: KeyMediaStop,
|
|
57433: KeyMediaFastForward,
|
|
57434: KeyMediaRewind,
|
|
57435: KeyMediaNext,
|
|
57436: KeyMediaPrev,
|
|
57437: KeyMediaRecord,
|
|
57438: KeyLowerVol,
|
|
57439: KeyRaiseVol,
|
|
57440: KeyMute,
|
|
57441: KeyLeftShift,
|
|
57442: KeyLeftCtrl,
|
|
57443: KeyLeftAlt,
|
|
57444: KeyLeftSuper,
|
|
57445: KeyLeftHyper,
|
|
57446: KeyLeftMeta,
|
|
57447: KeyRightShift,
|
|
57448: KeyRightCtrl,
|
|
57449: KeyRightAlt,
|
|
57450: KeyRightSuper,
|
|
57451: KeyRightHyper,
|
|
57452: KeyRightMeta,
|
|
57453: KeyIsoLevel3Shift,
|
|
57454: KeyIsoLevel5Shift,
|
|
}
|
|
|
|
const (
|
|
kittyShift = 1 << iota
|
|
kittyAlt
|
|
kittyCtrl
|
|
kittySuper
|
|
kittyHyper
|
|
kittyMeta
|
|
kittyCapsLock
|
|
kittyNumLock
|
|
)
|
|
|
|
func fromKittyMod(mod int) KeyMod {
|
|
var m KeyMod
|
|
if mod&kittyShift != 0 {
|
|
m |= Shift
|
|
}
|
|
if mod&kittyAlt != 0 {
|
|
m |= Alt
|
|
}
|
|
if mod&kittyCtrl != 0 {
|
|
m |= Ctrl
|
|
}
|
|
if mod&kittySuper != 0 {
|
|
m |= Super
|
|
}
|
|
if mod&kittyHyper != 0 {
|
|
m |= Hyper
|
|
}
|
|
if mod&kittyMeta != 0 {
|
|
m |= Meta
|
|
}
|
|
if mod&kittyCapsLock != 0 {
|
|
m |= CapsLock
|
|
}
|
|
if mod&kittyNumLock != 0 {
|
|
m |= NumLock
|
|
}
|
|
return m
|
|
}
|
|
|
|
// parseKittyKeyboard parses a Kitty Keyboard Protocol sequence.
|
|
//
|
|
// In `CSI u`, this is parsed as:
|
|
//
|
|
// CSI codepoint ; modifiers u
|
|
// codepoint: ASCII Dec value
|
|
//
|
|
// The Kitty Keyboard Protocol extends this with optional components that can be
|
|
// enabled progressively. The full sequence is parsed as:
|
|
//
|
|
// CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
|
|
//
|
|
// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
|
func parseKittyKeyboard(csi *ansi.CsiSequence) Event {
|
|
var isRelease bool
|
|
key := Key{}
|
|
|
|
if params := csi.Subparams(0); len(params) > 0 {
|
|
code := params[0]
|
|
if sym, ok := kittyKeyMap[code]; ok {
|
|
key.Sym = sym
|
|
} else {
|
|
r := rune(code)
|
|
if !utf8.ValidRune(r) {
|
|
r = utf8.RuneError
|
|
}
|
|
|
|
key.Rune = r
|
|
|
|
// alternate key reporting
|
|
switch len(params) {
|
|
case 3:
|
|
// shifted key + base key
|
|
if b := rune(params[2]); unicode.IsPrint(b) {
|
|
// XXX: When alternate key reporting is enabled, the protocol
|
|
// can return 3 things, the unicode codepoint of the key,
|
|
// the shifted codepoint of the key, and the standard
|
|
// PC-101 key layout codepoint.
|
|
// This is useful to create an unambiguous mapping of keys
|
|
// when using a different language layout.
|
|
key.BaseRune = b
|
|
}
|
|
fallthrough
|
|
case 2:
|
|
// shifted key
|
|
if s := rune(params[1]); unicode.IsPrint(s) {
|
|
// XXX: We swap keys here because we want the shifted key
|
|
// to be the Rune that is returned by the event.
|
|
// For example, shift+a should produce "A" not "a".
|
|
// In such a case, we set AltRune to the original key "a"
|
|
// and Rune to "A".
|
|
key.AltRune = key.Rune
|
|
key.Rune = s
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if params := csi.Subparams(1); len(params) > 0 {
|
|
mod := params[0]
|
|
if mod > 1 {
|
|
key.Mod = fromKittyMod(mod - 1)
|
|
}
|
|
if len(params) > 1 {
|
|
switch params[1] {
|
|
case 2:
|
|
key.IsRepeat = true
|
|
case 3:
|
|
isRelease = true
|
|
}
|
|
}
|
|
}
|
|
// TODO: Associated keys are not support yet.
|
|
// if params := csi.Subparams(2); len(params) > 0 {
|
|
// r := rune(params[0])
|
|
// if unicode.IsPrint(r) {
|
|
// key.AltRune = r
|
|
// }
|
|
// }
|
|
if isRelease {
|
|
return KeyUpEvent(key)
|
|
}
|
|
return KeyDownEvent(key)
|
|
}
|