24 Filter fudgets

The fact that all I/O effects of a fudget are represented through constructors in the datatypes Request, Response and others, opens up the possibility to write what we will call filters, which alter specific aspects of a fudget's input/output behaviour. Filters have type F a b -> F a b, which indicates that they do not tamper with the high-level messages, they only analyse and modify the low-level messages.

A number of problems can be solved by using filters--for example, swapping the meaning of the left and the right mouse buttons, or swapping the black and white colors in GUI fudgets.

In the following sections, we will see two examples of filters from the Fudget library which alter the behaviour of complex fudgets:

The filters in the Fudget library are constructed by means of a combinator that resembles loopThroughRightF, and is called loopThroughLowF:
loopThroughLowF :: SP (Either TRequest TResponse)
                      (Either TRequest TResponse) 
                -> F i o -> F i o
Just as loopThroughRightF is often used by application programmers to encapsulate and modify the behaviour of existing fudgets, loopThroughLowF is used in filters located in fudlogue and shellF, and can thus modify certain aspects of all fudgets in an application. The controlling stream processor, which is the first argument to loopThroughLowF, receives as input the stream of all tagged requests that are output from the encapsulated fudgets, and also all tagged responses directed to the same fudgets. It can analyse and manipulate these messages in any manner before outputting them, after which they will continue their way to the I/O system (in the case of the requests), or the fudgets (in the case of the responses). The simplest conceivable filter is

loopThroughLowF idSP
which simply passes through all requests and responses undisturbed, and thus acts as the identity filter.

24.1 The cache filter

Each GUI fudget allocates or queries a number of resources in the X server, such as fonts, font descriptions, graphical contexts and colors. For example, a fudget program with a large GUI may query a large number of font descriptions. This can result in a slow startup time, especially if the round trip delay between the program and server is large. Usually, most GUI fudgets will query the same resources as the others in the program, which seems wasteful. It would be beneficial if the resource allocation could be shared between the GUI fudgets. Not only would this result in a faster startup and less network load, but the program would also consume less memory. This is relevant in the case where font descriptions are queried, since these could occupy a significant amount of the heap.

It is the role of the cache filter to support this resource sharing between fudgets. It is part of fudlogue, which means that all fudgets in the program benefit from the resource sharing.

The effect of the cache filter is most notable on slow connections with high round trip delays, such as dialup connections. To demonstrate this, we have run Cla, one of the demonstration programs from the Fudget distribution, over a dial-up connection using PPP and secure shell (ssh, compression rate 9). The modem speed was 14400 bits per second, and the round trip delay 250 milliseconds on average. To eliminate errors due to different compression rates, the program was started repeatedly, until the startup time converged. Without the cache filter, the minimum startup time for Cla was clocked to 133 seconds. When enabling the cache, the startup time decreased to 9.6 seconds, a speedup factor of over 13. (As a comparison, we also ran Cla on this slow connection without compression: the startup times were 274 seconds with no cache, and 31 seconds with cache. Compression is a good thing!)

The heap usage is also better when the cache is enabled, the peak decreases from 990 to 470 kilobytes.

These figures should not come as a surprise since the GUI in Cla consists of one display, and 28 push buttons which can share the same resources.

Using the cache filter means that there is an overhead in the program. Except for drawing commands, the filter will analyse each request that is output. As a result, the calculator startup time is about 5% longer when the X server runs on the same computer as the calculator. In this case, the connection is fast and has negligible round trip delay.

24.1.1 Implementation

Before describing the implementation, we will show a communication scenario that takes place when a fudget allocates a particular kind of resource, namely a graphics context (GC). First, the fudget outputs the X request CreateGC d tgc al, where d is the drawable in which the GC will be used, tgc is a template GC, and al is a list of attributes that the new GC should have. The request is turned into a call to the Xlib function XCreateGC, which returns a reference to a new GC. This is sent back as the response GCCreated gc to the requesting fudget, which brings it to use. When the GC is not needed anymore, the fudget can explicitly deallocate it by outputting the X command FreeGC gc.

The idea of using a cache is of course that if a second fudget wants to create a GC with the same template and attributes, we could reuse the first GC, if it is not yet deallocated. So a GC cache maintains table from template and attributes to graphics contexts and reference counts.

It turns out that most resource (de)allocation follows the same pattern as our scenario, if we abstract from the specific request and response constructors. This abstraction is captured in the type RequestType, which expresses whether a request is an allocation, a deallocation, or something else:

data RequestType a r =  Allocate a
                     |  Free r
                     |  Other
The argument to the Allocate constructor carries allocation data that the cache filter uses as search key in the resource table. Similarly, the Free constructor carries the resource that should be freed. In the case of graphics contexts, the allocation data are pairs of template GCs and attribute lists, and the resources are graphics contexts.

The function gcRequestType determines the type of request for graphics contexts:

gcRequestType :: Request -> RequestType (GCId,GCAttributeList) GCId
gcRequestType r = 
  case r of
    CreateGC d tgc al  -> Allocate (tgc,al)
    FreeGC gc          -> Free gc
    _                  -> Other
The general cache filter cacheFilter is parameterised over the function that determines the request type:

cacheFilter :: (Eq a,Eq r) => (Request -> RequestType a r)
                           -> F i o -> F i o

cacheFilter rtf = loopThroughLowF (cache [])
   where cache table = ...
The internal state table is a list of type [(a, (r, Int))], where the elements are allocation data with associated resources and reference counts.

The definition of a cache for graphics contexts is now simple:

gcCacheFilter :: F i o -> F i o
gcCacheFilter = cacheFilter gcRequestType
The Fudget library defines request type functions like gcRequestType for a number of resources, and the corresponding cache filters, using the general cacheFilter. All these filters are combined into allCacheFilter:
allcacheFilter :: F a b -> F a b
allcacheFilter =
    fontCacheFilter .
    fontStructCacheFilter .
    gcCacheFilter .
    colorCacheFilter .
    bitmapFileCacheFilter .
This cache filter is wrapped around all fudget programs in fudlogue. One should fear that allcacheFilter would impose a considerable overhead, since all commands must be inspected in turn by each of the six filters. In practice, the overhead is not a big problem.

24.2 The focus filter

When I type on the keyboard, which GUI element should receive the typed characters? Equivalently, which GUI element has the input focus? Initially, the Fudget library implemented the simple model of point-to-type focus, since it is directly supported by X Windows. With point-to-type, a GUI fudget cannot have the input focus unless the pointer is over it. A GUI fudget (such as stringF) signals its interest in focus by configuring its event mask to include KeyPressMask, EnterWindowMask, and LeaveWindowMask. This means that the fudget can receive keyboard input, and also events when the pointer enters or leaves the fudget (crossing events). The crossing events are used to give visual feedback about which fudget has the focus.

A potential problem with point-to-type controlled focus, is that the user must move a hand back and forth a lot between the keyboard and the pointing device (assuming that the pointer cannot be controlled from the keyboard), if she wants to fill in data in a form that consists of several input fields. It is also easy to touch the pointing device accidentally so that the pointer jumps a little, which could result in a focus change.

These problems vanish when using a click-to-type focus model. With click-to-type, the tight coupling between the pointer position and focus is removed. Instead, the user clicks in an input field to indicate that it should have the focus. The focus stays there until the user clicks in another input field. In addition, if the keyboard can be used for circulating focus between the input fields in a form, it can be filled in without using the pointing device.

A limited variant of this improved input model has been added to the Fudget library as a filter in the shell fudgets, leaving the various GUI fudgets unmodified. The limitation is that the model is only click-to-type as long as the pointer is inside the shell fudget. When the pointer leaves the shell fudget, focus goes to whatever application window is under it, unless the window manager uses click-to-type.

24.2.1 Implementation

The implementation of the focus is based on the key observation that GUI fudgets that need keyboard input (let us call them focus fudgets) can be distinguished by the kind of events that they configure their window to report. All focus fudgets are of course interested in key press events, but they also need crossing events, for giving proper visual feedback when they have focus. Therefore, focus fudgets will initially set their window event mask so that ffMask is a subset:

ffMask = [KeyPressMask, EnterWindowMask, LeaveWindowMask]
A simplified implementation of a focus filter is shown in Figure 57.
focusFilter :: F a b -> F a b
focusFilter f = loopThroughLowF (focusSP []) 
                                (simpleGroupF [KeyPressMask] f)

focusSP :: [Path] -> SP (Either TRequest TResponse) 
                        (Either TRequest TResponse)
focusSP fpaths = getSP (either request response)
    request (p,r) =
        case getEventMask r of
          Just mask | ffMask `issubset` mask ->
                putSP (Left (p,setEventMask (mask' r)) $
                focusSP (p:fpaths)
            where mask' = [ButtonPressMask] `union` mask

          _ -> putSP (Left (p,r)) $ 
               focusSP fpaths

    response (p,r) = 
          if keyPressed r 
          then (putSP (Right (head fpaths, r)) $
                focusSP fpaths)
          else if leftButtonPressed 1 r && p `elem` fpaths 
          then putSP (Right (p,r)) $
               focusSP (aft++bef)
          else putSP (Right (p,r))
       where (bef,aft) = break (==path) fpaths

-- Auxiliary functions:
simpleGroupF :: [EventMask] -> F a b -> F a b
getEventMask :: Request -> Maybe [EventMask]
setEventMask :: [EventMask] -> Request -> Request
keyPressed   :: Request -> Bool
leftButtonPressed :: Request -> Bool

Figure 57. A focus filter.

The focus filters reside immediately inside the shell fudgets. To get keyboard events, no matter the position of the pointer (as long as it is inside the shell window), a group fudget is created around the inner fudgets with a suitable event mask. This is done with simpleGroupF, which acts as a groupF without a kernel.

The filtering is done in focusSP, whose argument fpaths accumulates a list of paths to the focus fudgets. This is done by looking for window configuration commands with matching event masks. The event masks of the focus fudgets is modified to mask', so that the windows of focus fudgets will generate mouse button events.

The head of fpaths is considered to own the focus, and incoming key events are redirected to it. If the user clicks in one of the focus fudgets, fpaths is reorganised so that the path of the clicked fudget comes first.

As noted, Figure 57 shows a simplified focus filter. The filter in the Fudget library is more developed; it also handles crossing events, and focus changes using the keyboard. More complex issues, like dynamic creation and destruction of fudgets, are also handled. Still, it ignores some complications, introduced by the migrating fudgets in Chapter 25.

It should also be noted that the X window model supports special focus change events which should rather be used when controlling focus. This fits better with window managers that implement click-to-type.

24.3 Pros and cons of filters

The experience we have had with filters in the Fudget library are both good and bad. On the good side, the filters open the possibility to modify the I/O behaviour of existing software without having to alter its source code. On the other side, although the filters were developed without changing the source code of the GUI fudgets, detailed knowledge about their source code was used in order to decide on what assumptions we could make about their behaviour. For example, we have seen that the focus filter assumes that all GUI fudgets that should be under focus control can be distinguished by analysing their event masks. This complicates the semantics of event masks, something that must be taken into account when programming new GUI fudgets. Similarly, the possible sharing of a resource caused by the cache filter means that imperative operations on resources (such as XChangeGC) must be avoided in the GUI fudgets.

The implementation of filters often involves that a piece of state must be associated with each GUI fudget. This means that the state of some GUI fudgets are spread out in the library, in some sense. One piece resides in the fudget itself as local state, then there is non-local state in the focus filter, and in the fudlogue tables, which are used to route asynchronous events. If fudget state is distributed like this, there is always a danger that it becomes inconsistent, for example when fudgets move or die.