Chapter 7. Using Unixqueue together with Tcl (labltk) and Glib (lablgtk)

The Tcl programming language has already an event queue implementation, and the Tk toolkit applies it to realize event queues for graphical user interfaces (GUIs). In the O'Caml world, Tcl/Tk is available through the packages camltk and labltk.

The same holds for the Glib library which is used by the gtk GUI toolkit to implement event queues. In the O'Caml world, gtk bindings are provided by lablgtk (and lablgtk2).

While the GUI queues mainly process GUI events (e.g. mouse and keyboard events), they can also watch files in the same way as Unixqueue does it. It is, however, not possible to run both types of queues in parallel, because there is the problem that when one type of queue blocks, the other is implicitly also blocked, even when there would be events to process. The solution is to integrate both queues, and because the GUI queues can subsume the functionality of Unixqueue, the GUI queues are the more fundamental ones, and Unixqueue must integrate its event processing into the GUI queues.

This type of integration into the GUI queues is implemented by defining the alternate classes Uq_tcl.tcl_event_system and Uq_gtk.gtk_event_system. These classes can be used in the same way as Unixqueue.unix_event_system, but automatically arrange the event queue integration.

For example, a labltk program uses

let ues = new Uq_tcl.tcl_event_system()
to create the event system object, which can be used in the same way as event systems created with create_unix_event_system or new unix_event_system. There is one important difference, however. One must no longer call Unixqueue.run to start the processing. The reason is that the TCL queue is already started, and remains active during the runtime of the program. Remember that when the GUI function
Tk.mainLoop()
is entered, the TCL queue becomes active, and all subsequent execution of O'Caml code is triggered by callback functions. The integrated queue now behaves as follows: When handlers, resources, or events are added to ues, they are automatically considered for processing when the current callback function returns. For example, this might look as follows:
let b1 = Button.create 
           ~text:"Start function" 
           ~command:(fun () ->
                      Unixqueue.add_handler ues ...; ...) widget in
let b2 = Button.create 
           ~text:"Stop function" 
           ~command:(fun () ->
                      Unixqueue.remove_handler ues ...; ...) widget in
...
When the button is pressed, the function is triggered, and the callback function passed as command starts executing. This adds handlers, resources, and what ever is needed to start the activated function. The callback function returns immediately, and the processing of the event queue is performed by the regular GUI event system. Of course, it is still possible to press other buttons etc., because GUI and Unixqueue events are processed in an interweaved way. So the user is able to press the "Stop" button to stop the further execution of the activated function.

API change. In Equeue-2.1, the interface for the Tcl queue integration was changed. It works now as described above; the function Unixqueue.attach_to_tcl_queue no longer exists. The new scheme has the advantage that the Glib-type queues (and probably any other event queue implementation) can be also easily supported.

Sample code. The example discussed before, copying files in an event-driven way, has been extended to show how Unixqueue and Tcl can cooperate. While the file is being copied, a window informs about the progress and offers a "Stop" button which immediately aborts the copy procedure. See the directory "filecopy_labltk" in the distributed tarball. There is also a variant that works with lablgtk or lablgtk2, see the directory "filecopy_lablgtk".

Pitfalls. If you call Unixqueue functions from Unixqueue event handlers, the functions behave exactly as described in the previous chapters. However, it is also possible to call Unixqueue functions from TCL/Glib event handlers. In this case, not all change requests will be immediately honoured. Especially, add_event does not immediately invoke the appropriate event handler; the event is just recorded, and the handler will be called when the next system event happens (either a GUI event, a file descriptor event, or a timeout event). You can force to respect the new event as soon as possible by adding an empty handler using Unixqueue.once with a timeout of 0 seconds. - The other Unixqueue functions should not behave differently (although the actually performed operations are very different). Especially you can call add_resource and remove_resource and the change will be respected immediately.