blob: 8320341d2f3c6eb47bac8e6e7e2fc6499871899d [file] [log] [blame]
Bram Moolenaara44c7812022-11-15 22:59:07 +00001vim9script
2
3# Script to get various codes that keys send, depending on the protocol used.
4#
Bram Moolenaar83030352022-11-16 16:08:30 +00005# Usage: vim -u NONE -S keycode_check.vim
Bram Moolenaara44c7812022-11-15 22:59:07 +00006#
Bram Moolenaar94722c52023-01-28 19:19:03 +00007# Author: Bram Moolenaar
8# Last Update: 2022 Nov 15
Bram Moolenaara44c7812022-11-15 22:59:07 +00009#
10# The codes are stored in the file "keycode_check.json", so that you can
11# compare the results of various terminals.
12#
13# You can select what protocol to enable:
14# - None
15# - modifyOtherKeys level 2
16# - kitty keyboard protocol
17
18# Change directory to where this script is, so that the json file is found
19# there.
20exe 'cd ' .. expand('<sfile>:h')
21echo 'working in directory: ' .. getcwd()
22
23const filename = 'keycode_check.json'
24
25# Dictionary of dictionaries with the results in the form:
26# {'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'},
27# 'xterm2': {protocol: 'mok2', 'Tab': '09', 'S-Tab': '09'},
28# 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '09'},
29# }
30# The values are in hex form.
31var keycodes = {}
32
33if filereadable(filename)
34 keycodes = readfile(filename)->join()->json_decode()
35else
36 # Use some dummy entries to try out with
37 keycodes = {
38 'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'},
39 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '1b5b393b3275'},
40 }
41endif
42var orig_keycodes = deepcopy(keycodes) # used to detect something changed
43
44# Write the "keycodes" variable in JSON form to "filename".
45def WriteKeycodes()
46 # If the file already exists move it to become the backup file.
47 if filereadable(filename)
48 if rename(filename, filename .. '~')
49 echoerr $'Renaming {filename} to {filename}~ failed!'
50 return
51 endif
52 endif
53
54 if writefile([json_encode(keycodes)], filename) != 0
55 echoerr $'Writing {filename} failed!'
56 endif
57enddef
58
59# The key entries that we want to list, in this order.
60# The first item is displayed in the prompt, the second is the key in
61# the keycodes dictionary.
62var key_entries = [
63 ['Tab', 'Tab'],
64 ['Shift-Tab', 'S-Tab'],
65 ['Ctrl-Tab', 'C-Tab'],
66 ['Alt-Tab', 'A-Tab'],
67 ['Ctrl-I', 'C-I'],
68 ['Shift-Ctrl-I', 'S-C-I'],
69 ['Esc', 'Esc'],
70 ['Shift-Esc', 'S-Esc'],
71 ['Ctrl-Esc', 'C-Esc'],
72 ['Alt-Esc', 'A-Esc'],
73 ['Space', 'Space'],
74 ['Shift-Space', 'S-Space'],
75 ['Ctrl-Space', 'C-Space'],
76 ['Alt-Space', 'A-Space'],
77 ]
78
Bram Moolenaar83030352022-11-16 16:08:30 +000079# Given a terminal name and a item name, return the text to display.
80def GetItemDisplay(term: string, item: string): string
81 var val = get(keycodes[term], item, '')
82
83 # see if we can pretty-print this one
84 var pretty = val
85 if val[0 : 1] == '1b'
86 pretty = 'ESC'
87 var idx = 2
88
89 if val[0 : 3] == '1b5b'
90 pretty = 'CSI'
91 idx = 4
92 endif
93
94 var digits = false
95 while idx < len(val)
96 var cc = val[idx : idx + 1]
97 var nr = str2nr('0x' .. cc, 16)
98 idx += 2
99 if nr >= char2nr('0') && nr <= char2nr('9')
100 if !digits
101 pretty ..= ' '
102 endif
103 digits = true
104 pretty ..= cc[1]
105 else
106 if nr == char2nr(';') && digits
107 # don't use space between semicolon and digits to keep it short
108 pretty ..= ';'
109 else
110 digits = false
111 if nr >= char2nr(' ') && nr <= char2nr('~')
112 # printable character
113 pretty ..= ' ' .. printf('%c', nr)
114 else
115 # non-printable, use hex code
116 pretty = val
117 break
118 endif
119 endif
120 endif
121 endwhile
122 endif
123
124 return pretty
125enddef
126
Bram Moolenaara44c7812022-11-15 22:59:07 +0000127
128# Action: list the information in "keycodes" in a more or less nice way.
129def ActionList()
130 var terms = keys(keycodes)
131 if len(terms) == 0
132 echo 'No terminal results yet'
133 return
134 endif
Bram Moolenaar83030352022-11-16 16:08:30 +0000135 sort(terms)
Bram Moolenaara44c7812022-11-15 22:59:07 +0000136
Bram Moolenaarcc090712022-11-27 11:31:23 +0000137 var items = ['protocol', 'version', 'kitty', 'modkeys']
Bram Moolenaar83030352022-11-16 16:08:30 +0000138 + key_entries->copy()->map((_, v) => v[1])
139
140 # For each terminal compute the needed width, add two.
141 # You may need to increase the terminal width to avoid wrapping.
142 var widths = []
143 for [idx, term] in items(terms)
144 widths[idx] = len(term) + 2
145 endfor
146
147 for item in items
148 for [idx, term] in items(terms)
149 var l = len(GetItemDisplay(term, item))
150 if widths[idx] < l + 2
151 widths[idx] = l + 2
152 endif
153 endfor
154 endfor
155
156 # Use one column of width 10 for the item name.
157 echo "\n"
158 echon ' '
159 for [idx, term] in items(terms)
160 echon printf('%-' .. widths[idx] .. 's', term)
Bram Moolenaara44c7812022-11-15 22:59:07 +0000161 endfor
162 echo "\n"
163
Bram Moolenaara44c7812022-11-15 22:59:07 +0000164 for item in items
165 echon printf('%8s ', item)
Bram Moolenaar83030352022-11-16 16:08:30 +0000166 for [idx, term] in items(terms)
167 echon printf('%-' .. widths[idx] .. 's', GetItemDisplay(term, item))
Bram Moolenaara44c7812022-11-15 22:59:07 +0000168 endfor
169 echo ''
170 endfor
171 echo "\n"
172enddef
173
Bram Moolenaar83030352022-11-16 16:08:30 +0000174# Convert the literal string after "raw key input" into hex form.
175def Literal2hex(code: string): string
176 var hex = ''
177 for i in range(len(code))
178 hex ..= printf('%02x', char2nr(code[i]))
179 endfor
180 return hex
181enddef
182
Bram Moolenaara44c7812022-11-15 22:59:07 +0000183def GetTermName(): string
184 var name = input('Enter the name of the terminal: ')
185 return name
186enddef
187
188# Gather key codes for terminal "name".
189def DoTerm(name: string)
190 var proto = inputlist([$'What protocol to enable for {name}:',
191 '1. None',
192 '2. modifyOtherKeys level 2',
193 '3. kitty',
194 ])
195 echo "\n"
196 &t_TE = "\<Esc>[>4;m"
Bram Moolenaarcc090712022-11-27 11:31:23 +0000197 var proto_name = 'unknown'
Bram Moolenaara44c7812022-11-15 22:59:07 +0000198 if proto == 1
Bram Moolenaarcc090712022-11-27 11:31:23 +0000199 # Request the XTQMODKEYS value and request the kitty keyboard protocol status.
200 &t_TI = "\<Esc>[?4m" .. "\<Esc>[?u"
201 proto_name = 'none'
Bram Moolenaara44c7812022-11-15 22:59:07 +0000202 elseif proto == 2
Bram Moolenaarcc090712022-11-27 11:31:23 +0000203 # Enable modifyOtherKeys level 2 and request the XTQMODKEYS value.
Bram Moolenaarc255b782022-11-26 19:16:48 +0000204 &t_TI = "\<Esc>[>4;2m" .. "\<Esc>[?4m"
Bram Moolenaara44c7812022-11-15 22:59:07 +0000205 proto_name = 'mok2'
206 elseif proto == 3
Bram Moolenaarcc090712022-11-27 11:31:23 +0000207 # Enable Kitty keyboard protocol and request the status.
Bram Moolenaar83030352022-11-16 16:08:30 +0000208 &t_TI = "\<Esc>[>1u" .. "\<Esc>[?u"
Bram Moolenaara44c7812022-11-15 22:59:07 +0000209 proto_name = 'kitty'
210 else
211 echoerr 'invalid protocol choice'
212 return
213 endif
214
Bram Moolenaar83030352022-11-16 16:08:30 +0000215 # Append the request for the version response, this is used to check we have
216 # the results.
217 &t_TI ..= "\<Esc>[>c"
218
219 # Pattern that matches the line with the version response.
220 const version_pattern = "\<Esc>\\[>\\d\\+;\\d\\+;\\d*c"
221
Bram Moolenaarc255b782022-11-26 19:16:48 +0000222 # Pattern that matches the XTQMODKEYS response:
223 # CSI > 4;Pv m
224 # where Pv indicates the modifyOtherKeys level
225 const modkeys_pattern = "\<Esc>\\[>4;\\dm"
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000226
Bram Moolenaar83030352022-11-16 16:08:30 +0000227 # Pattern that matches the line with the status. Currently what terminals
228 # return for the Kitty keyboard protocol.
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000229 const kitty_status_pattern = "\<Esc>\\[?\\d\\+u"
Bram Moolenaar83030352022-11-16 16:08:30 +0000230
231 ch_logfile('keylog', 'w')
232
Bram Moolenaara44c7812022-11-15 22:59:07 +0000233 # executing a dummy shell command will output t_TI
234 !echo >/dev/null
235
Bram Moolenaar83030352022-11-16 16:08:30 +0000236 # Wait until the log file has the version response.
237 var startTime = reltime()
238 var seenVersion = false
239 while !seenVersion
240 var log = readfile('keylog')
241 if len(log) > 2
242 for line in log
243 if line =~ 'raw key input'
244 var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
245 if code =~ version_pattern
246 seenVersion = true
247 echo 'Found the version response'
248 break
249 endif
250 endif
251 endfor
252 endif
253 if reltime(startTime)->reltimefloat() > 3
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000254 # break out after three seconds
Bram Moolenaar83030352022-11-16 16:08:30 +0000255 break
256 endif
257 endwhile
258
259 echo 'seenVersion: ' seenVersion
260
261 # Prepare the terminal entry, set protocol and clear status and version.
Bram Moolenaara44c7812022-11-15 22:59:07 +0000262 if !has_key(keycodes, name)
263 keycodes[name] = {}
264 endif
265 keycodes[name]['protocol'] = proto_name
Bram Moolenaar83030352022-11-16 16:08:30 +0000266 keycodes[name]['version'] = ''
Bram Moolenaarcc090712022-11-27 11:31:23 +0000267 keycodes[name]['kitty'] = ''
Bram Moolenaarc255b782022-11-26 19:16:48 +0000268 keycodes[name]['modkeys'] = ''
Bram Moolenaara44c7812022-11-15 22:59:07 +0000269
Bram Moolenaar83030352022-11-16 16:08:30 +0000270 # Check the log file for a status and the version response
271 ch_logfile('', '')
272 var log = readfile('keylog')
273 delete('keylog')
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000274
Bram Moolenaar83030352022-11-16 16:08:30 +0000275 for line in log
276 if line =~ 'raw key input'
277 var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000278
Bram Moolenaarc255b782022-11-26 19:16:48 +0000279 # Check for the XTQMODKEYS response.
280 if code =~ modkeys_pattern
281 var modkeys = substitute(code, '.*\(' .. modkeys_pattern .. '\).*', '\1', '')
Bram Moolenaarcc090712022-11-27 11:31:23 +0000282 # We could get the level out of the response, but showing the response
283 # itself provides more information.
284 # modkeys = substitute(modkeys, '.*4;\(\d\)m', '\1', '')
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000285
Bram Moolenaarc255b782022-11-26 19:16:48 +0000286 if keycodes[name]['modkeys'] != ''
287 echomsg 'Another modkeys found after ' .. keycodes[name]['modkeys']
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000288 endif
Bram Moolenaarc255b782022-11-26 19:16:48 +0000289 keycodes[name]['modkeys'] = modkeys
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000290 endif
291
Bram Moolenaar83030352022-11-16 16:08:30 +0000292 # Check for kitty keyboard protocol status
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000293 if code =~ kitty_status_pattern
294 var status = substitute(code, '.*\(' .. kitty_status_pattern .. '\).*', '\1', '')
295 # use the response itself as the status
296 status = Literal2hex(status)
297
Bram Moolenaarcc090712022-11-27 11:31:23 +0000298 if keycodes[name]['kitty'] != ''
299 echomsg 'Another status found after ' .. keycodes[name]['kitty']
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000300 endif
Bram Moolenaarcc090712022-11-27 11:31:23 +0000301 keycodes[name]['kitty'] = status
Bram Moolenaar83030352022-11-16 16:08:30 +0000302 endif
303
304 if code =~ version_pattern
305 var version = substitute(code, '.*\(' .. version_pattern .. '\).*', '\1', '')
306 keycodes[name]['version'] = Literal2hex(version)
307 break
308 endif
309 endif
310 endfor
311
312 echo "For Alt to work you may need to press the Windows/Super key as well"
313 echo "When a key press doesn't get to Vim (e.g. when using Alt) press x"
Bram Moolenaara44c7812022-11-15 22:59:07 +0000314
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000315 # The log of ignored typeahead is left around for debugging, start with an
316 # empty file here.
317 delete('keylog-ignore')
318
Bram Moolenaara44c7812022-11-15 22:59:07 +0000319 for entry in key_entries
Bram Moolenaar83030352022-11-16 16:08:30 +0000320 # Consume any typeahead. Wait a bit for any responses to arrive.
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000321 ch_logfile('keylog-ignore', 'a')
322 while 1
Bram Moolenaar83030352022-11-16 16:08:30 +0000323 sleep 100m
Bram Moolenaarc896adb2022-11-19 19:02:40 +0000324 if getchar(1) == 0
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000325 break
326 endif
Bram Moolenaarc896adb2022-11-19 19:02:40 +0000327 while getchar(1) != 0
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000328 getchar()
329 endwhile
Bram Moolenaar83030352022-11-16 16:08:30 +0000330 endwhile
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000331 ch_logfile('', '')
Bram Moolenaar83030352022-11-16 16:08:30 +0000332
Bram Moolenaara44c7812022-11-15 22:59:07 +0000333 ch_logfile('keylog', 'w')
334 echo $'Press the {entry[0]} key (q to quit):'
335 var r = getcharstr()
336 ch_logfile('', '')
337 if r == 'q'
338 break
339 endif
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000340
Bram Moolenaar83030352022-11-16 16:08:30 +0000341 log = readfile('keylog')
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000342 delete('keylog')
Bram Moolenaara44c7812022-11-15 22:59:07 +0000343 if len(log) < 2
344 echoerr 'failed to read result'
345 return
346 endif
347 var done = false
348 for line in log
349 if line =~ 'raw key input'
350 var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
351
Bram Moolenaar83030352022-11-16 16:08:30 +0000352 # Remove any version termresponse
353 code = substitute(code, version_pattern, '', 'g')
354
355 # Remove any XTGETTCAP replies.
356 const cappat = "\<Esc>P[01]+\\k\\+=\\x*\<Esc>\\\\"
357 code = substitute(code, cappat, '', 'g')
358
359 # Remove any kitty status reply
Bram Moolenaar236dffa2022-11-18 21:20:25 +0000360 code = substitute(code, kitty_status_pattern, '', 'g')
Bram Moolenaar83030352022-11-16 16:08:30 +0000361 if code == ''
362 continue
363 endif
364
365 # Convert the literal bytes into hex. If 'x' was pressed then clear
366 # the entry.
Bram Moolenaara44c7812022-11-15 22:59:07 +0000367 var hex = ''
Bram Moolenaar83030352022-11-16 16:08:30 +0000368 if code != 'x'
369 hex = Literal2hex(code)
370 endif
371
Bram Moolenaara44c7812022-11-15 22:59:07 +0000372 keycodes[name][entry[1]] = hex
373 done = true
374 break
375 endif
376 endfor
377 if !done
378 echo 'Code not found in log'
379 endif
380 endfor
381enddef
382
383# Action: Add key codes for a new terminal.
384def ActionAdd()
385 var name = input('Enter name of the terminal: ')
386 echo "\n"
387 if index(keys(keycodes), name) >= 0
388 echoerr $'Terminal {name} already exists'
389 return
390 endif
391
392 DoTerm(name)
393enddef
394
395# Action: Replace key codes for an already known terminal.
396def ActionReplace()
397 var terms = keys(keycodes)
398 if len(terms) == 0
399 echo 'No terminal results yet'
400 return
401 endif
402
403 var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg))
404 echo "\n"
405 if choice > 0 && choice <= len(terms)
406 DoTerm(terms[choice - 1])
Bram Moolenaar83030352022-11-16 16:08:30 +0000407 else
408 echo 'invalid index'
Bram Moolenaara44c7812022-11-15 22:59:07 +0000409 endif
Bram Moolenaar83030352022-11-16 16:08:30 +0000410enddef
411
412# Action: Clear key codes for an already known terminal.
413def ActionClear()
414 var terms = keys(keycodes)
415 if len(terms) == 0
416 echo 'No terminal results yet'
417 return
418 endif
419
420 var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg))
421 echo "\n"
422 if choice > 0 && choice <= len(terms)
423 remove(keycodes, terms[choice - 1])
424 else
425 echo 'invalid index'
426 endif
Bram Moolenaara44c7812022-11-15 22:59:07 +0000427enddef
428
429# Action: Quit, possibly after saving the results first.
430def ActionQuit()
431 # If nothing was changed just quit
432 if keycodes == orig_keycodes
433 quit
434 endif
435
436 while true
437 var res = input("Save the changed key codes (y/n)? ")
438 if res == 'n'
439 quit
440 endif
441 if res == 'y'
442 WriteKeycodes()
443 quit
444 endif
445 echo 'invalid reply'
446 endwhile
447enddef
448
449# The main loop
450while true
451 var action = inputlist(['Select operation:',
Bram Moolenaar94722c52023-01-28 19:19:03 +0000452 '1. List results',
Bram Moolenaara44c7812022-11-15 22:59:07 +0000453 '2. Add results for a new terminal',
454 '3. Replace results',
Bram Moolenaar83030352022-11-16 16:08:30 +0000455 '4. Clear results',
456 '5. Quit',
Bram Moolenaara44c7812022-11-15 22:59:07 +0000457 ])
458 echo "\n"
459 if action == 1
460 ActionList()
461 elseif action == 2
462 ActionAdd()
463 elseif action == 3
464 ActionReplace()
465 elseif action == 4
Bram Moolenaar83030352022-11-16 16:08:30 +0000466 ActionClear()
467 elseif action == 5
Bram Moolenaara44c7812022-11-15 22:59:07 +0000468 ActionQuit()
469 endif
470endwhile