Previous Up Next

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-handlerfunction

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:


[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:

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

Previous Up Next