Attempt at input event documentation
Peter Hutterer
mailinglists at who-t.net
Wed Aug 9 00:38:32 PDT 2006
Hey,
In my attempt to write MPX I had to dig through a lot of functions
and try to figure out how a (mouse) event is created and sent to the
client. I wrote up a summary of how the X server processes input events.
If the information is useful (and correct), I am happy with putting
it up on the wiki or anywhere else where it is of use to other
developers. At the moment it uses wiki syntax (DokuWiki), I can
change that to whatever meets the requirements.
I'd appreciate any feedback, especially if I misunderstood something
and the information is wrong.
Cheers,
Peter
===== X Server Input Events =====
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 [[http://wearables.unisa.edu.au/mpx]].
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.
I 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.
==== Overview ====
Generally, input events live through two stages. One is the
generation, where
input is gathered from the connected devices and transformed into
abstract input
events (''struct xEvent'') and the processing of the events, where
the events
are adopted to match the client's requirements. In the processing
stage, more
abstract events (such as enter and leave events) are generated.
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.
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 for X Input.
* ''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 any client
events.
Lots and lots of functions are called using function pointers.
Finding them can be very frustrating. See the last section on 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 worth a read but I did not find a lot of information about how
input
events are handled.
==== Event creation ====
When a device emits events, a SIGIO is fired and ''xf86SIGIO()'' 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, this function is ''MouseReadInput()''.
''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()''.
For a motion event, we call now ''xf86PostMotionEvent()''. For button
events
it is ''xf86PostButtonEvent()''.
Here an actual ''struct xEvent'' is assembled and put
onto the event queue. Not all of the events values are set already and
X Input Extension (XI) devices and core devices are handled
differently here.
XI events get the type, time, detail, device ID and the valuators
set. Core
devices only have time, type and detail set, the device ID field and the
valuators are omitted.
One very interesting thing that happens in ''xf86PostMotionEvent()''
is that
only non-core events are put on the event queue.
Core events are processed but only if a ''drag'' flag is on. How is
the drag
flag determined? It is > 0 if no button is pressed. It is > 0 if a
button is
pressed and the device is set to send drag events. It is only 0, if
buttons
are down and the device is set to not emit drag events.
If the drag flag is set, ''miPointerAbsoluteCursor()'' is called with
the new
position. ''miPointerAbsoluteCursor()'' confines the coordinates to
the screen
range (after changing screen if necessary) and calls ''miPointerMove
()''.
Another ''xEvent'' is created here and filled with the values type
(''MotionNotify''), rootX, rootY and time. It is then put onto the event
queue. There is also one global ''miPointerRec'' that represents the
cursor
and it is updated to the new coordinates (and screen) here. We need that
later. If the screen needs to be updated, the cursor sprite is
rendered but we
ignore that for now.
So we can have two types on the event queue: XI events (they consist
of two
''struct XEvents'', one with the event and one with the device
valuators) and
core events.
To sum it up in short: each time a interrupt happens on one of the
sockets to an input event, the mouse driver reads the data, hands it
back to
the X Server which constructs a ''struct xEvent'' 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 ''ProcessInputEvents()'' which calls
''xf86eqProcessInputEvents()'' (it also does additional stuff while the
server starts up but we ignore that).
''xf86eqProcessInputEvents()'' runs through the event queue from
beginning to
end. Depending on the type of event, either ''ProcessKeyboardEvent()'',
''ProcessPointerEvent()'' or (in the case of XI devices)
''ProcessOtherEvent()''.
''ProcessPointerEvent()'' calls ''CoreProcessPointerEvent()''. And we
get to the
interesting bits.
For all events that are not ''MotionNotify'', the rootX and rootY
values are
set to the values of the sprite. For button presses, device grabs are
checked
and if there is one, nothing else happens. For ''MotionNotify''
events, we
call ''CheckMotion()''. After ''CheckMotion()'' finishes, the
delivery enters
its final stages with a call to ''DeliverDeviceEvents()'' or
''DeliverGrabbedEvents()'' (if the mouse has a grab). If the grab
needs to be
deactivated, we finish with a call to ''DeactivatePointerGrab()''.
So what does ''CheckMotion()'' do? 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.
So now that we have finishedthe enter/leave events we concentrate on
what the
final event processing consists of.
''DeliverDeviceEvents()'' has two code paths: one for extension
events, one
for core events but they are fairly similar. 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 pointer grab.
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.
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. If the cursor is fully
rendered in
hardware the functions just go directly into the driver and the card
writes
the cursor directly into the output stream. 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 ''miPointerUpdate()''. 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. If the
cursor is
within the saved region, the saved region is changed and the cursor
is moved.
If it is not, the cursor is 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.
==== Functions and where to find them ====
| Dispatch() | xserver/dix/
dispatch.c |
| WaitForSomething() | xserver/os/
WaitFor.c |
| SIGIO() | xserver/hw/xfree86/os-support/shared/
sigio.c |
| xf86SigioReadInput() | xserver/hw/xfree86/
xf86Events.c |
| MouseReadInput() | driver/xf86-input-mouse/src/
mouse.c |
| InputInfoPtr | xserver/hw/xfree86/common/
xf86Xinput.h |
| MousePostEvent() | driver/xf86-input-mouse/src/
mouse.c |
| MouseDoPostEvent() | driver/xf86-input-mouse/src/
mouse.c |
| xf86PostMotionEvent() | xserver/hw/xfree86/common/
xf86Xinput.c |
| xf86PostButtonEvent() | xserver/hw/xfree86/common/
xf86Xinput.c |
| struct xEvent | proto/X11/
Xproto.h |
| miPointerAbsoluteCursor() | xserver/mi/
mipointer.c |
| miPointerMove() | xserver/mi/
mipointer.c |
| struct miPointerRec | xserver/mi/
mipointrst.h |
| ProcessInputEvents() | xserver/hw/xfree86/common/
xf86Events.c |
| xf86eqProcessInputEvents() | xserver//hw/xfree86/common/
xf86Xinput.c |
| ProcessKeyboardEvent() | xserver/xkb/
xkbPrKeyEv.c |
| ProcessPointerEvent() | xserver/xkb/
xkbAccess.c |
| ProcessOtherEvent() | xserver/Xi/
exevents.c |
| DeliverDeviceEvents() | xserver/dix/
events.c |
| DeliverGrabbedEvents() | xserver/dix/
events.c |
| DeactivatePointerGrab() | xserver/dix/
events.c |
| CheckMotion() | xserver/mi/
mipointer.c |
| DoEnterLeaveEvents() | xserver/dix/
events.c |
| PostNewCursor() | xserver/dix/
events.c |
| EnterNotifies() | xserver/dix/
events.c |
| LeaveNotifies() | xserver/dix/
events.c |
| EnterNotifies() | xserver/dix/
events.c |
| EnterLeaveEvent() | xserver/dix/
events.c |
| FixUpEventFromWindow() | xserver/dix/
events.c |
| DeliverEventToWindow() | xserver/dix/
events.c |
| miPointerUpdate() | xserver/mi/
mipointer.c |
| struct miPointerScreenRec | xserver/mi/
mipointrst.h |
| miSpriteMoveCursor() | xserver/mi/
misprite.c |
| miSpriteSetCursor() | xserver/mi/
misprite.c |
| miSpriteRemoveCursor() | xserver/mi/
misprite.c |
| miSpriteRestoreCursor() | xserver/mi/
misprite.c |
| miDCRestoreUnderCursor() | xserver/mi/
midispcur.c |
| miDCSaveUnderCursor() | xserver/mi/
midispcur.c |
| miDCPutUpCursor() | xserver/mi/
midispcur.c |
| miDCChangeSave() | xserver/mi/
midispcur.c |
| miDCMoveCursor() | xserver/mi/
midispcur.c |
==== Using ctags to find functions ====
Finding functions in X is hard. One way to search for the actual
definition of
a data type is to grep the source directory and then open the file.
This can
take forever, especially when you don't quite know where to look for.
However, vim's support for ctags makes it easier. It is possible to
create a
tags file for the whole system and then just use it from within vim.
That way,
in vim you only have to go to the occurence of the data type, press
CTRL+] and
it will open the matching definition. With CTRL+T you jump back to the
original file.
I created my tags file somewhere in my .vim directory.
<code>
$> mkdir .vim/tags/
$> cd .vim/tags/
$> ctags -R /usr/include/* /path/to/X/source/code
</code>
ctags will create a file "tags" in the current directory ($HOME/.vim/
tags in
this case). This way I got pretty much all defintions I need at the
moment.
Now you need to tell vim to include this file. Add the following line
to your
$HOME/.vimrc.
<code>
set tags=./tags,tags,/home/username/.vim/tags/tags
</code>
On your next startup of vim, everything will be available with CTRL
+]. If you
use tags heavily, you will find CTRL+G helpful. It shows the name of
the file
in the current buffer.
A recommendation is to write a little script to update your ctags and
run it
as a cron job every night. Your computer will not be very responsive
while
recursively searching for ctags in a multi-GB directory.
== Warning ==
This can be a hazardous setup as the ctags are absolute. If you are
working on
two different source trees (i.e. two releases of the same software),
using
CTRL+ ] will jump to the functions as defined in ctags. So you might be
editing the wrong source tree.
--
Multi-Pointer X Server
http://wearables.unisa.edu.au/mpx
More information about the xorg
mailing list