This is a puny attempt to explain how the X Server generates and processes input events. This document was created as part of the development for MPX, the Multi-Pointer X Server. This document does not replace a good look at the source code. It just helps understanding what happens and what order functions are called. And it gives a general overview on how events are born and sent to the client.
We do not give any warranty that the information here is complete and/or accurate. The information here concentrates on pointer events but some is the same for keyboard events.
Updated 17.06.2010. Reflects input processing in X servers 1.7 through to including 1.9
Overview
Generally, input events live through two stages: Event generation and event processing. In the event generation stage, input is gathered from the connected devices and transformed into abstract input events, the so-called InternalEvents. In the processing stage, these InternalEvents events are converted to protocol events, depending on the event masks of the windows. An InternalEvent may be converted into a core event, an XI 1.x event or an XI2 event. More events such as enter and leave events are generated during the processing stage as well.
The event generation stage is part of the interrupt handling. The event processing stage is part of the processing loop (Dispatch()).
In between those two stages, there is the event queue. Events are put on the event queue after the creation stage and taken off again at the start of the processing stage. Only InternalEvents are ever on the event queue.
There are only a few directories that are interesting for all that:
- xserver/dix ... device independent X. The events.c file is handling most of the events.
- xserver/mi ... machine independent X. Mouse cursor rendering stuff.
- xserver/hw/xfree86/common ... some additional stuff, especially the driver interface.
- xserver/Xi ... X Input Extension protocol stuff.
The method the server spends the most time in is Dispatch(), and in this method the server mostly waits in WaitForSomething for requests from the clients and to send off accumulated input events from the input devices to the clients.
Lots and lots of functions are called using function pointers. Finding them can be very frustrating. See how to set up ctags to jump around in the source and find functions easier.
The DESIGN document
There is a document that describes the design of the X server. Depending on where you have the source tree the document is in xserver/hw/xfree86/doc/DESIGN.sgml or if you have the xserver-xorg package installed you should have it in /usr/share/doc/xserver-xorg/DESIGN.gz. It's also posted on the web site with the server developer documentation.
It's worth a read but I did not find a lot of information about how input events are handled.
An important requirement to understand events
X has the concept of core devices. These are the devices that are visible in the core protocol (i.e. whenever a client issues a GrabPointer request, the core pointer is picked).
With the introduction of X Input in 1994, the definition of an extension device was added. These devices could also send XI events (e.g. DeviceMotionNotify). A device could only be either an XI or a core device, not both - hence the need for the ChangePointerDevice and ChangeKeyboardDevice requests. However, an extension device what was configured to "SendCoreEvents" would cause both XI events on the device and core events on the core pointer device.
X server 1.4 introduced the notion of a "virtual core pointer" and "virtual core keyboard" (VCP and VCK, respectively). These devices are hardcoded to be the core devices with all physical devices now being extension devices. This obsoleted the ChangePointerDevice and ChangeKeyboardDevice request, the core devices were always the virtual ones. A device configured to "SendCoreEvents" would send cause the VCP or VCK to generate a core event as well as the extension event on the device itself.
X server 1.7 introduced XI2 and the master/slave device hierarchy. VCP and VCK are the first two "master devices", with all physical devices being "attached" to either one. These physical devices are referred to as "slave devices". Events are generated by the slave devices and then move up to the respective master device. A slave device may only generate XI 1.x or XI2 events, a master device may generate core events as well. With MPX, there may be more than one pair of master device but the principle remains the same.
All event generation is in dix/getevents.c, see GetPointerEvents and GetKeyboardEvents as starting points.
Event creation
When a device emits events, a SIGIO is fired and the xf86SIGIO() handler is called which in turn calls the xf86SigioReadInput() for the given socket. The latter in turn calls the read input function for the pointer provided. For the mouse driver, this function is MouseReadInput. The evdev driver has it own handler (EvdevReadInput) and so do all other drivers.
MouseReadInput is one of the functions in the InputInfoPtr of the mouse driver. It is set when the input driver is initialised and MousePreInit is called (see section 5.6 in the DESIGN doc). MouseReadInput does all the processing for the different mouse protocols and then posts the event via MousePostEvent (again a function pointer in the InputInfoPtr) into MouseDoPostEvent.
- So, if you are using the mouse, the sequence executed on the driver's side is: MouseReadInput, MousePostEvent, MouseDoPostEvent;
- The generic sequence is Driver-specific _ReadInput, driver-specific processsing, xf86Post{Motion|Button|Keyboard|Proximity}Event()
For a motion event, the driver calls now xf86PostMotionEvent() and we are back on the server's side. For button events it is xf86PostButtonEvent(). Those in turn call GetPointerEvents (GetKeyboardEvents for keyboard events) which creates the necessary number of events and returns them to the caller. GetTimeInMillis is called inside GetPointerEvents and timestamps the OS time on the event. Inside the same function, miPointerSetPosition() is called to re-paint the mouse on the screen. It calls miPointerMoved(). The miPointerMoved() decides to start the hw or the sw management/rendering of the cursor (see section Cursor rendering). After this choose the events are put - one by one - onto the event queue using mieqEnqueue().
Note that all this is inside the SIGIO handler, this is important as you may not allocate of free memory at any time in this stage.
GetPointerEvents will generate a number of InternalEvents, for this tutorial the interesting onces are the DeviceEvents which represent physical input (motion, button, key events)
To sum it up in short: each time a interrupt happens on one of the sockets to an input event, the device driver reads the data, hands it back to the X Server which constructs one or more InternalEvents and puts it onto the event queue.
Event processing
The event processing stage is the stage where the events are taken off the event queue, individually processed and then sent to the client. Also, more abstract input events (enter and leave notifies) are generated synthetically here.
All input are processed in the DDX ProcessInputEvents. The actual processing is done in mieqProcessInputEvents() which runs through the event queue from beginning to end. Main entry point for DIX-specific processing is ProcessOtherEvent, all events pass through here. Note at this point that the XKB extension wraps ProcessOtherEvents and is called before we get to POE. XKB is not subject to this tutorial.
ProcessOtherEvent does a few things. It updates the DIX-internal device state with the information from the event, then gathers some state required for the event itself. For example it grabs the modifier state from the keyboard to be put into mouse events as additional info. Finally, it calls down into the delivery paths that eventually write the protocol event onto the wire. At this point, we're still dealing with InternalEvents only.
ProcessOtherEvents also calls CheckMotion. This function updates the cursor sprite's position and then sets the event's coordinates to the new sprite positions. Finally, we compare the window the updated sprite is over with the previous one and call DoEnterLeaveEvent if necessary. If the window has changed, we also issue a call to PostNewCursor which basically changes to the updated cursor shape.
Let us see what DoEnterLeaveEvent does. If the old window is a parent of the new window, we issue a LeaveNotify to the old window, then recursively send EnterNotify events to the ancestors of the target window (this is done in EnterNotifies) and then finally a EnterNotify to our new window. If the old window is a child of the new window, we do the same but with the leave and enter notifies swapped around. Finally, if the window are not related, we send a LeaveNotify to the old window and then recursively to its parents (using LeaveNotifies), then recursively send EnterNotify events (using EnterNotifies again) to the new window's parents and finally a EnterNotify to the new window. Remember that there are multiple types of EnterNotify and LeaveNotify events. The ones sent to the parents are all of type NotifyVirtual (or NotifyNonlinearVirtual if the windows are unrelated). The ones sent to the old and the new window are of types NotifyAncestor or NotifyInferior for related windows and NotifyNonlinear for unrelated windows. All enter and leave events are constructed in EnterLeaveEvent. A xEvent is created, filled with values and then sent to the window using DeliverEventsToWindow. Again, rootX and rootY is taken from the sprite coordinates. This is the simple explanation, the real implementation is somewhat more difficult as we need to synchronise Enter/Leave events to be protocol-correct even if there are multiple master devices present.
So now that we have finished the enter/leave events we concentrate on what the final event processing consists of. The rule here is: an InternalEvent may be delivered as exactly one protocol type, but possibly to multiple clients. So if two clients both selected for core events, both will get the core event. If one client selected for core events and one for XI events, only the XI event is delivered. In the final event delivery path, the client masks are checked on each window in the delivery path and the InternalEvent is converted to the respective protocol event.
The event is adopted to the window in FixUpEventFromWindow and then delivered to the window with DeliverEventsToWindow. FixUpEventFromWindow adopts the window specific values to the event's window (the child, eventX and eventY values). If the delivery failed to a given window, the parent is tried until we run out of parent windows. DeliverEventToWindow calls TryClientEvents to write the events to the client. If the event is a button press event, DeliverEventToWindow also activates the implicit pointer grab (a grab that is deactivated automatically on the next button release event).
Now we have completed event processing, all the events were written to the client and we jump back to the last lines of ProcessInputEvents. What is left now is cursor rendering (if applicable), called with miPointerUpdateSprite().
Again, a short summary of the event processing stage: the server takes the events off the queue, fills them with the right variables, generate enter and leave notifies if necessary and writes them to the client.
Cursor rendering
Cursor rendering is a bit complicated to understand and hard to debug. It is a layered architecture to do as much in hardware as possible and pretty much everything is called via function pointers. Some need for function pointers has been removed with 1.9, but the basic priniciple is the same.
hw cursor
If the cursor is fully rendered in hardware, xf86CursorMoveCursor() and xf86MoveCursor() are the two first functions called. These functions are called just after the miPointerMoved(), the function that decides if the render will be in sw or hw, on the event creation stage. xf86MoveCursor() will call the video driver function desired to take care of the hw rendering. On my case, ATIMach64SetCursorPosition() is called. It do the calculations and paint the sprites on the memory mapped directly to the output stream. In other words, exactly on this moment the mouse is moved on the screen.
sw cursor
If it is done in sofware, the cursor has to be back-buffered. Every time it moves we restore the previous image, save the window at the target position, then render the cursor into the stream.
We start with everything at the end of ProcessInputEvents and the call to miPointerUpdateSprite(). Here we grab the current coordinates of the pointer (remember, they were set when we called miPointerMove() in the event generation stage) and call the MoveCursor function in the spriteFuncs of the miPointerScreenRec struct. Of course, if the cursor has changed screen or the shape has changed, this needs to be taken care of too. The MoveCursor function is set to miSpriteMoveCursor() which just calls miSpriteSetCursor(). This function first checks whether the cursor has changed at all and then the new positions of the cursor. The cursor is then removed with miSpriteRemoveCursor() and then restored at the new position with miSpriteRestoreCursor(). miSpriteRemoveCursor() is fairly simple, it just calls the restore function miDCRestoreUnderCursor(), which then calls the next layer (damage) to copy the saved area into the window at a given position. miSpriteRestoreCursor() saves the area under the cursor (miDCSaveUnderCursor()) into the buffer and then puts up the cursor again (miDCPutUpCursor()). If, as mentioned before, the new position is insided the saved buffer, a call to miDCChangeSave() updates the saved region and a call to miDCMoveCursor() will move the cursor. This moving doesn't cause any flickering, the remove and restore procedure may flicker.
As easy as this sounds, there is more to cursor rendering. Quite a fair bit of work is done outside this explicit rendering calls that are issued when all input events have been processed. Interestingly, pretty much all other function that handle sprite rendering (everything with miSprite...) basically remove the cursor from the screen if necessary (i.e. when the window is moved). The one exception is the block handler function (called when there's nothing else to do and the server would block while waiting for input). miSpriteBlockHandler() checks if the cursor was previously removed but should be visible and renders it to the screen again if necessary.
What must be clear on the cursor rendering is: hw cursor is rendered before the event be enqueued (i.e., on the event creation stage) and sw cursor is rendered after the ProcessInputEvents (i.e., after the event creation and after the event processing stage).