Mercurial > vim
view src/testdir/keycode_check.vim @ 34074:1629cc65d78d v9.1.0006
patch 9.1.0006: is*() and to*() function may be unsafe
Commit: https://github.com/vim/vim/commit/184f71cc6868a240dc872ed2852542bbc1d43e28
Author: Keith Thompson <Keith.S.Thompson@gmail.com>
Date: Thu Jan 4 21:19:04 2024 +0100
patch 9.1.0006: is*() and to*() function may be unsafe
Problem: is*() and to*() function may be unsafe
Solution: Add SAFE_* macros and start using those instead
(Keith Thompson)
Use SAFE_() macros for is*() and to*() functions
The standard is*() and to*() functions declared in <ctype.h> have
undefined behavior for negative arguments other than EOF. If plain char
is signed, passing an unchecked value from argv for from user input
to one of these functions has undefined behavior.
Solution: Add SAFE_*() macros that cast the argument to unsigned char.
Most implementations behave sanely for negative arguments, and most
character values in practice are non-negative, but it's still best
to avoid undefined behavior.
The change from #13347 has been omitted, as this has already been
separately fixed in commit ac709e2fc0db6d31abb7da96f743c40956b60c3a
(v9.0.2054)
fixes: #13332
closes: #13347
Signed-off-by: Keith Thompson <Keith.S.Thompson@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Thu, 04 Jan 2024 21:30:04 +0100 |
parents | dbec60b8c253 |
children |
line wrap: on
line source
vim9script # Script to get various codes that keys send, depending on the protocol used. # # Usage: vim -u NONE -S keycode_check.vim # # Author: Bram Moolenaar # Last Update: 2022 Nov 15 # # The codes are stored in the file "keycode_check.json", so that you can # compare the results of various terminals. # # You can select what protocol to enable: # - None # - modifyOtherKeys level 2 # - kitty keyboard protocol # Change directory to where this script is, so that the json file is found # there. exe 'cd ' .. expand('<sfile>:h') echo 'working in directory: ' .. getcwd() const filename = 'keycode_check.json' # Dictionary of dictionaries with the results in the form: # {'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'}, # 'xterm2': {protocol: 'mok2', 'Tab': '09', 'S-Tab': '09'}, # 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '09'}, # } # The values are in hex form. var keycodes = {} if filereadable(filename) keycodes = readfile(filename)->join()->json_decode() else # Use some dummy entries to try out with keycodes = { 'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'}, 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '1b5b393b3275'}, } endif var orig_keycodes = deepcopy(keycodes) # used to detect something changed # Write the "keycodes" variable in JSON form to "filename". def WriteKeycodes() # If the file already exists move it to become the backup file. if filereadable(filename) if rename(filename, filename .. '~') echoerr $'Renaming {filename} to {filename}~ failed!' return endif endif if writefile([json_encode(keycodes)], filename) != 0 echoerr $'Writing {filename} failed!' endif enddef # The key entries that we want to list, in this order. # The first item is displayed in the prompt, the second is the key in # the keycodes dictionary. var key_entries = [ ['Tab', 'Tab'], ['Shift-Tab', 'S-Tab'], ['Ctrl-Tab', 'C-Tab'], ['Alt-Tab', 'A-Tab'], ['Ctrl-I', 'C-I'], ['Shift-Ctrl-I', 'S-C-I'], ['Esc', 'Esc'], ['Shift-Esc', 'S-Esc'], ['Ctrl-Esc', 'C-Esc'], ['Alt-Esc', 'A-Esc'], ['Space', 'Space'], ['Shift-Space', 'S-Space'], ['Ctrl-Space', 'C-Space'], ['Alt-Space', 'A-Space'], ] # Given a terminal name and a item name, return the text to display. def GetItemDisplay(term: string, item: string): string var val = get(keycodes[term], item, '') # see if we can pretty-print this one var pretty = val if val[0 : 1] == '1b' pretty = 'ESC' var idx = 2 if val[0 : 3] == '1b5b' pretty = 'CSI' idx = 4 endif var digits = false while idx < len(val) var cc = val[idx : idx + 1] var nr = str2nr('0x' .. cc, 16) idx += 2 if nr >= char2nr('0') && nr <= char2nr('9') if !digits pretty ..= ' ' endif digits = true pretty ..= cc[1] else if nr == char2nr(';') && digits # don't use space between semicolon and digits to keep it short pretty ..= ';' else digits = false if nr >= char2nr(' ') && nr <= char2nr('~') # printable character pretty ..= ' ' .. printf('%c', nr) else # non-printable, use hex code pretty = val break endif endif endif endwhile endif return pretty enddef # Action: list the information in "keycodes" in a more or less nice way. def ActionList() var terms = keys(keycodes) if len(terms) == 0 echo 'No terminal results yet' return endif sort(terms) var items = ['protocol', 'version', 'kitty', 'modkeys'] + key_entries->copy()->map((_, v) => v[1]) # For each terminal compute the needed width, add two. # You may need to increase the terminal width to avoid wrapping. var widths = [] for [idx, term] in items(terms) widths[idx] = len(term) + 2 endfor for item in items for [idx, term] in items(terms) var l = len(GetItemDisplay(term, item)) if widths[idx] < l + 2 widths[idx] = l + 2 endif endfor endfor # Use one column of width 10 for the item name. echo "\n" echon ' ' for [idx, term] in items(terms) echon printf('%-' .. widths[idx] .. 's', term) endfor echo "\n" for item in items echon printf('%8s ', item) for [idx, term] in items(terms) echon printf('%-' .. widths[idx] .. 's', GetItemDisplay(term, item)) endfor echo '' endfor echo "\n" enddef # Convert the literal string after "raw key input" into hex form. def Literal2hex(code: string): string var hex = '' for i in range(len(code)) hex ..= printf('%02x', char2nr(code[i])) endfor return hex enddef def GetTermName(): string var name = input('Enter the name of the terminal: ') return name enddef # Gather key codes for terminal "name". def DoTerm(name: string) var proto = inputlist([$'What protocol to enable for {name}:', '1. None', '2. modifyOtherKeys level 2', '3. kitty', ]) echo "\n" &t_TE = "\<Esc>[>4;m" var proto_name = 'unknown' if proto == 1 # Request the XTQMODKEYS value and request the kitty keyboard protocol status. &t_TI = "\<Esc>[?4m" .. "\<Esc>[?u" proto_name = 'none' elseif proto == 2 # Enable modifyOtherKeys level 2 and request the XTQMODKEYS value. &t_TI = "\<Esc>[>4;2m" .. "\<Esc>[?4m" proto_name = 'mok2' elseif proto == 3 # Enable Kitty keyboard protocol and request the status. &t_TI = "\<Esc>[>1u" .. "\<Esc>[?u" proto_name = 'kitty' else echoerr 'invalid protocol choice' return endif # Append the request for the version response, this is used to check we have # the results. &t_TI ..= "\<Esc>[>c" # Pattern that matches the line with the version response. const version_pattern = "\<Esc>\\[>\\d\\+;\\d\\+;\\d*c" # Pattern that matches the XTQMODKEYS response: # CSI > 4;Pv m # where Pv indicates the modifyOtherKeys level const modkeys_pattern = "\<Esc>\\[>4;\\dm" # Pattern that matches the line with the status. Currently what terminals # return for the Kitty keyboard protocol. const kitty_status_pattern = "\<Esc>\\[?\\d\\+u" ch_logfile('keylog', 'w') # executing a dummy shell command will output t_TI !echo >/dev/null # Wait until the log file has the version response. var startTime = reltime() var seenVersion = false while !seenVersion var log = readfile('keylog') if len(log) > 2 for line in log if line =~ 'raw key input' var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '') if code =~ version_pattern seenVersion = true echo 'Found the version response' break endif endif endfor endif if reltime(startTime)->reltimefloat() > 3 # break out after three seconds break endif endwhile echo 'seenVersion: ' seenVersion # Prepare the terminal entry, set protocol and clear status and version. if !has_key(keycodes, name) keycodes[name] = {} endif keycodes[name]['protocol'] = proto_name keycodes[name]['version'] = '' keycodes[name]['kitty'] = '' keycodes[name]['modkeys'] = '' # Check the log file for a status and the version response ch_logfile('', '') var log = readfile('keylog') delete('keylog') for line in log if line =~ 'raw key input' var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '') # Check for the XTQMODKEYS response. if code =~ modkeys_pattern var modkeys = substitute(code, '.*\(' .. modkeys_pattern .. '\).*', '\1', '') # We could get the level out of the response, but showing the response # itself provides more information. # modkeys = substitute(modkeys, '.*4;\(\d\)m', '\1', '') if keycodes[name]['modkeys'] != '' echomsg 'Another modkeys found after ' .. keycodes[name]['modkeys'] endif keycodes[name]['modkeys'] = modkeys endif # Check for kitty keyboard protocol status if code =~ kitty_status_pattern var status = substitute(code, '.*\(' .. kitty_status_pattern .. '\).*', '\1', '') # use the response itself as the status status = Literal2hex(status) if keycodes[name]['kitty'] != '' echomsg 'Another status found after ' .. keycodes[name]['kitty'] endif keycodes[name]['kitty'] = status endif if code =~ version_pattern var version = substitute(code, '.*\(' .. version_pattern .. '\).*', '\1', '') keycodes[name]['version'] = Literal2hex(version) break endif endif endfor echo "For Alt to work you may need to press the Windows/Super key as well" echo "When a key press doesn't get to Vim (e.g. when using Alt) press x" # The log of ignored typeahead is left around for debugging, start with an # empty file here. delete('keylog-ignore') for entry in key_entries # Consume any typeahead. Wait a bit for any responses to arrive. ch_logfile('keylog-ignore', 'a') while 1 sleep 100m if getchar(1) == 0 break endif while getchar(1) != 0 getchar() endwhile endwhile ch_logfile('', '') ch_logfile('keylog', 'w') echo $'Press the {entry[0]} key (q to quit):' var r = getcharstr() ch_logfile('', '') if r == 'q' break endif log = readfile('keylog') delete('keylog') if len(log) < 2 echoerr 'failed to read result' return endif var done = false for line in log if line =~ 'raw key input' var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '') # Remove any version termresponse code = substitute(code, version_pattern, '', 'g') # Remove any XTGETTCAP replies. const cappat = "\<Esc>P[01]+\\k\\+=\\x*\<Esc>\\\\" code = substitute(code, cappat, '', 'g') # Remove any kitty status reply code = substitute(code, kitty_status_pattern, '', 'g') if code == '' continue endif # Convert the literal bytes into hex. If 'x' was pressed then clear # the entry. var hex = '' if code != 'x' hex = Literal2hex(code) endif keycodes[name][entry[1]] = hex done = true break endif endfor if !done echo 'Code not found in log' endif endfor enddef # Action: Add key codes for a new terminal. def ActionAdd() var name = input('Enter name of the terminal: ') echo "\n" if index(keys(keycodes), name) >= 0 echoerr $'Terminal {name} already exists' return endif DoTerm(name) enddef # Action: Replace key codes for an already known terminal. def ActionReplace() var terms = keys(keycodes) if len(terms) == 0 echo 'No terminal results yet' return endif var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg)) echo "\n" if choice > 0 && choice <= len(terms) DoTerm(terms[choice - 1]) else echo 'invalid index' endif enddef # Action: Clear key codes for an already known terminal. def ActionClear() var terms = keys(keycodes) if len(terms) == 0 echo 'No terminal results yet' return endif var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg)) echo "\n" if choice > 0 && choice <= len(terms) remove(keycodes, terms[choice - 1]) else echo 'invalid index' endif enddef # Action: Quit, possibly after saving the results first. def ActionQuit() # If nothing was changed just quit if keycodes == orig_keycodes quit endif while true var res = input("Save the changed key codes (y/n)? ") if res == 'n' quit endif if res == 'y' WriteKeycodes() quit endif echo 'invalid reply' endwhile enddef # The main loop while true var action = inputlist(['Select operation:', '1. List results', '2. Add results for a new terminal', '3. Replace results', '4. Clear results', '5. Quit', ]) echo "\n" if action == 1 ActionList() elseif action == 2 ActionAdd() elseif action == 3 ActionReplace() elseif action == 4 ActionClear() elseif action == 5 ActionQuit() endif endwhile