This page describes the server internals of multitouch event processing for XI 2.2. Documentation may get out of date, assume that the final reference is always the code. In order to understand this text, you need to be familiar with the XI 2.2 protocol specification. Many of the terms used here will not make sense otherwise.
Pointer emulation is the most complicated part of the code since it needs to transparently work around pointer grabs in all three protocols (core, xi2, xi).
Pointer emulation is decided on in the event generation code: if a new TouchBegin is submitted and no other emulating touchpoint is currently active, the new touchpoint becomes the emulating touchpoint. This code has a few corner cases that will be buggy. E.g. two touch device submitting their first event through the master, both will assume pointer emulation. Furthermore, the events the server deals with internally are always touch events. If a pointer-emulating touch should send pointer events, this conversion happens quite late and only for the wire protocol. The reason is simple - if an event is replayed, we need to know that the original event was a touch event to make sure we deliver the right type to the next client.
The driver API consists of one new call to set up the device: InitTouchClassDeviceStruct. For the actual valuators, the drivers should use the existing API xf86InitValuatorAxisStruct, just as they did before.
Touch events can be submitted to the server with xf86PostTouchEvent(). Each touch event must have touch ID unique for the duration of the touch. Touch IDs must be new for XI_TouchBegin events and one of the currently used ones for XI_TouchUpdate or XI_TouchEnd events. Once a touchid is used for a XI_TouchEnd event, the driver can re-use it.
The xfree86 input API has a single call for drivers to submit touch events: xf86PostTouchEvent(). This is a wrapper around the DIX' GetTouchEvent()_ which is the main event generation function. Driver events are usually submitted in a SIGIO handler, valuator data is converted into an InternalEvent (DeviceEvent) and appended to the event queue. During event processing time the events come out of the event queue, where they affect the logical state of the device and are sent to the clients. This is the same process as for other input events.
Drivers cannot generate TouchOwnership events, they are generated in the server only.
Each touch-capable device has a number of DDX touchpoints which represent the physical touchpoints as seen by the driver. DDX touchpoints are the equivalent to last.valuators. These touchpoints are not affected by grabs, they begin on a driver-submitted TouchBegin and end on a driver-submitted TouchEnd event. Each DDX touchpoints has two touch IDs, the driver's touch ID and the protocol-visible client ID. Either must be unique but they are unlikely to ever be identical. The driver's touch ID is irrelevant outside the event generation code, it is only used for matching TouchUpdate/TouchEnd events to the matching touch points. Once that's done, the (server-assigned) client ID is the only one that matters.
Pointer emulation is decided on DDX touchpoint creation - if the touchpoint is the first one on the device pointer emulation is enabled. Otherwise, or if the device is a DependendTouch device, pointer emulation is disabled. DDX touchpoints are separate from the DIX, so pointer emulation and other information is transferred via event flags.
Note that due to the grab semantics, touch events may be submitted from the DIX with the client-ID as identifier. This is indicated with a flag.
Raw touch events are virtually identical to pointer/keyboard raw events and do not require specific handling. Assume that raw events are handled like pointer events. For pointer-emulating touches, no raw events are generated.
In event processing, the touch events are passed from the event queue into ProcessTouchEvents. The master/slave device hierarchy is handled identical to other input events. Touch events are delivered to a bunch of listeners that are established on TouchBegin. Event mask changes during touch sequences have no effect on the listeners, and the listener list is static, listeners may not be added after the TouchBegin. Listeners may be removed if they will not receive further events for this touch sequence.
All listeners have a state that specifies the next event that listener expects to receive.
- LISTENER_AWAITING_BEGIN: the listener has not received the TouchBegin yet. Listeners not supporting ownership will be in this state until they become the owner.
- LISTENER_AWAITING_OWNER: the listener has received a TouchBegin but is not yet the current owner. It must receive a TouchOwnership event first.
- LISTENER_IS_OWNER: the listener has received the TouchBegin and, if applicable, a TouchOwnership event. If the listener is a grab, it must now accept/reject the touch
LISTENER_HAS_END: the listener has received the TouchEnd event but the server is still waiting for a accept/reject. Listeners also have a type and the input level they work on. The level is one of XI2, XI or CORE, with the latter two only being available to pointer emulation. The type is one of
LISTENER_REGULAR: window event mask selected for touch events, type always XI2
- LISTENER_GRAB: passive grab selected for touch events, type is always XI2
- LISTENER_POINTER_REGULAR: window event mask selected for pointer events, any type
- LISTENER_POINTER_GRAB: passive grab selected for pointer events, any type.
A note on the code: listeners states and types were introduced quite late and the earlier MT patches do not make use of them. This history may be squashed out
On the initial TouchBegin event, a DIX touchpoint is generated. This touchpoint is re-used for future TouchUpdate/TouchEnd; it is a bug if a touchpoint cannot be found for a TouchUpdate/End event. A touchpoint has a number of listeners (recipients for events). The current position of the sprite identifies the window trace. This trace is traversed root-to-bottom for any touch grabs and then again bottom-to-root for the first applicable touch event selection. Any matching grab or client is added to the listeners list. This listener list is static, listeners may not be added after the TouchBegin. Listeners may be removed if they will not receive further events for this touch sequence. If any of the listeners does not support touch ownership events, a touch history is allocated and all touch events are stored for replaying on reject. Listeners not supporting touch ownership that are not the current owner do not receive the TouchBegin event until the current owner has rejected the touch.
The TouchBegin event is sent to the owner of the touch (top-most grab or the window if no grab is active), and to any listener that supports ownership events.
If a touch emulates pointers, a MotionNotify event is generated and sent before the TouchBegin is processed. In addition to touch event masks, grabs and windows are also checked for an applicable pointer event mask. Each grab or window is first checked for a touch mask, then for XI2, XI, core pointer events (in that order) before the traversal moves to the next grab/window. Thus, a pointer selection on a window will prevent a touch selection on a higher window.
A ButtonPress is sent only if the touch is emulating and the current owner of the touch requires button events.
On the intial TouchBegin, the device state is updated and the device appears with one button logically down.
TouchUpdate events are sent to the current owner and all listeners that support touch ownership.
A MotionNotify event is generated and sent exclusively to the current owner if applicable.
A driver-generated TouchEnd event is sent exclusively to the current owner. If any other listener supports touch ownership, the event is sent as TouchUpdate with the pending flag to all these listeners. If no grab is active, the TouchEnd finalises the touch, otherwise the current owner must accept or reject the touch.
If the touch is accepted, a new TouchEnd is generated and sent to all listeners supporting ownership (those not supporting ownership have never seen the TouchBegin). If the touch is rejected, a TouchEnd event is generated and sent to the current owner unless that owner is in LISTENER_HAS_END. That owner is removed from the listeners.
If the next listener is in state LISTENER_AWAITING_BEGIN, the event history is replayed for that listener (i.e. the TouchBegin and all TouchUpdate events). The listener moves to state LISTENER_IS_OWNER.
If the next listener is in state LISTENER_AWAITING_OWNER, a TouchOwnership event is generated and sent to the that listener. The listener moves to state LISTENER_IS_OWNER.
It is now the new owner's job to accept or reject the touch. A event history may be replayed multiple times.
A MotionNotify event is generated and sent to the current owner if applicable before the TouchEnd event is processed.
On the final TouchEnd event, the device state is updated and the device appears with the button logically up.
Replaying the event history on a reject causes the respective pointer events to be delivered to the current listener.