You are on page 1of 5

Keyboard and Mouse Event Propagation

When reporting ButtonPress, ButtonRelease, KeyPress, KeyRelease, and MotionNoti fy.events,


the X server first determines which window should receive the event. For k~yboard events, this is
thewindow with the input focus. For mouse events, this is a visible window, lowest iri the hierarchy
ofwindows that enclose the mouse pointer. The server checks the event_mask of this window tc
determine if the window accepts this event. If it does, the server reports the event for this window.
In this case, this window is the event window--the window to which the. event is reported.

Ifthis window does not request the event, the server does not discard the event. Instead, it propa-
gatesthe event up the hierarchy. The first step in the propagation is to send the event to the parent.
Ihhe parent window accepts the event, the server reports the event to it. The event data structure
forthese keyboard and mouse events includes a subwindow field, in which the server reports the
childwindow where the event actually occurred. If the immediate parent does not accept the event,
it issent to the next higher level, until the server encounters a window that has selected the event or
onethat stops the propagation of the event. The server discards the event only if it cannot find any
window that has requested the event.

The programmer can control the propagation of keyboard and mouse events. By default, keyboard
andmouse events propagate up the window hierarchy, but a field named do_not_propagate_mask
in the window attribute controls this feature (see the XWindowAttributes structUre in Chapter 7).
If you do not want an event to propagate to the ancestors of a window, set the
do_not_propagate_mask of that window to that event's mask. Use the same names you use when
selecting events with XSelectInput (see Table 6.3 in Chapter 6). For example, if you want
ButtonPress and ButtonRelease events to stop at the window win, you can use the following
codeto set it up:
Display theDisplaYj
Window win1;
XWindowAttributes xwa;
XSetWindowAttributes xswaj

if (XGetWindowAttributes(theDisplay, win1, &xwa) != 0)


{
xswa.do_not_propagate_mask = xwa.do_not_propagate_mask :
ButtonPressMask : ButtonReleaseMask;

XChangeWindowAttributes(theDisplay, win1,
CWDontPropagate, &xswa)j

This code fragment gets the current attributes of the window and changes the
do_not_propagate_mask without disturbing anything else. You can use a similar approach when
alteringany attribute of a window.

Eventpropagation can be handy for centralized event handling. For example, you may decide to
implement a .nenu with several small child windows (the menu items) inside a larger parent win-
dow that serves as an outer shell. You can handle mouse clicks on the menu items by enabling
ButtonPress and ButtonRelease events in the outer window only. When the user presses on one
.• It sends to the server any X protocol requests waiting in Xlib's buffer. This feature is handy
of the menu items, the events are reported to the outer window, but the event's information indi-
because, unless this buffer is flushed, the effect of drawing functions does not appear on
cates the subwindow where it actually occurred. You then can use this information to process the
the screen. You can call XFlush to perform the operation, but inclusion of this task in
menu selection.
XNextEvent makes the flushing almost automatic, because most applications rely on
XNextEvent to get events from Xlib.
Unsolicited Events: MappingNotify and ClientMessage .• XNextEvent looks for any events waiting in Xlib's event queue. If there is an event,
Recall.that the X server never reports events that were not explicitly solicited. There are, however, XNextEvent returns after copying the information abour the event into the XEvent union
two rypes of events that are always sent to applications. The first one is the MappingNoti fy event. whose address you provide as an argument to XNextEvent. If no events are available yet,
XNextEvent waits until an event arrives from the server. XNextEvent "blocks" until it
This event is generated when any application rearranges the keyboard keycodes or changes the
receives an event.
numbering of the mouse buttons. It makes sense because all applications using a display share the
keyboard and the mouse. It is easy to handle this event. Put the following case statement in the Some applications have a problem with the fact that XNextEvent does not return until an event
swi tch statement of the main event handling loop: becomes available. For example, suppose your application is performing some lengthy computa-
tions and displaying the results as work progresses. If you want to do the computation and still re-
spond to the user's input (perhaps a click on a Cancel button), you cannot use XNextEvent.

To achieve your goal, you must break the computation into smaller chunks. Mter you organize the
work into smaller portions, you need ways to check whether any events are waiting and to process
case MappingNotify:
XRefreshKeyboardMapping(&theEvent); the waiting events. If no events are available, you can perform a small chunk.of computation. You
break; can use the XEventsQueued function together with XNextEvent to do the job:
XEvent theEvent;
Display *theDisplay;
int AppDone, events_pending;
The examples shown thus far lack this code, but you should include this in all your X applications.

Another type of unsolicited event delivered to your application is a ClientMessage event sent by AppDone = 0;
another X client using the XSendEvent function. The interpretation of the information passed by while (!AppDone)
{
these events is up to the sender and the receiver. Window managers often use ClientMessage events /* Process all current events in the queue */
to communicate with applications. The exact message type and the recommended handling of the if«events_pending XEventsQueued(theDisplay,
event depends on the window manager. Of course, even when ClientMessage events reach your QueuedAfterReading» == 0)
{
application, your application is not required to do anything with these events. /* Perform a small part of your computations including
* any drawing operations
*/

When the X server sends events to the application, Xlib automatically saves the events in a queue.
Your programs have to call certain routines to get the events from the queue. So far, the example }
/* Process all waiting events */
programs in this book have used the XNextEvent function to get events from Xlib's event queue: while (events_pending - )
{
Display *theDisplay;
XEvent theEvent; XNextEvent(theDisplay, &theEvent);
sWitch(theEvent.type)
{

XNextEvent(theDisplay, &theEvent); case MappingNotify:


XRefreshKeyboardMapping(&theEvent);
This code performs two tasks: break;
/* Handle other events ... */
case ButtonPress:
0()'J-k
Chapter Eight: Handling Events

f* Handle events occurring in this window *f


if(theEvent.xany.window == this_window)
{
switch(theEvent.type)
{
case Expose:
if (theEvent.xexpose.count == 0)
{

}
break;

As you can see, the field theEvent. xexpose. count represents the count of Expose events. When
the count is zero, you can clear the window and draw the graphics and text~that should appear in
the window. This part of the operation depends on the application. Chapter 9, "Drawing Graph-
ics," provides more details on Expose events.

Recall also that some X displays may have off-screen memory known as "backing store" that en-
ables them to save the contents of a window when it gets obscured. With backing store, the server
can refresh the window automatically when it becomes visible again. In these cases, the server does
not send any Expose- events, which, in turn, spares the application the computational burden of
redrawing its windows. Because not all displays have backing store, you should always include code
to handle Expose events so that your application can run on all X displays. Additionally, backing
store is usually in limited supply; once the server runs Qut of memory for backing store, it has to use
Expose events for maintaining the contents of windows.

lfp, Wf{~
use Events .V
X a{5Pticatlonsalways handle events from mouse input, keyboard input, or both. For a graphical
user in~d'ace, handling mouse events-including button press, button-release, and, occasionally,
mo~vement-is essential. Applications such as word processors and editors also handle events
from the keyboard. Because these inp~ices are so importan00st of the rest of this cbapter is
devoted to the discussion of the events generated by the mouse and the keyboard.
Pointing devices are crucial in graphical point-and-click user interfaces. Thus, workstations that
support the X Window System include some sort of pointing device, usually a mouse. Recall that
the mouse controls a small graphical shape kno~n as the cursor or pointer that aF-pearson the screen;
as you 'move the mouse, the pointer tracks the motion on the screen. In a graphical interface, -you
interact with the application by bringing the mouse pointer to well-defined areas of the screen and
pressing the mouse button. You essentially can press a button to get a job done. In a drawing appli-
cation, for example, you draw a line by dragging the mouse, which involves moving the mouse wKile
keeping a mouse button pressed. You can quickly press and release a mouse button to achieve the
win_x, win_y; /* Position in w's frame */
effect of clicking on a window. On the Macintosh and on Microsoft Windows, YQlLStartapplica- keys_buttons; /* Info on mouse buttons */
tions by double-clicking. on their icons. This requires two clicks in quick succes;ion with the pointer
--=----
at ~e same!'-o-ca-t-l-o-n-.
if(!XQueryPointer(theDisplay, w, &root, &child, &root_x, &root_y,
&win_x, &win_y, &keys_buttons»
In the1- Window System, there are no events equivalent to button clicks or double-clicks. Instead, {
the X server reports a few basic mOlJ~nts: ~hen a button iU'resse~and released, wh~n t~e mouse /* Pointer is not on the screen where window w is */
is moved, and when the mouse pointer enters or leaves a window. If you understand when these }

basic events are generated and how they are reportea;you can write your own event handling code Here, w is the window in whose coordinate frame you want to find the pointer's position.
to give the appearance of a button 0ck or doubl~-.~;li~k. / XQueryPointer returns a zero if the pointer is not on the screen where window w is displayed. In
this case, in the root variable, it returns the 10 of the root window on the screen where the pointer

, uttons, Pointer, and Cursor


appears.

The number of mouse buttons varies from one workstation to another. The mouse in the Apple If successful, XQueryPointer provides the information in a set of variables. You have to declare
Macintosh has only one button; the Microsoft mouse, prevalent in the MS-DOS and Microsoft these variables and pass their addresses as arguments to the function. In root and child, you get
Windows world, uses two buttons. Most UNIX workstations and some MS-DOS systems use a back the window 10 of the root window and any visible subwindow of w that contains the pointer.
three-button mouse.~ allows for mice with up to five buttons, named Button1 through ButtonS. If the pointer is in wand not in any of its child windows, child will be set to None. The coordinates
Usually, the mouse buttons are numbered from left to right, but a programmer can call the of the pointer with respect to the root window are returned in root.:...x and root_y, and win_x,
XSetPointerMapping function to change the orde~lng. --'0 win_yare the coordinates in w's frame.

The pointer refers to the graphical indication of the mouse's position in the display screen. The cursor The keys_buttons variable is a bit mask that indicates which mouse button and which modifier
is the actual graphical object that determines the appearance of the pointer in the screen. key (Shift, Ctrl, Meta, or Alt) is pressed at that moment. To decipher this inforination, you have to
perform bitwise-AND of keys_buttons with bitmask names defined in the header ftle <X11/X. h>.
For example, to see if Button1 is pressed, you might write the following:
if (keys_buttons & Button1Mask)
Although the X server automatically moves the pointer to track the movements of the mouse, you {
/* Yes, button 1 is pressed */
have control over several aspects of the pointer. You Can, for example, call the XWarpPointer func-
tion to move the pointer forcibly to a specified location On the screen. This can be disconcerting to
the user; therefore, you probably should not warp the pointer unless absolutely necessary fof"some
task in your application.

Two other parameters of the pointer, acceleration and threshold, can also be set by programmers.
Acceleration refers to how fast the pointer moves as you move the mouse. The acceleration is ap-
Because xaueryPointer has to get its information from the X server, each call to this
plied only when the mouse movement exceeds the threshold. You can use XGetPointerControl
function requires a costly round trip over the network connection to the server. Thus,
and XChangePointerControl to change these parameters, but you rarely need to do so. Many users
·excessive use ofxaueryPointer can hurt the performance of your application. To keep
run the utility program name xset to set the acceleration and threshold. On most UNIX work-
track of mouse positions in a window,-use the MotionNotify event instead.
stations, you should be able to get further information on xset by typing the command man xset
at the shell prompt.
After you find the coordinates of the pointer in one window's coordinate frame, you can convert it
to another window's frame by calling XTranslateCoordinates. Unfortunately, this conversion is
also performed by sending a protocol request to the server and getting a reply back. Thus,
Sometimes your application may have to determine the current position of the mouse pointer. Xlib XTranslateCoordinates is as costly as using xaueryPointer.
incluUe.sthe function XQueryPointer for this purpose:
One interesting use of XTranslateCoordinates is to determine the window where a point lies.
Display *theDisplay; /* Identifies the X server */
Window w,
For example, if you know the coordinates x and y of a point, relative to the root window, you can
/* Window of interest */
root, child; /* For return values */ determine the ID of the top-level Vi"indowcontaining the point:
root_x, root_y, /* Position in root */
XColor fgcolor, bgcolor; t* Colors in XColor structure *t
Window toplevel; t* Window that contains the point (x,y) *t Cursor arrow_cursor; t* Cursor whose color is set *t
int x, y; t* Coordinates of the point of interest *t
int xx, yy; t* Storage for value being discarded *t
XTranslateCoordinates(theDisplay, DefaultRootWindow(theDisplay), To specifYthe color, you have to use RGB values. For more information on color, see Chapter 11,
DefaultRootWindow(theDisplay), x, y, &xx, &yy, &toplevel);
"Using Color in X."
On return from XTranslateCoordinates, the vatiable toplevel contains the 10 of the window
where the point (x, y) lies. If the point lies in the root window, but not in any top-level subwindow,
the returned 10 will be the constant None.
You can use your own source and mask bitmaps to define a cursor. After you have the two pixmaps-
bitmaps are pixmaps of depth one-you can use XCreatePixmapCursor to create a new cursor.
This function needs the two pixmaps, the foreground and background colors, and the coordinates
The cursor determines the on-screen appearance of the pointer. In X, a cursor is defined by a source of the hotspot:
bitmap, a mask bitmap, foreground and background colors specified as RGB values, and a hotspot.
Display *theDi~play;
You can think of the bitmaps as a small rectangular array of Is and Os (usually 16x16 or 32x32). Cursor my_cursor;
When drawing the cursor, the server paints the pi..xelscorresponding to Is using the foreground Pixmap source, mask;
color; pixels at locations with Osappear in the background color. The mask bitmap determines the XColor fgcolor, bgcolor;
unsigned int x_hot, y_hot;
outline within which the cursor shape is drawn. The hotspot is a point that defines the location of
the pointer in the screen. For many cursor shapes, the hotspot is at the center of the cursor's birmap. my_cursor = XCreatePixmapCursor(theDisplay, source, maSk,
For an arrow cursor, die hotspot is the point of the arrow. &fgcolor, &bgcolor, X_hot, y_hot);

You can assign a cursor to any window in your application. Typically, you create a new cursor from Another way to get a cursor is to select a specific character from a font and use the bitmap of that
a standard cursor font and assign it to a window: character as a cursor. Before using the font, you have to load the font by calling XloadFont (see
Chapter 10, "Drawing Text"). Then you can create the cursor using the function
#include <X11tcursorfont.h> XCreateG.lyphCursor (see its reference page in Appendix A for information on calling this func-
Cursor arrow_cursor;
tion).

XDefineCur'sor(theDisplay , my_window, arrow_cursor);


In the text of this book, some Xlib functions are described in derail; many others are
Once this is done, the cursor shape changes to the arrow cursor when the pointer enters the win-
mentioned briefly. If yOllneed more information on the calling syntax:of any function,
dow my_window.This selection remains in effect until you undefine the cursor for that window by
please consult the reference pages in Appendix"'A, "Xlib Functions." For quick access to the
calling XUndefineCursor. When you remove the cursor from a window, the server displays the
Xlib functions, check Appendix I, "Xlib Reference."
cursor of its parent when the pointer is in this window.

Note that the second argument to XCreateFontCursor specifies the cursor shape with a symbolic
name. These names are defined in the header file <X11/ cursorfont. h>.
ButtonPress and ButtonRelease Events /'~
After assigning a cursor to a window, if you do not intend to refer to it any more, you can free the
cursor by calling XFreeCursor. Any window that displays this cursor will continue to do so. The After you know how to create and control the cursor, you are re¥~o use,the mouse in a user in-
server will get rid of the cursor only after that cursor is not defined for any window. Once you undefine terface. For a simple case, in which you want something done when the user presses the mouse bunon
a cursor, you must not refer to that cursor's 10 again. in a specific window, you can handle the Butt~ onlyai1(fignore the 'ButtojiRelease
events. If you want to SUPPDrt mouse button clicks and double-clicks, you have to process both
button events. '--------

The file xbutton. c shown in Listing 8.3 shQwSan example of handling mouse bunonpress events.
When a cursor is created, it has, by default, a black foreground and a white background color. To To handle the event; you have to lookt~r the event type ButtonPress. Once you get the event,
change the color of a cursor, use the XRecolorCursor fu ction: you can perform the action associated with the bunonpress .