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.
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 given 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.
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))