Bram Moolenaar | a44c781 | 2022-11-15 22:59:07 +0000 | [diff] [blame] | 1 | vim9script |
| 2 | |
| 3 | # Script to get various codes that keys send, depending on the protocol used. |
| 4 | # |
| 5 | # Usage: vim -u keycode_check.vim |
| 6 | # |
| 7 | # Author: Bram Moolenaar |
| 8 | # Last Update: 2022 Nov 15 |
| 9 | # |
| 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. |
| 20 | exe 'cd ' .. expand('<sfile>:h') |
| 21 | echo 'working in directory: ' .. getcwd() |
| 22 | |
| 23 | const 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. |
| 31 | var keycodes = {} |
| 32 | |
| 33 | if filereadable(filename) |
| 34 | keycodes = readfile(filename)->join()->json_decode() |
| 35 | else |
| 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 | } |
| 41 | endif |
| 42 | var orig_keycodes = deepcopy(keycodes) # used to detect something changed |
| 43 | |
| 44 | # Write the "keycodes" variable in JSON form to "filename". |
| 45 | def 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 |
| 57 | enddef |
| 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. |
| 62 | var 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 | |
| 79 | |
| 80 | # Action: list the information in "keycodes" in a more or less nice way. |
| 81 | def ActionList() |
| 82 | var terms = keys(keycodes) |
| 83 | if len(terms) == 0 |
| 84 | echo 'No terminal results yet' |
| 85 | return |
| 86 | endif |
| 87 | |
| 88 | # Use one column of width 10 for the item name, then columns of 20 |
| 89 | # characters to fit most codes. You will need to increase the terminal |
| 90 | # width to avoid wrapping. |
| 91 | echon printf(' ') |
| 92 | for term in terms |
| 93 | echon printf('%-20s', term) |
| 94 | endfor |
| 95 | echo "\n" |
| 96 | |
| 97 | var items = ['protocol'] + key_entries->copy()->map((_, v) => v[1]) |
| 98 | |
| 99 | for item in items |
| 100 | echon printf('%8s ', item) |
| 101 | for term in terms |
| 102 | var val = get(keycodes[term], item, '') |
| 103 | |
| 104 | # see if we can pretty-print this one |
| 105 | var pretty = val |
| 106 | if val[0 : 1] == '1b' |
| 107 | pretty = 'ESC' |
| 108 | var idx = 2 |
| 109 | |
| 110 | if val[0 : 3] == '1b5b' |
| 111 | pretty = 'CSI' |
| 112 | idx = 4 |
| 113 | endif |
| 114 | |
| 115 | var digits = false |
| 116 | while idx < len(val) |
| 117 | var cc = val[idx : idx + 1] |
| 118 | var nr = str2nr('0x' .. cc, 16) |
| 119 | idx += 2 |
| 120 | if nr >= char2nr('0') && nr <= char2nr('9') |
| 121 | if !digits |
| 122 | pretty ..= ' ' |
| 123 | endif |
| 124 | digits = true |
| 125 | pretty ..= cc[1] |
| 126 | else |
| 127 | digits = false |
| 128 | if nr >= char2nr(' ') && nr <= char2nr('~') |
| 129 | # printable character |
| 130 | pretty ..= ' ' .. printf('%c', nr) |
| 131 | else |
| 132 | # non-printable, use hex code |
| 133 | pretty = val |
| 134 | break |
| 135 | endif |
| 136 | endif |
| 137 | endwhile |
| 138 | endif |
| 139 | |
| 140 | echon printf('%-20s', pretty) |
| 141 | endfor |
| 142 | echo '' |
| 143 | endfor |
| 144 | echo "\n" |
| 145 | enddef |
| 146 | |
| 147 | def GetTermName(): string |
| 148 | var name = input('Enter the name of the terminal: ') |
| 149 | return name |
| 150 | enddef |
| 151 | |
| 152 | # Gather key codes for terminal "name". |
| 153 | def DoTerm(name: string) |
| 154 | var proto = inputlist([$'What protocol to enable for {name}:', |
| 155 | '1. None', |
| 156 | '2. modifyOtherKeys level 2', |
| 157 | '3. kitty', |
| 158 | ]) |
| 159 | echo "\n" |
| 160 | &t_TE = "\<Esc>[>4;m" |
| 161 | var proto_name = 'none' |
| 162 | if proto == 1 |
| 163 | &t_TI = "" |
| 164 | elseif proto == 2 |
| 165 | &t_TI = "\<Esc>[>4;2m" |
| 166 | proto_name = 'mok2' |
| 167 | elseif proto == 3 |
| 168 | &t_TI = "\<Esc>[>1u" |
| 169 | proto_name = 'kitty' |
| 170 | else |
| 171 | echoerr 'invalid protocol choice' |
| 172 | return |
| 173 | endif |
| 174 | |
| 175 | # executing a dummy shell command will output t_TI |
| 176 | !echo >/dev/null |
| 177 | |
| 178 | if !has_key(keycodes, name) |
| 179 | keycodes[name] = {} |
| 180 | endif |
| 181 | keycodes[name]['protocol'] = proto_name |
| 182 | |
| 183 | echo "When a key press doesn't get to Vim (e.g. when using Alt) press Space" |
| 184 | |
| 185 | for entry in key_entries |
| 186 | ch_logfile('keylog', 'w') |
| 187 | echo $'Press the {entry[0]} key (q to quit):' |
| 188 | var r = getcharstr() |
| 189 | ch_logfile('', '') |
| 190 | if r == 'q' |
| 191 | break |
| 192 | endif |
| 193 | var log = readfile('keylog') |
| 194 | delete('keylog') |
| 195 | if len(log) < 2 |
| 196 | echoerr 'failed to read result' |
| 197 | return |
| 198 | endif |
| 199 | var done = false |
| 200 | for line in log |
| 201 | if line =~ 'raw key input' |
| 202 | var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '') |
| 203 | |
| 204 | # convert the literal bytes into hex |
| 205 | var hex = '' |
| 206 | for i in range(len(code)) |
| 207 | hex ..= printf('%02x', char2nr(code[i])) |
| 208 | endfor |
| 209 | keycodes[name][entry[1]] = hex |
| 210 | done = true |
| 211 | break |
| 212 | endif |
| 213 | endfor |
| 214 | if !done |
| 215 | echo 'Code not found in log' |
| 216 | endif |
| 217 | endfor |
| 218 | enddef |
| 219 | |
| 220 | # Action: Add key codes for a new terminal. |
| 221 | def ActionAdd() |
| 222 | var name = input('Enter name of the terminal: ') |
| 223 | echo "\n" |
| 224 | if index(keys(keycodes), name) >= 0 |
| 225 | echoerr $'Terminal {name} already exists' |
| 226 | return |
| 227 | endif |
| 228 | |
| 229 | DoTerm(name) |
| 230 | enddef |
| 231 | |
| 232 | # Action: Replace key codes for an already known terminal. |
| 233 | def ActionReplace() |
| 234 | var terms = keys(keycodes) |
| 235 | if len(terms) == 0 |
| 236 | echo 'No terminal results yet' |
| 237 | return |
| 238 | endif |
| 239 | |
| 240 | var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg)) |
| 241 | echo "\n" |
| 242 | if choice > 0 && choice <= len(terms) |
| 243 | DoTerm(terms[choice - 1]) |
| 244 | endif |
| 245 | echo 'invalid index' |
| 246 | enddef |
| 247 | |
| 248 | # Action: Quit, possibly after saving the results first. |
| 249 | def ActionQuit() |
| 250 | # If nothing was changed just quit |
| 251 | if keycodes == orig_keycodes |
| 252 | quit |
| 253 | endif |
| 254 | |
| 255 | while true |
| 256 | var res = input("Save the changed key codes (y/n)? ") |
| 257 | if res == 'n' |
| 258 | quit |
| 259 | endif |
| 260 | if res == 'y' |
| 261 | WriteKeycodes() |
| 262 | quit |
| 263 | endif |
| 264 | echo 'invalid reply' |
| 265 | endwhile |
| 266 | enddef |
| 267 | |
| 268 | # The main loop |
| 269 | while true |
| 270 | var action = inputlist(['Select operation:', |
| 271 | '1. List results', |
| 272 | '2. Add results for a new terminal', |
| 273 | '3. Replace results', |
| 274 | '4. Quit', |
| 275 | ]) |
| 276 | echo "\n" |
| 277 | if action == 1 |
| 278 | ActionList() |
| 279 | elseif action == 2 |
| 280 | ActionAdd() |
| 281 | elseif action == 3 |
| 282 | ActionReplace() |
| 283 | elseif action == 4 |
| 284 | ActionQuit() |
| 285 | endif |
| 286 | endwhile |