MPX and Enter/Leave events

Owen Taylor otaylor at redhat.com
Fri Aug 8 12:28:33 PDT 2008


I've been doing some reading recently in the MPX changes, and one thing
that concerns me is the handling of Enter/Leave events in the presence
of multiple pointers. (FocusIn/FocusOut is very similar, but there are
some extra wrinkles there, and I'm going to ignore them for the purposes
of this mail.)

The current handling is described by text described in a comment:

   Sending multiple core enter/leave events to the same window 
   confuses the client.  We can send multiple events that have detail 
   NotifyVirtual or NotifyNonlinearVirtual however. For most clients
   anyway.

   For standard events (NotifyAncestor, NotifyInferior, NotifyNonlinear)
   we only send an enter event for the first pointer to enter. A leave 
   event is sent for the last pointer to leave.  For events with Virtual
   detail, we send them only to a window that does not have a pointer
   inside.

   For a window tree in the form of

     A -> Bp -> C -> D+     
      \               (where B and E have pointers)
       -> Ep


   If the pointer moves from E into D, a LeaveNotify is sent to E,
   an EnterNotify is sent to D, an EnterNotify with detail
   NotifyNonlinearVirtual to C and nothing to B.

The problem to me is that "For most clients anyways" ... let me show
a fragment of real code from GtkButton:

  if ((event_widget == widget) &&
      (event->detail != GDK_NOTIFY_INFERIOR))
    {
      button->in_button = TRUE;
      gtk_button_enter (button);
    }

GtkButton considers the pointer to be in the button (causing it to 
prelight) if it's in the button's widget, *or any subwindow*. An
enter with a detail of NotifyVirtual or NotifyNonlinearVirtual is an
enter, even though the pointer ends up on a subwindow. On the other
hand, an enter with a notify of NotifyInferior is not an enter, because
it means that the pointer went from the window itself to a subwindow.


This to allow a subwindow that displays an image but still can be
clicked on as part of the button. [ Let's say this is GTK+ version
1.2. While the code is the same in GTK+ 2.x, the window structure is
more
complex and confuses the issue. ]


Consider the case:

 Pointer 1 enters window (enter, detail=NotifyAncestor)
 Pointer 2 enters window (nothing)
 Pointer 1 goes from window to inferior (nothing)
 Pointer 2 goes from window to ancestor (leave, detail=NotifyAncestor)

GTK+ will think there is no pointer within the window, and the button
will not be clickable, but pointer 1 is still within the window.

In essence what the current MPX solution does is say:

 For events delivered to a window, the pointer is considered to be
 within the window itself if there is at least one pointer within
 the window itself.

But that neglects the fact that X allows not just tracking whether
the pointer is within a window itself: it also allows tracking whether
the pointer is within a descendant. So, the above statement is not
sufficient to allow us to determine coherently what events should be
delivered to a window, and what the detail fields should be.

As an aside, it should be clear that there is no solution to the problem
of delivering core events that is 100% compatible with any conceivable 
X application ... if an X app looks at all events delivered to all
windows, then the only compatible approach is to say that there is a
single unique window with the pointer, which really ruins the 
whole idea of MPX. I think what should strive for is a *locally*
compatible view: that any single window should get core events that 
look like there is a single unique window with the pointer, but that
different windows may have different views of what that single unique
window is.

What I would propose as a better solution is to say that for the
purposes of delivering events to a *single* window W, we should 
generate events as if the pointer window was determined as follows:

 If there is at least one pointer located on the window itself,
 not counting the subwindows, the pointer window is the window 
 itself.
                                                                                                                                                                                                  
 Otherwise, if there is at least one pointer located within a 
 descendant of W, then the pointer window is the descendant
 containing the descendant with the highest priority pointer.

 Otherwise, the pointer window is the window with the highest
 priority pointer.

This involves putting an arbitrary priority, ordering on the pointers
but it will turn out that it has very little impact, and only
affects what gets put into the 'subwindow' field of crossing events.

What holds, given the above definition, is:

 When a pointer moves from one window A to another window B, the
 events generated are a subset of the events generated in a classic
 X server for the single pointer moving from A to B, in the following
 sense:

  - The events are sent to the same window
  - The events are sent are of the same type (enter or leave) 
  - In some cases the event may be suppressed
  - In some cases the detail field may be changed
  - In some cases the subwindow field may be changed

The full case-by-case analysis is appended to the end of the message.

This result means that it's possible to implement idea without a lot
of extra complexity.. while the case-by-case analysis has 19 cases,
that's not meant to be literally the implementation technique, what it's
meant to show is that everything can be done by modifying
EnterLeaveEvent ... that we don't have to generate extra events. 

What I think the right thing to do is to compute the new and old P(W),
and determine from that whether the event should be suppressed, and if
not, the appropriate detail and subwindow fields.

Anyways, that's my thinking on the issue; I think the current approach
is going to a good amount of existing code (especially older code) to
fall over in weird ways. My guess is that same approach should be
extensible to handle keyboard focus as well, though PointerRoot makes
things somewhat more "fun".

- Owen

Appendix
========

Definition: a window is 'above' W if it is not W or a descendant of W
Notation: P(W) - the pointer window as seen by W

Case 1: 
  A and B are above W

  Classically: The move generates no events on W 
  MPX: P(W) doesn't change, so no events should be generated on W

Case 2:
  A is above W, B=W
  
  Classically: The move generates an EnterNotify on W with a detail of
    Ancestor or Nonlinear

  MPX:
    Case 2A: There is at least one other pointer on W itself
      P(W) doesn't change, so the event should be suppressed
    Case 2B: Otherwise, if there is at least one other pointer in a
      descendant
      P(W) moves from a descendant to W. detail is changed to Inferior,
      subwindow is set to the child containing the previous P(W)
    Case 2C: Otherwise:
      P(W) changes from a window above W to W itself. 
      The detail may need to be changed from Ancestor to Nonlinear
      or vice-versa depending on the previous P(W).

Case 3:
 A is above W, B is a descendant

  Classically: The move generates an EnterNotify on W with a detail of
    Virtual or NonlinearVirtual

 MPX:
    Case 3A: There is at least one other pointer on W itself
      P(W) doesn't change, so the event should be suppressed
    Case 3B: Otherwise, if there is at least one other pointer in a
      descendant
      P(W) stays on the same descendant, or changes to a different
      descendant. The event should be suppressed.
    Case 3C: Otherwise:
      P(W) moves from a window above W to a descendant. The subwindow
      field is set to the child containing the descendant. The detail
      may need to be changed from Virtual to NonlinearVirtual depending
      on the previous P(W).

Case 4:
 A is W, B is above W
 
Classically: The move generates a LeaveNotify on W with a detail of
   Ancestor or Nonlinear

 MPX:
    Case 3A: There is at least one other pointer on W itself
      P(W) doesn't change, the event should be suppressed
    Case 3B: Otherwise, if there is at least one other pointer in a
    descendant of W
      P(W) changes from W to a descendant of W. The subwindow field
      is set to the child containing the new P(W), the detail field
      is set to Inferior
    Case 3C: Otherwise:
      The pointer window moves from W to a window above W.
      The detail may need to be changed from Ancestor to Nonlinear or
      vice versa depending on the the new P(W)
 
Case 5:
A is W, B is W

 Nothing happens

Case 6:
A is W, B is a descendant of W

Classically: A LeaveNotify is generated on W with a detail of
   NotifyInferior

MPX:
    Case 3A: There is at least one other pointer on W itself
      P(W) doesn't change, the event should be suppressed
    Case 3B: Otherwise:
      P(W) changes from W to a descendant of W. The subwindow field
      is set to the child containing the new P(W)
      
Case 7:
A is a descendant of W, B is above W

Classically: A LeaveNotify is generated on W with a detail of Virtual
  or NonlinearVirtual.

MPX:
    Case 3A: There is at least one other pointer on W itself
      P(W) doesn't change, the event should be suppressed.
    Case 3B: Otherwise, if there is at least one other pointer in a
    descendant
      P(W) stays on the same descendant, or changes to a different
      descendant. The event should be suppressed.
    Case 3C: Otherwise:
      P(W) changes from the descendant of W to a window above W.
      The detail may need to be changed from Virtual to NonlinearVirtual
      or vice-versa depending on the new P(W).
       
Case 8:
A is a descendant of W, B is W

Classically: A EnterNotify is generated on W with a detail of
    NotifyInferior

MPX:
    Case 3A: There is at least one other pointer on W itself
      P(W) doesn't change, the event should be suppressed
    Case 3B: Otherwise:
      P(W) changes from a descendant to W itself. The subwindow
      field should be set to the child containing the old P(W)

Case 9:
A is a descendant of W, B is a descendant of W

Classically: No events are generated on W
MPX: The pointer window stays the same or moves to a different 
  descendant of W. No events should be generated on W.






More information about the xorg mailing list