Some important changes pushed again. Let me explain (get yourself a beverage of your choice, this may take a while).
Historically, devices generate two input events: one core event, one XI event. The latter was ignored by pretty much everything but the GIMP, the former is basically what is used by every app out there. These two events came from two different devices, the core event always came from the core pointer, the XI event from the device directly.
With MPX, this doesn't work. XI is required to actually differ between devices, but core events are required as well if we want to actually use anything.
So one of the changes in MPX that was introduced many moons ago is that both core and XI events come from the same device. Likewise, if an app grabs the pointer, it would grab the device (search for ClientPointer in the archives), allowing for multiple pointer grabs on multiple device. But to ensure apps work correctly, only a single grab per device. Sounds good in theory, doesn't it?
Fast forward until last week. We* ran into the following problem: If an app would listen for DeviceButtonPress under a window manager that puts a passive core grab onto the button, the XI events never get through. Metacity with focus-follows mouse falls under that category. Turns out that the event freezing semantics caused the XI event to "disappear". So step one was to figure out WTF can be done.
Each device has an event queue for when event processing is frozen (during synchronous grabs). And with the code, the event that caused the freeze would be stored in the EQ. In our case, this was the core event. When that happend, the XI event was lost, and when the device was unfrozen, the XI event surprisingly did not arise like phoenix from the ashes but stayed lost. Probably in a pub, drinking beer, laughing at me.
After a bit of hacking, I changed the code that the XI event would get stored as well, after the core event. When the device is unfrozen, the server first resends the initial event (the core press, remember?) and then subsequent events. So my theory was that it would try to send the core event to the app, fail and then try the XI event and we can live happily ever after.
Wrong. Turns out metacity listens for button presses on the container window. And X traverses the window hierarch upwards for each event until it can be delivered. So the unfrozen core event actually got sent to metacity. This in turn caused an implicit passive grab to activate inside the X server (I think I wrote about that ages ago). And when it was time to process the XI event, the grab would discard the XI event. After all, metacity doesn't do XI.
The solution one was reasonably simple: fix up the passive grab event processing and frozen event queue to emulate core events on demand, but actually only ever deal with XI events. So even thought the core event is send to the client, we store the XI event in the device's EQ. And when thawing the device, the XI event is delivered first, and if not, a core event is generated and then sent.
Then we** found something else. If an app would listen for DeviceButtonPress and the window manager for ButtonPress on a parent window, trouble would pop in for a quick hello. The ButtonPress event, delivered to the WM would cause an implicit passive grab on the device. Then, the XI event would cause another implicit passive grab to activate. And it would overwrite the previous core implicit passive grab.
The result? Metacity never got a release event, so the window was following each and every mouse move. And while this behaviour is appropriate for, say, cat, a window manager has more important things to do. Like managing windows.
The solution to this is to support two grabs on a device. While at the same time only supporting a single grab on the device.
And up to now you thought you had it hard...
This is when I changed how input devices are perceived in the server. Instead of two separate events being processed, a single input event is pumped through two different APIs (core and XI). Sounds like esoterical BS but it helps to understand the whole problem better.
Each event can only be sent to a single application. First we try to send it as core, then as XI event. If an app receives either, processing stops. Otherwise, we try again on the parent window, until we reach the root window or time stops. Whichever is sooner.
The significant change with all this is that an event can only be sent to a single client. If the button press was sent to a core client, no XI client will receive the same press as an XI event. Which makes sense, as this means that an input event can only cause a single action, and not two actions in two different apps.
Ok. Not that exciting, I admit. But writing this gave me a perfect excuse to procrastinate from writing my thesis. After all, that's the main goal in life. Thank you for your attention.
*curtain falls. thunderous applause*
* by "we" I mean Baljinder, one of the lab's summer interns, who's writing a multi-device diagram editor.
* by "we" I mean my colleague Ben who's adopting FLTK to work with MPX.
|