- Common Lisp 100%
To the latest commit of cl-ffi, whose allocate boxes no pointer, of Pyssel's step 25. |
||
|---|---|---|
| ffi | ||
| src | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| cl-win32.asd | ||
| CLAUDE.md | ||
| LICENSE | ||
| README.org | ||
| stiftfile | ||
cl-win32
- Windows and messages
- Displays and DPI
- Keyboard and pointer
- Input methods
- The clipboard
- Events
- Errors
- The raw layer
- Development
- License
Win32 for Common Lisp: window classes and window procedures, windows, message loops, DPI, and keyboard and pointer state. Handles are integers, strings are Lisp strings, and failures are conditions.
(asdf:load-system :cl-win32)
(defpackage #:example
(:use #:cl)
(:local-nicknames (#:win32 #:cl-win32)))
(in-package #:example)
;; Windows calls this with each message for the windows of its classes.
(win32:define-window-procedure example-procedure (window message wparam lparam)
(declare (ignore wparam lparam))
(cond ((= message win32:+wm-close+) (win32:destroy-window window) 0)
((= message win32:+wm-destroy+) (win32:post-quit-message 7) 0)
;; NIL: the default handling.
(t nil)))
(win32:set-thread-dpi-awareness :per-monitor-aware-v2)
(win32:register-window-class "example" 'example-procedure)
(let ((window (win32:create-window "example" :title "Hello" :width 640 :height 400)))
(win32:show-window window)
(prog1 (list (win32:window-text window)
(win32:window-dpi window)
;; As if the user closed the window.
(progn (win32:post-message window win32:+wm-close+)
(win32:run-message-loop)))
(win32:unregister-window-class "example")))
;; => ("Hello" 96 7) on a display at 100%
Windows and messages
A window class names a window procedure. Define one with
define-window-procedure: Windows calls it with the window's handle, the
message, such as +wm-size+, and the message's two parameters, all
integers. It returns the message's result, or nil for the default
handling. register-window-class registers a class, and create-window
makes a window of it: :styles such as :overlapped-window (a title bar
and borders) and :visible, keywords of the type window-style, or
:parent :message for a message-only window, which only receives
messages. A :popup window, without a frame, made with another as its
:parent, is owned by it: it stays above it and goes with it, as a menu
does, and with the :extended-styles :no-activate and :tool-window
it is not activated when clicked and has no button on the taskbar.
destroy-window and unregister-window-class release them.
A window belongs to the thread that created it. Only that thread receives
its messages, and only that thread may destroy it. run-message-loop hands
the thread's messages to their window procedures until
post-quit-message; process-messages hands over those waiting and
returns. post-message puts a message on a window's queue, from any
thread; send-message calls its window procedure and waits for the result,
on the window's thread. peek-message looks at the next message without
taking it. message-time and message-position say when the message being
handled was sent, and where the pointer was then.
window-text, client-size, window-bounds, set-window-bounds,
show-window and validate-window do what their names say.
change-window-styles adds and removes styles, keeping the rest, and
has Windows draw the frame anew. window-placement says where a window
goes when neither minimised nor maximised and whether it is either, and
set-window-placement puts it back so.
window-thread-id names a window's thread, as current-thread-id names the
caller's. (setf window-enabled-p) disables a window's input from the
mouse and keyboard, as a modal dialog disables its owner while it is open,
and enables it again; window-enabled-p tells.
low-word, high-word, signed-low-word and signed-high-word take
parameters apart, as LOWORD and GET_X_LPARAM do; make-lparam puts one
together. read-rect reads the RECT a parameter points to, as
+wm-dpichanged+'s does.
Displays and DPI
display-work-area returns the part of a display that windows may cover,
without the taskbar, for the display a rectangle on the screen is on, to
keep a window such as a menu on the screen. display-bounds returns all
of the display a window is on, which a fullscreen window covers:
(win32:change-window-styles window :remove '(:overlapped-window :maximize))
(multiple-value-bind (x y width height) (win32:display-bounds window)
(win32:set-window-bounds window :x x :y y :width width :height height))
Keep window-placement before, to give the window its frame and place
back after.
set-thread-dpi-awareness with :per-monitor-aware-v2 has Windows report
sizes and positions in pixels for the windows the thread creates from then
on, and send +wm-dpichanged+ when one moves to a display of another DPI.
window-dpi is the DPI of a window's display, 96 (+default-dpi+) at
100%. window-size-for-client-size adds a title bar and borders to a size.
Keyboard and pointer
Virtual keys are keywords: the names of their VK_ constants without the
prefix, such as :shift, :lcontrol, :return, :f1, and :key-a and
:key-1 for letters and digits. virtual-key-name and virtual-key-code
convert. key-state says whether a key is down and toggled, as of the
message being handled; map-virtual-key converts between virtual keys,
scancodes and characters for the keyboard layout.
track-pointer-leave asks for +wm-mouseleave+. set-capture and
release-capture keep the pointer's messages for a window during a drag.
system-cursor returns a system cursor, such as :ibeam or :size-we,
and set-cursor shows it; a window procedure keeps its own cursor by
calling set-cursor for +wm-setcursor+ when LPARAM's low word is
+htclient+, and returning 1.
screen-to-client and client-to-screen convert positions.
cursor-position and set-cursor-position read and move the pointer on
the screen. clip-cursor keeps the pointer within a rectangle, and
cursor-clip says which; the pointer is the desktop's, so let it go when
the window loses the focus. register-raw-mouse has the mice send their
own motion to a window as +wm-input+, which raw-mouse-motion reads in
counts, without the pointer's acceleration and whether or not the pointer
can move: with the pointer clipped to one point and hidden, a game's
captured pointer. A window
procedure answers +wm-mouseactivate+ with +ma-noactivate+ to stay
inactive when clicked, and +wm-nchittest+ with +httransparent+ to let
the pointer through to the window below. system-metric answers questions
such as :mouse-present.
Input methods
An input method composes text from what is typed, such as Chinese or
Japanese, and the user chooses the result from its candidate list. A
window's input method is enabled when it is made; disable-input-method
and enable-input-method turn it off while no text field has the focus
and on again. To show the text being composed in the window yourself:
- In
+wm-ime-setcontext+, clear+isc-showuicompositionwindow+from LPARAM before the default handling, and answer+wm-ime-startcomposition+with 0, so the input method draws no composition window of its own. - In
+wm-ime-composition+, readinput-method-composition, the text and its caret, when LPARAM holds+gcs-compstr+, andinput-method-result, the text chosen, when it holds+gcs-resultstr+; answer with 0, or Windows sends the result again as+wm-char+. set-input-method-caretplaces the candidate list beside the caret.
cancel-input-method-composition drops the text being composed. Call
these from the window's thread.
The clipboard
clipboard-text returns the text on the clipboard, or nil if it holds
none, and setf puts text there. Both take a window, which Windows makes
the clipboard's owner when text is put there, and wait up to :timeout
seconds while another program has the clipboard open:
(setf (win32:clipboard-text window) "Copied")
(win32:clipboard-text window) ; => "Copied"
Windows' text has CR LF between lines; cl-win32 neither adds nor removes
them. While the session is locked, Windows gives no program the
clipboard, and both signal win32-error.
Events
make-event makes an event object, a flag one thread sets with set-event
and others wait for with wait-for-event, which takes a timeout in
seconds. with-event closes it on leaving.
Errors
A failed Win32 call signals win32-error, with the function's name, the
error code and Windows' description of it:
DestroyWindow failed with Windows error 1400: Invalid window handle.
An argument of the wrong type signals a type-error before Windows is
called: each function declaims the full domain of its arguments, so a
window, an event or a cursor is never 0, which names nothing, and a width
or a height is never negative, and SBCL warns of a constant outside it
when it compiles the call. The keywords the functions take are types,
such as window-style, show-command and system-cursor-name, so an
unknown one is refused with the ones there are:
:SIDEWAYS, among the styles (:OVERLAPPED-WINDOW :SIDEWAYS), is not a window style: :OVERLAPPED, :POPUP, ...
An error in a window procedure cannot unwind through the Windows code that
called it. It is caught there, the message gets the default handling, and
the error is signalled again from the cl-win32 function during which
Windows called the procedure: run-message-loop, process-messages,
send-message, create-window, destroy-window and the like.
run-message-loop may be called again to go on.
The raw layer
cl-win32/ffi binds the C functions one to one, with handles as integers
and strings as Lisp strings, of the cl-ffi type wide-string, which
cl-ffi copies to UTF-16 for each call. Use it for what cl-win32 does
not cover.
Development
License
MIT. See LICENSE.