This package provides a socket based lisp-only interface to X11. It started as an experiment but as I added support for XPutImage using BIG-REQUESTS it now seems more useful to me than CLX.
Connect will send a request to open a connection to the X-Server and
parses its response to obtain the constants *RESOURCE-ID-BASE*
,
*RESOURCE-ID-MASK*
and *ROOT*
. These are stored in dynamic variables
and are later used by other functions, e.g. by MAKE-WINDOW
to create
a new window.
-
[function] CONNECT &KEY (IP #(127 0 0 1)) (PORT 6000)
Initiate the connection with the X server. Use little endian, parse the servers initial response to obtaining root and resource-id-{base, mask} (for creating new window identifiers). Enable big requests (which just means that for some requests you can send zero in the 16-bit length field and use an additional 32-bit length field for the request instead).
-
[function] MAKE-WINDOW &KEY (WIDTH 512) (HEIGHT 512) (X 0) (Y 0)
Create a window with size
WIDTH
xHEIGHT
at position (X
Y
) using root. The window id is generated using*RESOURCE-ID-BASE*
and*RESOURCE-ID-MASK*
and returned.
-
[function] DRAW-WINDOW X1 Y1 X2 Y2
Draw a line from (x1 y1) to (x2 y2) in
*WINDOW*
.
-
[function] QUERY-POINTER
Ask the X server for the current cursor position. Returns the 4 multiple values (values root-x root-y win-x win-y).
-
[function] PUT-IMAGE-BIG-REQ IMG &KEY (DST-X 0) (DST-Y 0)
Create a PutImage request to write the 3D array
IMG
with dimensions (h w c) as a WxH image with 32 bits per pixel into*WINDOW*
using*GC*
.
-
[function] PARSE-INITIAL-REPLY R
Extracts root, resource-id-{base, mask} from the first response of the server and stores into dynamic variables.
-
[function] READ-REPLY-WAIT
The protocol specification states:
Every reply contains a 32-bit length field expressed in units of four bytes. Every reply consists of 32 bytes followed by zero or more additional bytes of data, as specified in the length field. Unused bytes within a reply are not guaranteed to be zero. Every reply also contains the least significant 16 bits of the sequence number of the corresponding request. (this is implicitly assigned)
This code first reads 32 bytes from the socket s. It parses the reply length and if necessary reads the rest of the reply packet.
-
[variable] *S* NIL
Socket for communication with X server.
- [variable] *RESOURCE-ID-BASE* NIL
- [variable] *RESOURCE-ID-MASK* NIL
-
[variable] *ROOT* NIL
Root ID as extracted from the initial reply of the X server.
- [variable] *WINDOW* NIL
-
[function] BIG-REQUESTS-ENABLE
If it hasn't been done so far, obtain the opcode for BIG-REQUESTS and then issue an enable request.
Let's see the transcript of a real session of someone working
with PURE-X11
:
(progn ;; open a window and draw a line
(connect)
(make-window)
(draw-window 0 0 100 100))
(query-pointer) ;; ask for current mouse cursor position
;; => 700, 700, 302, -321
;; while a *WINDOW* is open, one can copy image data into it
;; like this:
(let*((w 512)
(h 512)
(c 4)
(a (make-array (list h w c)
:element-type '(unsigned-byte 8))))
(dotimes (j h)
(dotimes (i w)
(setf (aref a j i 0) (mod i 255) ;; b
(aref a j i 1) (mod j 255) ;; g
(aref a j i 2) 255 ;; r
(aref a j i 3) 255))) ;; a
(put-image-big-req a))
I used http://www.x.org/archive/X11R7.5/doc/x11proto/proto.pdf as
a reference to implement the X protocol. There are requests and
replies. Requests are sent from the Lisp code to the X Server and
replies are read back. I implemented several versions of functions
for reading from the socket: blocking, non-blocking and one that
uses SBCL internals to read everything that is currently in the
buffer. Of those, I strive to only use the blocking READ-REPLY-WAIT
,
as this is the only one which will give robust code.
The packets as defined by the X protocol contain information stored
in various types of which I currently support CARD{8,16,32} and
STRING8
. I use the macro WITH-PACKET
to create a request and
WITH-REPLY
to parse a response. Both define a local function for
each type that will write/return a properly constructed binary
packet/parsed Common Lisp value to/from the stream while
maintaining a counter to keep track of the currently written byte
position.
The following function QUERY-POINTER
can act as a simple
example. First, a query pointer request is constructed and sent using
WITH-PACKET
. Then the reply is read back using READ-REPLY-WAIT
and
parsed inside the macro WITH-REPLY
:
(defun query-pointer ()
(with-packet
(card8 38) ; opcode
(card8 0) ; unused
(card16 2) ; length
(card32 *window*) ; window
)
(with-reply (read-reply-wait)
(let ((reply (card8))
(same-screen (card8))
(sequence-number (card16))
(reply-length (card32))
(root (card32))
(child (card32))
(root-x (card16))
(root-y (card16))
(win-x (card16))
(win-y (card16)))
(values root-x root-y win-x win-y))))