This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Keyboard and Pointer
Virtual keys
Windows names keys by virtual keys: codes the keyboard layout assigns,
so the key that types Q on a French keyboard is the virtual key A. cl-win32
names them with keywords, the names of their VK_ constants in Lisp case
without the prefix:
| Virtual key | Keyword |
|---|---|
VK_SHIFT, VK_LSHIFT, VK_RSHIFT |
:shift, :lshift, :rshift |
VK_CONTROL, VK_MENU (Alt) |
:control, :menu |
VK_RETURN, VK_ESCAPE, VK_BACK |
:return, :escape, :back |
VK_PRIOR, VK_NEXT (Page Up, Down) |
:prior, :next |
VK_CAPITAL (Caps Lock) |
:capital |
VK_F1 … VK_F24 |
:f1 … :f24 |
VK_NUMPAD0 … VK_NUMPAD9 |
:numpad0 … :numpad9 |
VK_OEM_PLUS, VK_OEM_1 |
:oem-plus, :oem-1 |
| the letter and digit keys | :key-a … :key-z, :key-0 … :key-9 |
(win32:virtual-key-name 13) ; => :RETURN
(win32:virtual-key-name #x41) ; => :KEY-A
(win32:virtual-key-code :lcontrol) ; => 162
virtual-key-name takes a key message's wparam and allocates nothing.
Functions that take a virtual key take either the keyword or the code.
Key messages
A key going down sends +wm-keydown+, coming up +wm-keyup+; with Alt
held, or for F10, +wm-syskeydown+ and +wm-syskeyup+. wparam is the
virtual key. lparam holds more:
Bits of lparam |
Meaning |
|---|---|
| 0–15 | repeat count |
| 16–23 | the scancode: the key's place on the keyboard, whatever the layout |
| 24 | set for an extended key: the right Control and Alt, the arrows and navigation keys apart from the keypad's, keypad Enter and / |
| 29 | set if Alt is held |
| 30 | set if the key was already down: a repeat |
| 31 | set for a release |
((= message win32:+wm-keydown+)
(let ((scancode (ldb (byte 8 16) lparam))
(extended (logbitp 24 lparam))
(repeat (logbitp 30 lparam)))
(press (win32:virtual-key-name wparam) scancode extended repeat))
0)
Windows repeats held keys itself, with bit 30 set.
Return nil for +wm-syskeydown+ with F4 to let Alt+F4 close the window,
through +wm-close+. Returning nil for other system keys has Windows
open the window menu when Alt is released.
Text
The message loops of cl-win32 translate key messages: after a key press
that types a character, Windows posts +wm-char+ with it, dead keys and
AltGr taken into account. wparam is a UTF-16 code unit, so a character
outside the Basic Multilingual Plane, such as 𝄞, arrives as two +wm-char+
messages, a high surrogate (#xD800–#xDBFF) and then a low one
(#xDC00–#xDFFF), to be joined:
(+ #x10000 (ash (- high #xD800) 10) (- low #xDC00))
Control+letter types control characters, below 32. Keys typed with Alt
send +wm-syschar+; return 0 for it, or Windows beeps.
Key state
(win32:key-state :shift) ; => T, NIL: down, not toggled
(win32:key-state :capital) ; => NIL, T: up, Caps Lock on
key-state says whether a key is down and whether it is toggled on, as of
the message being handled: the state at the time of the message, not now.
Scancodes and layouts
(win32:map-virtual-key :escape :virtual-key-to-scancode) ; => 1
(win32:map-virtual-key :rcontrol :virtual-key-to-scancode-ex) ; => #xE01D
(win32:map-virtual-key #x2A :scancode-to-virtual-key-ex) ; => #xA0, the left Shift
(win32:map-virtual-key :oem-minus :virtual-key-to-character) ; => 45 (#\-) on most layouts
map-virtual-key converts for the calling thread's keyboard layout; the
translations are the type key-translation.
:virtual-key-to-character gives the character a key types unshifted,
with bit 31 set for a dead key. The -ex translations tell left and right
keys apart, and give extended scancodes with #xE0 in the high byte.
AltGr
On layouts with AltGr, Windows sends the right Alt with a made-up left
Control just before it, at the same message-time, so Control and Alt both
look held. To tell AltGr from a real Control, look at the message after
the left Control with peek-message: the right Alt (+wm-keydown+ or
+wm-syskeydown+, :menu, bit 24 set) at the same time means AltGr.
The pointer
+wm-mousemove+, the button messages and +wm-mousewheel+ carry
positions in lparam, as signed-low-word and signed-high-word. For
the wheel messages they are on the screen; screen-to-client turns them
into the window's, and client-to-screen the other way:
((= message win32:+wm-mousewheel+)
(let ((notches (/ (win32:signed-high-word wparam) win32:+wheel-delta+)))
(multiple-value-bind (x y) (win32:screen-to-client window
(win32:signed-low-word lparam)
(win32:signed-high-word lparam))
(scroll notches x y)))
0)
A wheel notch is +wheel-delta+, 120; touchpads send smaller steps.
Positive is away from the user for +wm-mousewheel+, right for
+wm-mousehwheel+. The extra buttons' messages say which button in
wparam's high word, +xbutton1+ (back) or +xbutton2+ (forward).
Leaving
Windows does not say when the pointer leaves a window unless asked:
(win32:track-pointer-leave window) ; +WM-MOUSELEAVE+ once the pointer leaves
It is posted once. Ask again the next time the pointer comes in, at the
first +wm-mousemove+ after a +wm-mouseleave+.
Capture
During a drag, keep the pointer's messages even when the pointer leaves the window:
(win32:set-capture window) ; on the button press
(win32:release-capture) ; on the release
(win32:capture) ; => the window with the capture, or NIL
A window may lose the capture before the release, when another window
takes it or the user switches windows; it then gets
+wm-capturechanged+, and the release will not come.
Cursors
(win32:set-cursor (win32:system-cursor :ibeam)) ; over text
(win32:set-cursor nil) ; hide the pointer
system-cursor returns a system cursor, in the user's cursor scheme:
:arrow; :ibeam, over text; :hand, over a link; :wait, busy;
:app-starting, busy in the background; :cross; :up-arrow; :help;
:no, not allowed; :size-all, moving; and :size-we, :size-ns,
:size-nwse and :size-nesw, resizing: the type system-cursor-name.
System cursors are shared: never destroy one, and system-cursor
allocates nothing, so calling it for each +wm-setcursor+ costs nothing. set-cursor shows a cursor, or hides the pointer for nil,
and returns the cursor shown before.
Windows asks a window for its cursor with +wm-setcursor+ whenever the
pointer moves over it. To keep its own cursor in its client area, a window
procedure answers it for +htclient+, the low word of lparam, by calling
set-cursor and returning 1, and leaves the rest, such as the borders'
resizing cursors, to the default:
((and (= message win32:+wm-setcursor+)
(= (win32:low-word lparam) win32:+htclient+))
(win32:set-cursor (win32:system-cursor *cursor*)) ; the cursor the program wants now
1)
The class's :cursor, the arrow by default, is what the default handling
shows elsewhere. Call set-cursor from the thread of the window the
pointer is over.
Holding the pointer
The pointer is the desktop's, and a program holds it only while it has the focus, as a game does:
(win32:cursor-position) ; x, y on the screen
(win32:set-cursor-position 800 450)
(win32:clip-cursor 700 400 200 100) ; keep it within a rectangle on the screen
(win32:cursor-clip) ; => 700, 400, 200, 100
(win32:clip-cursor) ; let it go anywhere again
cursor-positionandset-cursor-positionread and move the pointer on the screen, in pixels. Move it only as the user asks.clip-cursorkeeps the pointer within a rectangle, or lets it go anywhere when given none;cursor-clipsays which rectangle, all of the screens while nothing keeps it. Let the pointer go when the window loses the focus.
Raw mouse motion
(win32:register-raw-mouse window) ; the mice send their motion as +WM-INPUT+
((= message win32:+wm-input+)
(multiple-value-bind (dx dy) (win32:raw-mouse-motion lparam)
(when dx (look-around dx dy)))
nil) ; let the default handling clean up
register-raw-mouse has the mice send their own motion to a window as
+wm-input+ while the program is in the foreground. raw-mouse-motion
reads it from the message's lparam, in the devices' own counts, without
the pointer's acceleration, and whether or not the pointer can move; it
returns nil twice for input that is not a mouse's relative motion. It
allocates nothing. A program has one such window, the last registered;
unregister-raw-mouse stops it.
With the pointer clipped to one point and hidden, and the motion read raw, a window has a game's captured pointer.
System metrics
(win32:system-metric :mouse-present) ; => nonzero with a pointing device
The metrics, the type system-metric-name: :screen-width,
:screen-height, :mouse-present, :mouse-buttons,
:mouse-wheel-present, :swap-buttons.