gui/gui-lib/mred/private/wx/win32/key.rkt
Matthew Flatt 282a22b8f0 add any-control+alt-is-altgr
Thanks to Bert De Ketelaere for helping to sort out this new
behavior.
2016-03-17 16:39:40 -06:00

399 lines
16 KiB
Racket

#lang racket/base
(require racket/class
ffi/unsafe
"utils.rkt"
"types.rkt"
"const.rkt"
"../common/event.rkt")
(provide
(protect-out maybe-make-key-event
generates-key-event?
reset-key-mapping
key-symbol-to-menu-key
any-control+alt-is-altgr))
(define-user32 GetKeyState (_wfun _int -> _SHORT))
(define-user32 MapVirtualKeyW (_wfun _UINT _UINT -> _UINT))
(define-user32 VkKeyScanW (_wfun _WCHAR -> _SHORT))
(define-user32 ToUnicode (_wfun _UINT _UINT _pointer _pointer _int _UINT -> _int))
(define-user32 GetKeyboardState (_wfun _pointer -> _BOOL))
(define control+alt-always-as-altgr? #f)
(define any-control+alt-is-altgr
(case-lambda
[() control+alt-always-as-altgr?]
[(on?) (set! control+alt-always-as-altgr? (and on? #t))]))
;; Back-door result from `key-mapped?` via `maybe-make-key-event`:
(define no-translate? #f)
;; Called to determine whether a WM_KEYDOWN event should
;; be passed to TranslateEvent() to get a WM_CHAR event.
;; If the WM_KEYDOWN event itself will translate to a
;; visible key event, then don't use TranslateEvent().
(define (generates-key-event? msg)
(let ([message (MSG-message msg)])
(and (or (eq? message WM_KEYDOWN)
(eq? message WM_SYSKEYDOWN)
(eq? message WM_KEYUP)
(eq? message WM_SYSKEYUP))
(or (maybe-make-key-event #t
(MSG-wParam msg)
(MSG-lParam msg)
#f
(or (= message WM_KEYUP)
(= message WM_SYSKEYUP))
(MSG-hwnd msg))
;; If ToUnicode() was used for checking, claim that
;; an event will be generated so that TranslateEvent()
;; is not used.
(begin0
no-translate?
(set! no-translate? #f))))))
(define (THE_SCAN_CODE lParam)
(bitwise-and (arithmetic-shift lParam -16) #x1FF))
(define generic_ascii_code (make-hasheq))
;; The characters in find_shift_alts are things that we'll try
;; to include in keyboard events as char-if-Shift-weren't-pressed,
;; char-if-AltGr-weren't-pressed, etc.
(define find_shift_alts (string-append
"!@#$%^&*()_+-=\\|[]{}:\";',.<>/?~`"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"))
(define other-key-codes #f)
(define (get-other-key-codes)
(or other-key-codes
(begin
(set! other-key-codes
(list->vector
(for/list ([i (in-string find_shift_alts)])
(VkKeyScanW (char->integer i)))))
other-key-codes)))
(define (reset-key-mapping)
(set! other-key-codes #f)
(set! mapped-keys (make-hash)))
(define (other-orig j)
(char->integer (string-ref find_shift_alts j)))
;; If a virtual key code has no mapping here, then the key should be
;; ignored by WM_KEYDOWN and processed by WM_CHAR instead
(define win32->symbol
(hasheq VK_CANCEL 'cancel
VK_BACK #\backspace
VK_TAB #\tab
VK_CLEAR 'clear
VK_RETURN #\return
VK_SHIFT 'shift
VK_CONTROL 'control
VK_MENU 'menu
VK_PAUSE 'pause
VK_SPACE #\space
VK_ESCAPE 'escape
VK_PRIOR 'prior
VK_NEXT 'next
VK_END 'end
VK_HOME 'home
VK_LEFT 'left
VK_UP 'up
VK_RIGHT 'right
VK_DOWN 'down
VK_SELECT 'select
VK_PRINT 'print
VK_EXECUTE 'execute
VK_INSERT 'insert
VK_DELETE #\rubout
VK_HELP 'help
VK_NUMPAD0 'numpad0
VK_NUMPAD1 'numpad1
VK_NUMPAD2 'numpad2
VK_NUMPAD3 'numpad3
VK_NUMPAD4 'numpad4
VK_NUMPAD5 'numpad5
VK_NUMPAD6 'numpad6
VK_NUMPAD7 'numpad7
VK_NUMPAD8 'numpad8
VK_NUMPAD9 'numpad9
VK_MULTIPLY 'multiply
VK_ADD 'add
VK_SUBTRACT 'subtract
VK_DECIMAL 'decimal
VK_DIVIDE 'divide
VK_F1 'f1
VK_F2 'f2
VK_F3 'f3
VK_F4 'f4
VK_F5 'f5
VK_F6 'f6
VK_F7 'f7
VK_F8 'f8
VK_F9 'f9
VK_F10 'f10
VK_F11 'f11
VK_F12 'f12
VK_F13 'f13
VK_F14 'f14
VK_F15 'f15
VK_F16 'f16
VK_F17 'f17
VK_F18 'f18
VK_F19 'f19
VK_F20 'f20
VK_F21 'f21
VK_F22 'f22
VK_F23 'f23
VK_F24 'f24
VK_NUMLOCK 'numlock
VK_SCROLL 'scroll))
(define (maybe-make-key-event just-check? wParam lParam is-char? is-up? hwnd)
(let* ([control-down? (not (zero? (arithmetic-shift (GetKeyState VK_CONTROL) -1)))]
[rcontrol-down? (and control-down?
(not (zero? (arithmetic-shift (GetKeyState VK_RCONTROL) -1))))]
[lcontrol-down? (and control-down?
(not (zero? (arithmetic-shift (GetKeyState VK_LCONTROL) -1))))]
[shift-down? (not (zero? (arithmetic-shift (GetKeyState VK_SHIFT) -1)))]
[rshift-down? (and shift-down?
(not (zero? (arithmetic-shift (GetKeyState VK_RSHIFT) -1))))]
[caps-down? (not (zero? (arithmetic-shift (GetKeyState VK_CAPITAL) -1)))]
[alt-down? (= (bitwise-and (HIWORD lParam) KF_ALTDOWN) KF_ALTDOWN)]
[ralt-down? (and alt-down?
(not (zero? (arithmetic-shift (GetKeyState VK_RMENU) -1))))]
[lalt-down? (and alt-down?
(not (zero? (arithmetic-shift (GetKeyState VK_LMENU) -1))))])
(let-values ([(id other-shift other-altgr other-shift-altgr)
(cond
[(symbol? wParam)
(values wParam #f #f #f)]
[is-char?
;; wParam is a character or symbol
(let ([id wParam]
[sc (THE_SCAN_CODE lParam)])
;; Remember scan codes to help with some key-release events:
(when (byte? id)
(hash-set! generic_ascii_code id sc))
;; Look for elements of find_shift_alts that have a different
;; shift/AltGr state:
(let ([k (MapVirtualKeyW sc 1)])
(if (zero? k)
(values (integer->char id) #f #f #f)
(for/fold ([id id][s #f][a #f][sa #f]) ([o (in-vector (get-other-key-codes))]
[j (in-naturals)])
(if (= (bitwise-and o #xFF) k)
;; Figure out whether it's different in the shift
;; for AltGr dimension, or both:
(if (eq? (zero? (bitwise-and o #x100)) shift-down?)
;; different Shift
(if (eq? (= (bitwise-and o #x600) #x600)
(and control-down? alt-down?))
;; same AltGr
(values id (other-orig j) a sa)
;; different AltGr
(values id s a (other-orig j)))
;; same Shift
(if (eq? (= (bitwise-and o #x600) #x600)
(and control-down? alt-down?))
;; same AltGr
(values id s a sa)
;; different AltGr
(values id s (other-orig j) sa)))
(values id s a sa))))))]
[else
;; wParam is a virtual key code
(let ([id (hash-ref win32->symbol wParam #f)]
[override-mapping? (and control-down?
;; not AltGR or no mapping:
(or (not alt-down?)
(not (or control+alt-always-as-altgr?
(and lcontrol-down?
ralt-down?)))
(not (key-mapped? wParam
(THE_SCAN_CODE lParam)
just-check?))))]
[try-generate-release
(lambda ()
(let ([sc (THE_SCAN_CODE lParam)])
(for/fold ([id #f]) ([i (in-range 256)] #:when (not id))
(and (equal? sc (hash-ref generic_ascii_code i #f))
(let ([id i])
(if (id . < . 127)
(char->integer (char-downcase (integer->char id)))
id))))))])
(if (not id)
(if (or override-mapping? is-up?)
;; Non-AltGr Ctl- combination, or a release event:
;; map manually, because the default mapping is
;; unsatisfactory
;; Set id to the unshifted key:
(let* ([id (bitwise-and (MapVirtualKeyW wParam 2) #xFFFF)]
[id (cond
[(zero? id) #f]
[(id . < . 128)
(char->integer (char-downcase (integer->char id)))]
[else id])])
(let-values ([(s a sa)
;; Look for shifted alternate:
(for/fold ([s #f][a #f][sa #f]) ([o (in-vector (get-other-key-codes))]
[j (in-naturals)])
(if (= (bitwise-and o #xFF) wParam)
(if (not (zero? (bitwise-and o #x100)))
(if (= (bitwise-and o #x600) #x600)
(values s a (other-orig j))
(values (other-orig j) a sa))
(if (= (bitwise-and o #x600) #x600)
(values s (other-orig j) sa)
(values s a sa)))
(values s a sa)))])
(if (and id shift-down?)
;; shift was pressed, so swap role of shifted and unshifted
(values s id sa a)
(values id s a sa))))
(values (and is-up? (try-generate-release)) #f #f #f))
(cond
[(and (not override-mapping?) (not is-up?)
;; Let these get translated to WM_CHAR or skipped
;; entirely:
(memq wParam
(list VK_ESCAPE VK_SPACE VK_RETURN VK_TAB VK_BACK)))
(values #f #f #f #f)]
[(and (not id) is-up?)
(values (try-generate-release) #f #f #f)]
[else
(values id #f #f #f)])))])])
(and id
(if just-check?
#t
(let* ([id (if (number? id) (integer->char id) id)]
[key-id (case id
[(#\033) 'escape]
[(shift) (if rshift-down?
'rshift
id)]
[(control) (if rcontrol-down?
'rcontrol
id)]
[else id])]
[e (new key-event%
[key-code (if is-up?
'release
key-id)]
[shift-down shift-down?]
[control-down control-down?]
[meta-down alt-down?]
[alt-down #f]
[x 0]
[y 0]
[time-stamp 0]
[caps-down caps-down?]
[control+meta-is-altgr (and control-down?
alt-down?
(or control+alt-always-as-altgr?
(and (not rcontrol-down?)
(not lalt-down?))))])]
[as-key (lambda (v)
(if (integer? v) (integer->char v) v))])
(when is-up?
(send e set-key-release-code key-id))
(when other-shift
(send e set-other-shift-key-code (as-key other-shift)))
(when other-altgr
(send e set-other-altgr-key-code (as-key other-altgr)))
(when other-shift-altgr
(send e set-other-shift-altgr-key-code (as-key other-shift-altgr)))
e))))))
(define (key-symbol-to-menu-key k)
(hash-ref keysyms k #f))
(define keysyms
'#hash((numpad5 . |Numpad 5|)
(numpad1 . |Numpad 1|)
(escape . Escape)
(right . Right)
(prior . Prior)
(cancel . Cancel)
(start . Start)
(f22 . F22)
(f17 . F17)
(f13 . F13)
(f8 . F8)
(f3 . F3)
(divide . Divide)
(add . Add)
(numpad8 . |Numpad 8|)
(numpad3 . |Numpad 3|)
(select . Select)
(down . Down)
(next . Next)
(clear . Clear)
(scroll . Scroll)
(f21 . F21)
(f16 . F16)
(f12 . F12)
(f9 . F9)
(f4 . F4)
(f1 . F1)
(separator . Separator)
(numpad9 . |Numpad 9|)
(numpad4 . |Numpad 4|)
(help . Help)
(execute . Execute)
(left . Left)
(end . End)
(menu . Menu)
(print . Print)
(f23 . F23)
(f18 . F18)
(f14 . F14)
(f7 . F7)
(f2 . F2)
(decimal . Decimal)
(multiply . Multiply)
(numpad7 . |Numpad 7|)
(numpad2 . |Numpad 2|)
(insert . Insert)
(snapshot . Snapshot)
(up . Up)
(home . Home)
(pause . Pause)
(f24 . F24)
(f19 . F19)
(f15 . F15)
(f11 . F11)
(f6 . F6)
(f5 . F5)
(subtract . Subtract)
(numpad-enter . |Numpad Enter|)
(numpad6 . |Numpad 6|)))
;; The `key-mapped?` function is used to predict whether an
;; AltGr combination will produce a key; if not, a key
;; event can be synthesized (like control combinations)
(define keys-state (make-bytes 256))
(define unicode-result (make-bytes 20))
(define mapped-keys (make-hash))
(define (key-mapped? vk sc just-check?)
(define key (vector vk sc))
(hash-ref mapped-keys
key
(lambda ()
(cond
[just-check?
;; In checking mode, we can use ToUnicode():
(GetKeyboardState keys-state)
(define n (ToUnicode vk sc keys-state unicode-result 10 0))
(when (= n -1)
;; For a dead char, ToUnicode() seems to have the effect
;; of TranslateEvent(), so avoid the latter.
(set! no-translate? #t))
(define mapped? (not (zero? n)))
;; Record what we learned for use by non-checking mode:
(hash-set! mapped-keys key mapped?)
mapped?]
[else #f]))))