kitty.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. package input
  2. import (
  3. "unicode"
  4. "unicode/utf8"
  5. "github.com/charmbracelet/x/ansi"
  6. "github.com/charmbracelet/x/ansi/kitty"
  7. )
  8. // KittyGraphicsEvent represents a Kitty Graphics response event.
  9. //
  10. // See https://sw.kovidgoyal.net/kitty/graphics-protocol/
  11. type KittyGraphicsEvent struct {
  12. Options kitty.Options
  13. Payload []byte
  14. }
  15. // KittyEnhancementsEvent represents a Kitty enhancements event.
  16. type KittyEnhancementsEvent int
  17. // Kitty keyboard enhancement constants.
  18. // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
  19. const (
  20. KittyDisambiguateEscapeCodes KittyEnhancementsEvent = 1 << iota
  21. KittyReportEventTypes
  22. KittyReportAlternateKeys
  23. KittyReportAllKeysAsEscapeCodes
  24. KittyReportAssociatedText
  25. )
  26. // Contains reports whether m contains the given enhancements.
  27. func (e KittyEnhancementsEvent) Contains(enhancements KittyEnhancementsEvent) bool {
  28. return e&enhancements == enhancements
  29. }
  30. // Kitty Clipboard Control Sequences.
  31. var kittyKeyMap = map[int]Key{
  32. ansi.BS: {Code: KeyBackspace},
  33. ansi.HT: {Code: KeyTab},
  34. ansi.CR: {Code: KeyEnter},
  35. ansi.ESC: {Code: KeyEscape},
  36. ansi.DEL: {Code: KeyBackspace},
  37. 57344: {Code: KeyEscape},
  38. 57345: {Code: KeyEnter},
  39. 57346: {Code: KeyTab},
  40. 57347: {Code: KeyBackspace},
  41. 57348: {Code: KeyInsert},
  42. 57349: {Code: KeyDelete},
  43. 57350: {Code: KeyLeft},
  44. 57351: {Code: KeyRight},
  45. 57352: {Code: KeyUp},
  46. 57353: {Code: KeyDown},
  47. 57354: {Code: KeyPgUp},
  48. 57355: {Code: KeyPgDown},
  49. 57356: {Code: KeyHome},
  50. 57357: {Code: KeyEnd},
  51. 57358: {Code: KeyCapsLock},
  52. 57359: {Code: KeyScrollLock},
  53. 57360: {Code: KeyNumLock},
  54. 57361: {Code: KeyPrintScreen},
  55. 57362: {Code: KeyPause},
  56. 57363: {Code: KeyMenu},
  57. 57364: {Code: KeyF1},
  58. 57365: {Code: KeyF2},
  59. 57366: {Code: KeyF3},
  60. 57367: {Code: KeyF4},
  61. 57368: {Code: KeyF5},
  62. 57369: {Code: KeyF6},
  63. 57370: {Code: KeyF7},
  64. 57371: {Code: KeyF8},
  65. 57372: {Code: KeyF9},
  66. 57373: {Code: KeyF10},
  67. 57374: {Code: KeyF11},
  68. 57375: {Code: KeyF12},
  69. 57376: {Code: KeyF13},
  70. 57377: {Code: KeyF14},
  71. 57378: {Code: KeyF15},
  72. 57379: {Code: KeyF16},
  73. 57380: {Code: KeyF17},
  74. 57381: {Code: KeyF18},
  75. 57382: {Code: KeyF19},
  76. 57383: {Code: KeyF20},
  77. 57384: {Code: KeyF21},
  78. 57385: {Code: KeyF22},
  79. 57386: {Code: KeyF23},
  80. 57387: {Code: KeyF24},
  81. 57388: {Code: KeyF25},
  82. 57389: {Code: KeyF26},
  83. 57390: {Code: KeyF27},
  84. 57391: {Code: KeyF28},
  85. 57392: {Code: KeyF29},
  86. 57393: {Code: KeyF30},
  87. 57394: {Code: KeyF31},
  88. 57395: {Code: KeyF32},
  89. 57396: {Code: KeyF33},
  90. 57397: {Code: KeyF34},
  91. 57398: {Code: KeyF35},
  92. 57399: {Code: KeyKp0},
  93. 57400: {Code: KeyKp1},
  94. 57401: {Code: KeyKp2},
  95. 57402: {Code: KeyKp3},
  96. 57403: {Code: KeyKp4},
  97. 57404: {Code: KeyKp5},
  98. 57405: {Code: KeyKp6},
  99. 57406: {Code: KeyKp7},
  100. 57407: {Code: KeyKp8},
  101. 57408: {Code: KeyKp9},
  102. 57409: {Code: KeyKpDecimal},
  103. 57410: {Code: KeyKpDivide},
  104. 57411: {Code: KeyKpMultiply},
  105. 57412: {Code: KeyKpMinus},
  106. 57413: {Code: KeyKpPlus},
  107. 57414: {Code: KeyKpEnter},
  108. 57415: {Code: KeyKpEqual},
  109. 57416: {Code: KeyKpSep},
  110. 57417: {Code: KeyKpLeft},
  111. 57418: {Code: KeyKpRight},
  112. 57419: {Code: KeyKpUp},
  113. 57420: {Code: KeyKpDown},
  114. 57421: {Code: KeyKpPgUp},
  115. 57422: {Code: KeyKpPgDown},
  116. 57423: {Code: KeyKpHome},
  117. 57424: {Code: KeyKpEnd},
  118. 57425: {Code: KeyKpInsert},
  119. 57426: {Code: KeyKpDelete},
  120. 57427: {Code: KeyKpBegin},
  121. 57428: {Code: KeyMediaPlay},
  122. 57429: {Code: KeyMediaPause},
  123. 57430: {Code: KeyMediaPlayPause},
  124. 57431: {Code: KeyMediaReverse},
  125. 57432: {Code: KeyMediaStop},
  126. 57433: {Code: KeyMediaFastForward},
  127. 57434: {Code: KeyMediaRewind},
  128. 57435: {Code: KeyMediaNext},
  129. 57436: {Code: KeyMediaPrev},
  130. 57437: {Code: KeyMediaRecord},
  131. 57438: {Code: KeyLowerVol},
  132. 57439: {Code: KeyRaiseVol},
  133. 57440: {Code: KeyMute},
  134. 57441: {Code: KeyLeftShift},
  135. 57442: {Code: KeyLeftCtrl},
  136. 57443: {Code: KeyLeftAlt},
  137. 57444: {Code: KeyLeftSuper},
  138. 57445: {Code: KeyLeftHyper},
  139. 57446: {Code: KeyLeftMeta},
  140. 57447: {Code: KeyRightShift},
  141. 57448: {Code: KeyRightCtrl},
  142. 57449: {Code: KeyRightAlt},
  143. 57450: {Code: KeyRightSuper},
  144. 57451: {Code: KeyRightHyper},
  145. 57452: {Code: KeyRightMeta},
  146. 57453: {Code: KeyIsoLevel3Shift},
  147. 57454: {Code: KeyIsoLevel5Shift},
  148. }
  149. func init() {
  150. // These are some faulty C0 mappings some terminals such as WezTerm have
  151. // and doesn't follow the specs.
  152. kittyKeyMap[ansi.NUL] = Key{Code: KeySpace, Mod: ModCtrl}
  153. for i := ansi.SOH; i <= ansi.SUB; i++ {
  154. if _, ok := kittyKeyMap[i]; !ok {
  155. kittyKeyMap[i] = Key{Code: rune(i + 0x60), Mod: ModCtrl}
  156. }
  157. }
  158. for i := ansi.FS; i <= ansi.US; i++ {
  159. if _, ok := kittyKeyMap[i]; !ok {
  160. kittyKeyMap[i] = Key{Code: rune(i + 0x40), Mod: ModCtrl}
  161. }
  162. }
  163. }
  164. const (
  165. kittyShift = 1 << iota
  166. kittyAlt
  167. kittyCtrl
  168. kittySuper
  169. kittyHyper
  170. kittyMeta
  171. kittyCapsLock
  172. kittyNumLock
  173. )
  174. func fromKittyMod(mod int) KeyMod {
  175. var m KeyMod
  176. if mod&kittyShift != 0 {
  177. m |= ModShift
  178. }
  179. if mod&kittyAlt != 0 {
  180. m |= ModAlt
  181. }
  182. if mod&kittyCtrl != 0 {
  183. m |= ModCtrl
  184. }
  185. if mod&kittySuper != 0 {
  186. m |= ModSuper
  187. }
  188. if mod&kittyHyper != 0 {
  189. m |= ModHyper
  190. }
  191. if mod&kittyMeta != 0 {
  192. m |= ModMeta
  193. }
  194. if mod&kittyCapsLock != 0 {
  195. m |= ModCapsLock
  196. }
  197. if mod&kittyNumLock != 0 {
  198. m |= ModNumLock
  199. }
  200. return m
  201. }
  202. // parseKittyKeyboard parses a Kitty Keyboard Protocol sequence.
  203. //
  204. // In `CSI u`, this is parsed as:
  205. //
  206. // CSI codepoint ; modifiers u
  207. // codepoint: ASCII Dec value
  208. //
  209. // The Kitty Keyboard Protocol extends this with optional components that can be
  210. // enabled progressively. The full sequence is parsed as:
  211. //
  212. // CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
  213. //
  214. // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/
  215. func parseKittyKeyboard(params ansi.Params) (Event Event) {
  216. var isRelease bool
  217. var key Key
  218. // The index of parameters separated by semicolons ';'. Sub parameters are
  219. // separated by colons ':'.
  220. var paramIdx int
  221. var sudIdx int // The sub parameter index
  222. for _, p := range params {
  223. // Kitty Keyboard Protocol has 3 optional components.
  224. switch paramIdx {
  225. case 0:
  226. switch sudIdx {
  227. case 0:
  228. var foundKey bool
  229. code := p.Param(1) // CSI u has a default value of 1
  230. key, foundKey = kittyKeyMap[code]
  231. if !foundKey {
  232. r := rune(code)
  233. if !utf8.ValidRune(r) {
  234. r = utf8.RuneError
  235. }
  236. key.Code = r
  237. }
  238. case 2:
  239. // shifted key + base key
  240. if b := rune(p.Param(1)); unicode.IsPrint(b) {
  241. // XXX: When alternate key reporting is enabled, the protocol
  242. // can return 3 things, the unicode codepoint of the key,
  243. // the shifted codepoint of the key, and the standard
  244. // PC-101 key layout codepoint.
  245. // This is useful to create an unambiguous mapping of keys
  246. // when using a different language layout.
  247. key.BaseCode = b
  248. }
  249. fallthrough
  250. case 1:
  251. // shifted key
  252. if s := rune(p.Param(1)); unicode.IsPrint(s) {
  253. // XXX: We swap keys here because we want the shifted key
  254. // to be the Rune that is returned by the event.
  255. // For example, shift+a should produce "A" not "a".
  256. // In such a case, we set AltRune to the original key "a"
  257. // and Rune to "A".
  258. key.ShiftedCode = s
  259. }
  260. }
  261. case 1:
  262. switch sudIdx {
  263. case 0:
  264. mod := p.Param(1)
  265. if mod > 1 {
  266. key.Mod = fromKittyMod(mod - 1)
  267. if key.Mod > ModShift {
  268. // XXX: We need to clear the text if we have a modifier key
  269. // other than a [ModShift] key.
  270. key.Text = ""
  271. }
  272. }
  273. case 1:
  274. switch p.Param(1) {
  275. case 2:
  276. key.IsRepeat = true
  277. case 3:
  278. isRelease = true
  279. }
  280. case 2:
  281. }
  282. case 2:
  283. if code := p.Param(0); code != 0 {
  284. key.Text += string(rune(code))
  285. }
  286. }
  287. sudIdx++
  288. if !p.HasMore() {
  289. paramIdx++
  290. sudIdx = 0
  291. }
  292. }
  293. //nolint:nestif
  294. if len(key.Text) == 0 && unicode.IsPrint(key.Code) &&
  295. (key.Mod <= ModShift || key.Mod == ModCapsLock || key.Mod == ModShift|ModCapsLock) {
  296. if key.Mod == 0 {
  297. key.Text = string(key.Code)
  298. } else {
  299. desiredCase := unicode.ToLower
  300. if key.Mod.Contains(ModShift) || key.Mod.Contains(ModCapsLock) {
  301. desiredCase = unicode.ToUpper
  302. }
  303. if key.ShiftedCode != 0 {
  304. key.Text = string(key.ShiftedCode)
  305. } else {
  306. key.Text = string(desiredCase(key.Code))
  307. }
  308. }
  309. }
  310. if isRelease {
  311. return KeyReleaseEvent(key)
  312. }
  313. return KeyPressEvent(key)
  314. }
  315. // parseKittyKeyboardExt parses a Kitty Keyboard Protocol sequence extensions
  316. // for non CSI u sequences. This includes things like CSI A, SS3 A and others,
  317. // and CSI ~.
  318. func parseKittyKeyboardExt(params ansi.Params, k KeyPressEvent) Event {
  319. // Handle Kitty keyboard protocol
  320. if len(params) > 2 && // We have at least 3 parameters
  321. params[0].Param(1) == 1 && // The first parameter is 1 (defaults to 1)
  322. params[1].HasMore() { // The second parameter is a subparameter (separated by a ":")
  323. switch params[2].Param(1) { // The third parameter is the event type (defaults to 1)
  324. case 2:
  325. k.IsRepeat = true
  326. case 3:
  327. return KeyReleaseEvent(k)
  328. }
  329. }
  330. return k
  331. }