Monday, December 10, 2012

What's new in XI 2.3 - Pointer Barrier events and barrier releases

The main feature making up XI 2.3 is Pointer Barrier events. This feature was initiated by
Chris Halse-Rogers and then taken up by Jasper St. Pierre and me, until it became part of XI 2.3.

The usual conditions apply: a client must announce XI2.3 support with the XIQueryVersion() request to utilise any of the below.

Pointer barriers

Pointer Barriers are an XFixes v5.0 additions to restrict pointer movement. They were first available in server 1.11 (released Aug 2011). A pointer barrier is created by a client along a specific horizontal or vertical line of pixels. Relative input devices such as mice or touchpads will be constrained by this barrier, preventing the cursor from moving across the barrier.

For example, GNOME3 creates a vertical barrier on the top-left screen edge. In a multi-monitor screen, flicking the pointer up to the Activities menu will constrain the pointer there even if there is a screen to the left of the menu. This makes the menu much easier to hit.

The client can choose for the barrier to be transparent in specific directions. GNOME3's pointer barrier is permissive in the direction of positive X (i.e. left-to-right). Thus, moving from the left screen to the right is unrestricted, even though the pointer is constrained when moving in the other direction.

A simple client that creates a vertical pointer barrier looks like this:

The above code will set up a barrier for all devices, blocking in all
directions.

Pointer barrier events

As of XI 2.3, pointer barrier events are sent to clients when pointer movement is constrained by a barrier, provided that client created pointer barriers, the clients have the matching event mask set. Two new event types were added, XI_BarrierHit and XI_BarrierLeave. Both events are only sent to clients owning a barrier and are only sent to the window used to create the barrier (the root window in the example above).

An XI_BarrierHit event is first sent when the pointer movement is first
constrained by a barrier. It includes some information such as the device,
the barrier and the window. It also includes coordinate data.

XI_BarrierHit events are sent for each movement of the pointer against this barrier, or along it, until the pointer finally moves away from the barrier again. "Moving away" means a move to different position that is not blocked by the barrier.

if the barrier is vertical, the pointer moves to a different X axis value, or

if the barrier is horizontal, the pointer moves to a different Y axis value, or

the pointer moves past the barrier's end point

Once the pointer does move away, a XI_BarrierLeave event is sent.
A pointer that moves against a barrier, pushes against it for 3
more events and then pulls back will thus generate 4 XI_BarrierHit events and
one XI_BarrierLeave event.

Who gets events? Always the client that created the barrier, and only if the
window has the event mask set. If the client has a grab on the device with
the grab_window being the barrier window, the barrier events follow the
usual grab event mask behaviour:

if the grab event mask has XI_BarrierHit set, the event is delivered

if the grab event mask does not have XI_BarrierHit set but the window
mask does and owner_events is True, the event is delivered

if owner_events is False and the grab mask does not have
XI_BarrierHit set, no event is sent

The above applies to XI_BarrierLeave events as well.

If the client's grab has a grab_window different to the barrier window, or
the device is grabbed by another client, event delivery is as usual. In all
cases, if the device is grabbed, the XIBarrierDeviceIsGrabbed flag is set.
Clients should use this flag to determine what to do. For example, the
barrier that is used to trigger the GNOME overlay should probably not
trigger if another client has a grab as it may interfere with drag-and-drop.

Coordinates and time in pointer barrier events

Barrier events contain two sets of x/y coordinates. First, the root
coordinates which represent the position of the pointer after being confined
by barrier (and screen extents, where applicable). This coordinate is the
same you would get from a subsequent XIQueryPointer request.

The second set are the delta coordinates (dx/dy), in screen coordinates, from the last pointer position, had the barrier not constrained it. So you can calculate how much the pointer would have moved and thus derive speed. The dtime field in the event helps you to calculate speed, it provides the time in milliseconds since the last pointer event. The deltas are calculated after taking pointer acceleration into account.

Releasing a pointer from barrier constraints

By default, a pointer barrier blocks all movement of relative input devices
across a barrier. However, a client can opt to temporarily release the
pointer from the barrier constraints with the XIBarrierReleasePointer request.

To do so, the client needs the event ID of the barrier event. Since a pointer may bump against the same barrier multiple times before the client reacts (X is asynchronous, after all), the event ID serves to identify a set of movements against or along the pointer barrier.

An event ID is assigned to the first XI_BarrierHit event, and then it remains
the same until the XI_BarrierLeave event. This event is the
last event with the current event ID, any future barrier events will have a
new event ID.
This approach may be familiar to you from dealing with touch
events, that use a similar approach (touch IDs start at TouchBegin and are
tracked through to the TouchEnd).

To release a pointer and let it pass through the barrier, call XIBarrierReleasePointer().

If, when the request arrives at the server, the pointer is still trapped by the barrier, the barrier is now transparent and the pointer can move through it with the next movement. If the pointer moves away from the barrier after releasing it and later moves against this barrier again, it will be constrained once more (albeit with a different eventid). Likewise, if the pointer has already moved away and against the barrier again before the client reacted, the release request has no effect.

If the release does succeed and the pointer moves through the barrier, the client gets a XI_BarrierLeave event with the XIBarrierPointerReleased flag.

The pointer barrier hit-box

As mentioned above, a XI_BarrierLeave event is sent when the pointer moves away from the barrier. This would usually require a 1 pixel movement (let's ignore subpixel-movement, our hand's aren't that precise). However, during testing we found that 1 px pointer-movement can still happen even when a user tries hart to move along the barrier, or even when pushing against the barrier. Thus, we implemented a hit-box of 2 pixels. Thus, a 1 px movement along the barrier still counts as hitting the barrier, and the pointer is not treated as having left the barrier until it leaves the hit-box.

Testing the current state

The current code is available in the barriers branch of the following repositories: