1#!/bin/zsh -f
2
3[[ -o interactive ]] && {
4    local -hi ARGC		# local is a no-op outside of a function
5    (ARGC=0) 2>/dev/null || {	# so ARGC remains read-only for "source"
6        print -u2 ${0}: must be run as a function or shell script, not sourced
7        return 1
8    }
9}
10
11emulate -RL zsh
12local zkbd term key seq
13
14zkbd=${ZDOTDIR:-$HOME}/.zkbd
15[[ -d $zkbd ]] || mkdir $zkbd || return 1
16
17trap 'unfunction getmbkey getseq; command rm -f $zkbd/$TERM.tmp' 0
18trap "return 1" 1 2 15
19
20getmbkey () {
21    local k='' i
22    for ((i=10; i>0; --i))
23    do
24	read -t -k 1 k && break
25	sleep 1
26    done
27    [[ -n $k ]] || return 1
28    [[ $k = $'\012' || $k = $'\015' || $k = ' ' ]] && return 0
29    # We might not be done yet, thanks to multibyte characters
30    local mbk=$k
31    while read -t -k 1 k
32    do
33       mbk=$mbk$k 
34    done
35    print -Rn $mbk
36}
37
38getseq () {
39    trap "stty ${$(stty -g 2>/dev/null):-echo -raw}" 0 1 2 15
40    stty raw -echo
41    local k='' seq='' i
42    for ((i=10; i>0; --i))
43    do
44	read -t -k 1 k && break
45	sleep 1
46    done
47    [[ -n $k ]] || return 1
48    [[ $k = $'\012' || $k = $'\015' || $k = ' ' ]] && return 0
49    seq=$k
50    while read -t -k 1 k
51    do
52       seq=$seq$k 
53    done
54    print -Rn ${(V)seq}
55}
56
57read term"?Enter current terminal type: [$TERM] "
58[[ -n $term ]] && TERM=$term
59print 'typeset -g -A key\n' > $zkbd/$TERM.tmp || return 1
60
61cat <<\EOF
62
63We will now test some features of your keyboard and terminal.
64
65If you do not press the requested keys within 10 seconds, key reading will
66abort.  If your keyboard does not have a requested key, press Space to
67skip to the next key.
68
69EOF
70
71local ctrl alt meta
72
73print -n "Hold down Ctrl and press X: "
74ctrl=$(getmbkey) || return 1
75print
76
77if [[ $ctrl != $'\030' ]]
78then
79    print "Your keyboard does not have a working Ctrl key?"
80    print "Giving up ..."
81    return 1
82else
83    print
84fi
85
86print "Your Meta key may have a Microsoft Windows logo on the cap."
87print -n "Hold down Meta and press X: "
88meta=$(getmbkey) || return 1
89print
90
91if [[ $meta == x ]]
92then
93    print "Your keyboard or terminal does not recognize the Meta key."
94    unset meta
95elif [[ $meta > $'\177' ]]
96then
97    print "Your keyboard uses the Meta key to send high-order characters."
98else
99    unset meta
100fi
101print
102
103print -n "Hold down Alt and press X: "
104alt=$(getmbkey) || return 1
105print
106
107if [[ $alt == x ]]
108then
109    print "Your keyboard or terminal does not recognize the Alt key."
110    unset alt
111elif [[ $alt == $meta ]]
112then
113    print "Your keyboard does not distinguish Alt from Meta."
114elif [[ $alt > $'\177' ]]
115then
116    print "Your keyboard uses the Alt key to send high-order characters."
117else
118    unset alt
119fi
120
121if (( $+alt + $+meta == 0 ))
122then
123    print $'\n---------\n'
124    if [[ -o multibyte ]]
125    then cat <<EOF
126You are using zsh in MULTIBYTE mode to support modern character sets (for
127languages other than English).  To use the Meta or Alt keys, you probably
128need to revert to single-byte mode with a command such as
129
130    unsetopt MULTIBYTE
131EOF
132    else cat <<EOF
133Your current terminal and keyboard configuration does not appear to use
134high-order characters.  You may be able to enable the Meta or Alt keys
135with a command such as
136
137    stty pass8
138EOF
139    fi
140    cat <<EOF
141
142If you want to use these extra keys with zsh, try adding the above command
143to your ${ZDOTDIR:-$HOME}/.zshrc file.
144
145See also "man stty" or the documentation for your terminal or emulator.
146EOF
147fi
148
149(( $+alt || $+meta )) && cat <<EOF
150
151---------
152
153You may enable keybindings that use the \
154${meta:+Meta}${meta:+${alt:+ and }}${alt:+Alt} key${meta:+${alt:+s}} \
155by adding
156
157    bindkey -m
158
159to your ${ZDOTDIR:-$HOME}/.zshrc file.
160
161EOF
162
163read -k 1 key"?Press a key to proceed: "
164[[ $key != $'\n' ]] && print
165
166cat <<\EOF
167
168---------
169
170You will now be asked to press in turn each of the 12 function keys, then
171the Backspace key, the 6 common keypad keys found on typical PC keyboards,
172plus the 4 arrow keys, and finally the Menu key (near Ctrl on the right).
173If your keyboard does not have the requested key, press Space to skip to
174the next key.
175
176Do not type ahead!  Wait at least one second after pressing each key for
177zsh to read the entire sequence and prompt for the next key.  If a key
178sequence does not echo within 2 seconds after you press it, that key may
179not be sending any sequence at all.  In this case zsh is not able to make
180use of that key.  Press Space to skip to the next key.
181
182EOF
183
184read -k 1 key"?Press a key when ready to begin: "
185[[ $key != $'\n' ]] && print
186
187cat <<\EOF
188
189If you do not press a key within 10 seconds, key reading will abort.
190If you make a mistake, stop typing and wait, then run this program again.
191
192EOF
193
194# There are 509 combinations of the following three arrays that represent
195# possible keystrokes.  (Actually, Sun keyboards don't have Meta or Menu,
196# though some have R{1..12} keys as well, so really there are either 433
197# or 517 combinations; but some X11 apps map Shift-F{1..11} to emulate the
198# unmodified Sun keys, so really only the 345 PC combinations are usable.
199# Let's not even get into distinguishing Left and Right Shift/Alt/Meta.)
200# No one would ever want to type them all into this program (would they?),
201# so by default ask for the 23 unmodified PC keys.  If you uncomment more,
202# you should fix the introductory text above.
203
204local -a pckeys sunkeys modifiers
205pckeys=(F{1..12}
206        Backspace  Insert  Home   PageUp 
207                   Delete  End   PageDown
208                            Up
209                    Left   Down   Right
210        Menu
211       )
212sunkeys=(Stop  Again
213         Props Undo
214         Front Copy
215         Open  Paste
216         Find  Cut
217            Help
218        )
219modifiers=(Shift- # Control- Alt- Meta-
220           # Control-Shift- Alt-Shift- Meta-Shift-
221           # Control-Alt- Control-Meta- Alt-Meta-
222           # Control-Alt-Shift- Control-Meta-Shift-
223           # Alt-Meta-Shift- Control-Alt-Meta-Shift-
224          )
225
226exec 3>/dev/tty
227
228for key in $pckeys # $^modifiers$^pckeys $sunkeys $^modifiers$^sunkeys
229do
230    print -u3 -Rn "Press $key: "
231    seq="$(getseq)" || return 1
232    print "key[$key]='${(q)seq}'"
233    print -u3 -R $seq
234done >> $zkbd/$TERM.tmp
235
236source $zkbd/$TERM.tmp || return 1
237if [[ "${key[Delete]}" == "${key[Backspace]}" ]]
238then
239    print
240    print Warning: Backspace and Delete key both send "${(q)key[Delete]}"
241else
242    if [[ "${key[Delete]}" != "^?" ]]
243    then
244	print
245        print Warning: Delete key sends "${(q)key[Delete]}" '(not ^?)'
246    fi
247    if [[ "${key[Backspace]}" != "^H" ]]
248    then
249	print
250        print Warning: Backspace sends "${(q)key[Backspace]}"
251    fi
252fi
253
254local termID=${${DISPLAY:t}:-$VENDOR-$OSTYPE} termFile=$zkbd/$TERM.tmp
255command mv $termFile $zkbd/$TERM-$termID && termFile=$zkbd/$TERM-$termID
256
257cat <<EOF
258
259Parameter assignments for the keys you typed have been written to the file:
260$termFile
261
262You may read this file into ${ZDOTDIR:-$HOME}/.zshrc or another startup
263file with the "source" or "." commands, then reference the \$key parameter
264in bindkey commands, for example like this:
265
266    source ${(D)zkbd}/\$TERM-\${\${DISPLAY:t}:-\$VENDOR-\$OSTYPE}
267    [[ -n \${key[Left]} ]] && bindkey "\${key[Left]}" backward-char
268    [[ -n \${key[Right]} ]] && bindkey "\${key[Right]}" forward-char
269    # etc.
270
271Adjust the name of the file being sourced, as necessary.
272EOF
273