Chapter 7 Event Dispatching with
SERVE-EVENT
by Bill Chiles and Robert
MacLachlan
It is common to have multiple activities simultaneously operating
in the same Lisp process. Furthermore, Lisp programmers tend to
expect a flexible development environment. It must be possible to
load and modify application programs without requiring
modifications to other running programs. CMUCL achieves this by
having a central scheduling mechanism based on an event-driven,
object-oriented paradigm.
An event is some interesting happening
that should cause the Lisp process to wake up and do something.
These events include X events and activity on Unix file
descriptors. The object-oriented mechanism is only available with
the first two, and it is optional with X events as described later
in this chapter. In an X event, the window ID is the object
capability and the X event type is the operation code. The Unix
file descriptor input mechanism simply consists of an association
list of a handler to call when input shows up on a particular file
descriptor.
7.1 Object Sets
An object set is a collection of
objects that have the same implementation for each operation.
Externally the object is represented by the object capability and
the operation is represented by the operation code. Within Lisp,
the object is represented by an arbitrary Lisp object, and the
implementation for the operation is represented by an arbitrary
Lisp function. The object set mechanism maintains this translation
from the external to the internal representation.
[Function]
system:make-object-set name
&optional default-handler
This function makes a new object set. Name is a string used only for purposes of
identifying the object set when it is printed. Default-handler is the function used as a handler
when an undefined operation occurs on an object in the set. You can
define operations with the serve-operation functions exported the extensions package for X events (see
section 7.4). Objects are added
with system:add-xwindow-object. Initially the
object set has no objects and no defined operations.
[Function]
system:object-set-operation object-set operation-code
This function returns the handler function that is the
implementation of the operation corresponding to operation-code in object-set. When set with setf, the setter function establishes the new handler.
The serve-operation
functions exported from the extensions
package for X events (see section 7.4) call this on behalf of the user when
announcing a new operation for an object set.
[Function]
system:add-xwindow-object window object object-set
These functions add port or window to object-set.
Object is an arbitrary Lisp object that
is associated with the port or window capability. Window
is a CLX window. When an event occurs, system:serve-event passes object as an argument to the handler
function.
7.2 The SERVE-EVENT Function
The system:serve-event function is the
standard way for an application to wait for something to happen.
For example, the Lisp system calls system:serve-event when it wants input from X or a
terminal stream. The idea behind system:serve-event is that it knows the appropriate
action to take when any interesting event happens. If an
application calls system:serve-event when it
is idle, then any other applications with pending events can run.
This allows several applications to run “at the same
time” without interference, even though there is only one
thread of control. Note that if an application is waiting for input
of any kind, then other applications will get events.
[Function]
system:serve-event &optional timeout
This function waits for an event to happen and then dispatches to
the correct handler function. If specified, timeout is the number of seconds to wait before
timing out. A time out of zero seconds is legal and causes
system:serve-event to poll for any events
immediately available for processing. system:serve-event returns t if
it serviced at least one event, and nil
otherwise. Depending on the application, when system:serve-event returns t, you
might want to call it repeatedly with a timeout of zero until it
returns nil.
If input is available on any designated file descriptor, then this
calls the appropriate handler function supplied by system:add-fd-handler.
Since events for many different applications may arrive
simultaneously, an application waiting for a specific event must
loop on system:serve-event until the desired
event happens. Since programs such as Hemlock call system:serve-event for input, applications usually do
not need to call system:serve-event at all;
Hemlock allows other application's handlers to run when it goes
into an input wait.
[Function]
system:serve-all-events &optional timeout
This function is similar to system:serve-event, except it serves all the pending
events rather than just one. It returns t if
it serviced at least one event, and nil
otherwise.
7.3 Using SERVE-EVENT with Unix File
Descriptors
Object sets are not available for use with file descriptors, as
there are only two operations possible on file descriptors: input
and output. Instead, a handler for either input or output can be
registered with system:serve-event for a
specific file descriptor. Whenever any input shows up, or output is
possible on this file descriptor, the function associated with the
handler for that descriptor is funcalled with the descriptor as
it's single argument.
[Function]
system:add-fd-handler fd
direction function
This function installs and returns a new handler for the file
descriptor fd. direction can be either :input if the system should invoke the handler when
input is available or :output if the system
should invoke the handler when output is possible. This returns a
unique object representing the handler, and this is a suitable
argument for system:remove-fd-handler
function must take one argument, the file
descriptor.
[Function]
system:remove-fd-handler handler
This function removes handler, that
add-fd-handler must have previously
returned.
[Macro]
system:with-fd-handler (fd
direction function) {form}*
This macro executes the supplied forms with a handler installed
using fd, direction, and function.
See system:add-fd-handler. The forms are
wrapped in an unwind-protect; the handler is
removed (see system:remove-fd-handler) when
done.
[Function]
system:wait-until-fd-usable fd direction &optional timeout
This function waits for up to timeout
seconds for fd to become usable for
direction (either :input or :output). If timeout is nil or
unspecified, this waits forever.
[Function]
system:invalidate-descriptor fd
This function removes all handlers associated with fd. This should only be used in drastic cases (such
as I/O errors, but not necessarily EOF). Normally, you should use
remove-fd-handler to remove the specific
handler.
7.4 Using SERVE-EVENT with the CLX
Interface to X
Remember from
section 7.1, an object set is a
collection of objects, CLX windows in this case, with some set of
operations, event keywords, with corresponding implementations, the
same handler functions. Since X allows multiple display connections
from a given process, you can avoid using object sets if every
window in an application or display connection behaves the same. If
a particular X application on a single display connection has
windows that want to handle certain events differently, then using
object sets is a convenient way to organize this since you need
some way to map the window/event combination to the appropriate
functionality.
The following is a discussion of functions exported from the
extensions package that facilitate handling
CLX events through system:serve-event. The
first two routines are useful regardless of whether you use
system:serve-event:
[Function]
ext:open-clx-display &optional string
This function parses string for an X
display specification including display and screen numbers.
String defaults to the following:
(cdr (assoc :display ext:*environment-list* :test #'eq))
If any field in the display specification is missing, this signals
an error. ext:open-clx-display returns the
CLX display and screen.
[Function]
ext:flush-display-events display
This function flushes all the events in display's event queue including the current event,
in case the user calls this from within an event
handler.
7.4.1 Without Object Sets
Since most applications that use CLX, can avoid the complexity of
object sets, these routines are described in a separate section.
The routines described in the next section that use the object set
mechanism are based on these interfaces.
[Function]
ext:enable-clx-event-handling display handler
This function causes system:serve-event to
notice when there is input on display's
connection to the X11 server. When this happens, system:serve-event invokes handler on display in a
dynamic context with an error handler bound that flushes all events
from display and returns. By returning,
the error handler declines to handle the error, but it will have
cleared all events; thus, entering the debugger will not result in
infinite errors due to streams that wait via system:serve-event for input. Calling this repeatedly
on the same display establishes
handler as a new handler, replacing any
previous one for display.
[Function]
ext:disable-clx-event-handling display
This function undoes the effect of ext:enable-clx-event-handling.
[Macro]
ext:with-clx-event-handling (display handler)
{form}*
This macro evaluates each form in a
context where system:serve-event invokes
handler on display whenever there is input on display's connection to the X server. This destroys
any previously established handler for display.
7.4.2 With Object Sets
This section discusses the use of object sets and system:serve-event to handle CLX events. This is
necessary when a single X application has distinct windows that
want to handle the same events in different ways. Basically, you
need some way of asking for a given window which way you want to
handle some event because this event is handled differently
depending on the window. Object sets provide this feature.
For each CLX event-key symbol-name iXXX (for example, key-press), there is a function serve-iXXX of two arguments, an object set and a
function. The serve-iXXX function establishes
the function as the handler for the :XXX
event in the object set. Recall from section 7.1, system:add-xwindow-object associates some Lisp object
with a CLX window in an object set. When system:serve-event notices activity on a window, it
calls the function given to ext:enable-clx-event-handling. If this function is
ext:object-set-event-handler, it calls the
function given to serve-iXXX, passing the
object given to system:add-xwindow-object and
the event's slots as well as a couple other arguments described
below.
To use object sets in this way:
- Create an object set.
- Define some operations on it using the
serve-iXXX functions.
- Add an object for every window on which you
receive requests. This can be the CLX window itself or some
structure more meaningful to your application.
- Call system:serve-event to service an X event.
[Function]
ext:object-set-event-handler display
This function is a suitable argument to ext:enable-clx-event-handling. The actual event
handlers defined for particular events within a given object set
must take an argument for every slot in the appropriate event. In
addition to the event slots, ext:object-set-event-handler passes the following
arguments:
- The object, as established by system:add-xwindow-object, on which the event
occurred.
- event-key, see xlib:event-case.
- send-event-p, see xlib:event-case.
Describing any ext:serve-event-key-name function, where event-key-name is an event-key symbol-name (for
example, ext:serve-key-press), indicates
exactly what all the arguments are in their correct order.
When creating an object set for use with ext:object-set-event-handler, specify ext:default-clx-event-handler as the default handler
for events in that object set. If no default handler is specified,
and the system invokes the default default handler, it will cause
an error since this function takes arguments suitable for handling
port messages.
7.5 A SERVE-EVENT Example
This section contains two examples using system:serve-event. The first one does not use object
sets, and the second, slightly more complicated one does.
7.5.1 Without Object Sets Example
This example defines an input handler for a CLX display connection.
It only recognizes :key-press events. The
body of the example loops over system:serve-event to get input.
(in-package "SERVER-EXAMPLE")
(defun my-input-handler (display)
(xlib:event-case (display :timeout 0)
(:key-press (event-window code state)
(format t "KEY-PRESSED (Window = ~D) = ~S.~%"
(xlib:window-id event-window)
;; See Hemlock Command Implementor's Manual for convenient
;; input mapping function.
(ext:translate-character display code state))
;; Make XLIB:EVENT-CASE discard the event.
t)))
(defun server-example ()
"An example of using the SYSTEM:SERVE-EVENT function and object sets to
handle CLX events."
(let* ((display (ext:open-clx-display))
(screen (display-default-screen display))
(black (screen-black-pixel screen))
(white (screen-white-pixel screen))
(window (create-window :parent (screen-root screen)
:x 0 :y 0 :width 200 :height 200
:background white :border black
:border-width 2
:event-mask
(xlib:make-event-mask :key-press))))
;; Wrap code in UNWIND-PROTECT, so we clean up after ourselves.
(unwind-protect
(progn
;; Enable event handling on the display.
(ext:enable-clx-event-handling display #'my-input-handler)
;; Map the windows to the screen.
(map-window window)
;; Make sure we send all our requests.
(display-force-output display)
;; Call serve-event for 100,000 events or immediate timeouts.
(dotimes (i 100000) (system:serve-event)))
;; Disable event handling on this display.
(ext:disable-clx-event-handling display)
;; Get rid of the window.
(destroy-window window)
;; Pick off any events the X server has already queued for our
;; windows, so we don't choke since SYSTEM:SERVE-EVENT is no longer
;; prepared to handle events for us.
(loop
(unless (deleting-window-drop-event *display* window)
(return)))
;; Close the display.
(xlib:close-display display))))
(defun deleting-window-drop-event (display win)
"Check for any events on win. If there is one, remove it from the
event queue and return t; otherwise, return nil."
(xlib:display-finish-output display)
(let ((result nil))
(xlib:process-event
display :timeout 0
:handler #'(lambda (&key event-window &allow-other-keys)
(if (eq event-window win)
(setf result t)
nil)))
result))
7.5.2 With Object Sets Example
This example involves more work, but you get a little more for your
effort. It defines two objects, input-box and
slider, and establishes a :key-press handler for each object, key-pressed and slider-pressed.
We have two object sets because we handle events on the windows
manifesting these objects differently, but the events come over the
same display connection.
(in-package "SERVER-EXAMPLE")
(defstruct (input-box (:print-function print-input-box)
(:constructor make-input-box (display window)))
"Our program knows about input-boxes, and it doesn't care how they
are implemented."
display ; The CLX display on which my input-box is displayed.
window) ; The CLX window in which the user types.
;;;
(defun print-input-box (object stream n)
(declare (ignore n))
(format stream "#<Input-Box ~S>" (input-box-display object)))
(defvar *input-box-windows*
(system:make-object-set "Input Box Windows"
#'ext:default-clx-event-handler))
(defun key-pressed (input-box event-key event-window root child
same-screen-p x y root-x root-y modifiers time
key-code send-event-p)
"This is our :key-press event handler."
(declare (ignore event-key root child same-screen-p x y
root-x root-y time send-event-p))
(format t "KEY-PRESSED (Window = ~D) = ~S.~%"
(xlib:window-id event-window)
;; See Hemlock Command Implementor's Manual for convenient
;; input mapping function.
(ext:translate-character (input-box-display input-box)
key-code modifiers)))
;;;
(ext:serve-key-press *input-box-windows* #'key-pressed)
(defstruct (slider (:print-function print-slider)
(:include input-box)
(:constructor %make-slider
(display window window-width max)))
"Our program knows about sliders too, and these provide input values
zero to max."
bits-per-value ; bits per discrete value up to max.
max) ; End value for slider.
;;;
(defun print-slider (object stream n)
(declare (ignore n))
(format stream "#<Slider ~S 0..~D>"
(input-box-display object)
(1- (slider-max object))))
;;;
(defun make-slider (display window max)
(%make-slider display window
(truncate (xlib:drawable-width window) max)
max))
(defvar *slider-windows*
(system:make-object-set "Slider Windows"
#'ext:default-clx-event-handler))
(defun slider-pressed (slider event-key event-window root child
same-screen-p x y root-x root-y modifiers time
key-code send-event-p)
"This is our :key-press event handler for sliders. Probably this is
a mouse thing, but for simplicity here we take a character typed."
(declare (ignore event-key root child same-screen-p x y
root-x root-y time send-event-p))
(format t "KEY-PRESSED (Window = ~D) = ~S –> ~D.~%"
(xlib:window-id event-window)
;; See Hemlock Command Implementor's Manual for convenient
;; input mapping function.
(ext:translate-character (input-box-display slider)
key-code modifiers)
(truncate x (slider-bits-per-value slider))))
;;;
(ext:serve-key-press *slider-windows* #'slider-pressed)
(defun server-example ()
"An example of using the SYSTEM:SERVE-EVENT function and object sets to
handle CLX events."
(let* ((display (ext:open-clx-display))
(screen (display-default-screen display))
(black (screen-black-pixel screen))
(white (screen-white-pixel screen))
(iwindow (create-window :parent (screen-root screen)
:x 0 :y 0 :width 200 :height 200
:background white :border black
:border-width 2
:event-mask
(xlib:make-event-mask :key-press)))
(swindow (create-window :parent (screen-root screen)
:x 0 :y 300 :width 200 :height 50
:background white :border black
:border-width 2
:event-mask
(xlib:make-event-mask :key-press)))
(input-box (make-input-box display iwindow))
(slider (make-slider display swindow 15)))
;; Wrap code in UNWIND-PROTECT, so we clean up after ourselves.
(unwind-protect
(progn
;; Enable event handling on the display.
(ext:enable-clx-event-handling display
#'ext:object-set-event-handler)
;; Add the windows to the appropriate object sets.
(system:add-xwindow-object iwindow input-box
*input-box-windows*)
(system:add-xwindow-object swindow slider
*slider-windows*)
;; Map the windows to the screen.
(map-window iwindow)
(map-window swindow)
;; Make sure we send all our requests.
(display-force-output display)
;; Call server for 100,000 events or immediate timeouts.
(dotimes (i 100000) (system:serve-event)))
;; Disable event handling on this display.
(ext:disable-clx-event-handling display)
(delete-window iwindow display)
(delete-window swindow display)
;; Close the display.
(xlib:close-display display))))
(defun delete-window (window display)
;; Remove the windows from the object sets before destroying them.
(system:remove-xwindow-object window)
;; Destroy the window.
(destroy-window window)
;; Pick off any events the X server has already queued for our
;; windows, so we don't choke since SYSTEM:SERVE-EVENT is no longer
;; prepared to handle events for us.
(loop
(unless (deleting-window-drop-event display window)
(return))))
(defun deleting-window-drop-event (display win)
"Check for any events on win. If there is one, remove it from the
event queue and return t; otherwise, return nil."
(xlib:display-finish-output display)
(let ((result nil))
(xlib:process-event
display :timeout 0
:handler #'(lambda (&key event-window &allow-other-keys)
(if (eq event-window win)
(setf result t)
nil)))
result))