Fix a bug in focus tracking when we move between has_pointer_focus and

2005-05-04  Owen Taylor  <otaylor@redhat.com>

        * gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
        in focus tracking when we move between has_pointer_focus and
        has_focus_window directly. (#109246, Billy Biggs, Niko Tyni
        and others)

        * gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
        that could happen in the case of no window manager + keyboard grabs,
        by moving to a more consistent model of when we pay attention
        to mode=NotifyGrab/NotifyUngrab events.

        * docs/focus_tracking.txt: Extensive writeup about how to track
        focus under X11
This commit is contained in:
Owen Taylor 2005-05-05 00:12:21 +00:00 committed by Owen Taylor
parent d990d01640
commit dcedc5bcef
6 changed files with 275 additions and 26 deletions

View File

@ -1,3 +1,18 @@
2005-05-04 Owen Taylor <otaylor@redhat.com>
* gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
in focus tracking when we move between has_pointer_focus and
has_focus_window directly. (#109246, Billy Biggs, Niko Tyni
and others)
* gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
that could happen in the case of no window manager + keyboard grabs,
by moving to a more consistent model of when we pay attention
to mode=NotifyGrab/NotifyUngrab events.
* docs/focus_tracking.txt: Extensive writeup about how to track
focus under X11.
Wed May 4 13:21:41 2005 Søren Sandmann <sandmann@redhat.com>
* tests/testcairo.c (draw): Replace cairo_show_surface() uses with

View File

@ -1,3 +1,18 @@
2005-05-04 Owen Taylor <otaylor@redhat.com>
* gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
in focus tracking when we move between has_pointer_focus and
has_focus_window directly. (#109246, Billy Biggs, Niko Tyni
and others)
* gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
that could happen in the case of no window manager + keyboard grabs,
by moving to a more consistent model of when we pay attention
to mode=NotifyGrab/NotifyUngrab events.
* docs/focus_tracking.txt: Extensive writeup about how to track
focus under X11.
Wed May 4 13:21:41 2005 Søren Sandmann <sandmann@redhat.com>
* tests/testcairo.c (draw): Replace cairo_show_surface() uses with

View File

@ -1,3 +1,18 @@
2005-05-04 Owen Taylor <otaylor@redhat.com>
* gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
in focus tracking when we move between has_pointer_focus and
has_focus_window directly. (#109246, Billy Biggs, Niko Tyni
and others)
* gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
that could happen in the case of no window manager + keyboard grabs,
by moving to a more consistent model of when we pay attention
to mode=NotifyGrab/NotifyUngrab events.
* docs/focus_tracking.txt: Extensive writeup about how to track
focus under X11.
Wed May 4 13:21:41 2005 Søren Sandmann <sandmann@redhat.com>
* tests/testcairo.c (draw): Replace cairo_show_surface() uses with

161
docs/focus_tracking.txt Normal file
View File

@ -0,0 +1,161 @@
Notational conventions
======================
We have a window W that we are tracking events on. Focus
can be on the following classes of objects
None : defined by X protocol
PointerRoot : defined by X protocol
W : the window itself
Ancestor : An ancestor of W, including W's root window
Descendant : A descendant of W
Other: : A window that is neither an ancestor or
descendant of W
has_pointer(W): the pointer is in W or one of its descendants.
NotifyPointer events
====================
X sends FocusIn or FocusOut events to W with a detail of NotifyPointer
in the following transitions, when the pointer is inside W
Other => Ancestor: FocusIn
Ancestor => {Other,None}: FocusOut
Ancestor => PointerRoot: FocusOut, then FocusIn
{None,W,Descendant,Other} => PointerRoot: FocusIn
PointerRoot => Ancestor: FocusOut, then FocusIn
PointerRoot => {None,W,Descendant,Other} => FocusOut
[ Ignoring keyboard grabs for the moment ]
Basic focus tracking algorithm
==============================
Keystroke events are delivered within W if and only if one of two
predicates hold:
has_focus_window(W): F==W || F==Descendant
has_pointer_focus(W): (F==Ancestor || F==PointerRoot) && has_pointer(W)
These two conditions are mutually exclusive.
has_focus_window(W) is easy to track.
FocusIn: detail != NotifyInferior: Set has_focus_iwndow
FocusOut: detail != NotifyInferior: Clear has_focus_iwndow
has_pointer_focus(W) is harder to track.
We can separate out the transitions from !has_pointer_focus(W) to
has_pointer_focus(W) into four cases:
T1: [(F==W || F==Descendant) => F==Ancestor]; has_pointer(W)
T2: [(F==W || F==Descendant) => F==PointerRoot]; has_pointer(W)
T3: [(F==None || F==Other) => (F==PointerRoot || F==Ancestor)];
has_pointer(W)
T4: [!has_pointer(W) => has_pointer(W)]; (F==Ancestor || F==PointerRoot)
All of these can be tracked by watching events on W.
T1:, we get a FocusOut with a mode of Ancestor or Virtual
We need to separately track has_pointer(W) to distinguish
this from the case where we get these events and !has_pointer(W)
T2, T3: together these are exactly the cases where we get
FocusIn/NotifyPointer.
For T4, we get an EnterNotify with the focus flag set. An
EnterNotify with a focus flag set will also be sent if
F==W, so we have to to explicitly test for that case
using has_focus_window(W)
The transitions from has_pointer_focus(W) to !has_pointer_focus(W)
are exactly the opposite
F1: [(F==W || F==Descendant) <= F==Ancestor]; has_pointer(W)
F2: [(F==W || F==Descendant) <= F==PointerRoot]; has_pointer(W)
F3: [(F==None || F==Other) <= (F==PointerRoot || F==Ancestor)];
has_pointer(W)
F4: [!has_pointer(W) <= has_pointer(W)]; (F==Ancestor || F==PointerRoot)
And can be tracked in the same ways:
F1: we get a FocusIn with a mode of Ancestor or Virtual
We need to separately track has_pointer(W) to distinguish
this from the case we get these events and !has_pointer(W)
F2, F3: together these are exactly the cases where we get
FocusOut/NotifyPointer.
F4: we get an LeaveNotify with the focus flag set. An
LeaveNotify with a focus flag set will also be sent if
F==W, so we have to to explicity test for that case
using has_focus_window(W).
Modifications for keyboard grabs
================================
The above algorithm ignores keyboard grabs, which also
generate focus events, and needs to be modified somewhat
to take keyboard grabs into effect. The basic idea
is that for has_pointer_focus(W)/has_window_focus(W) we track
them ignoring grabs and ungrabs, and then supplement
that with another predicate has_focus(W) which pays
attention to grabs and ungrabs.
Modification 1:
When tracking has_pointer_focus(W), ignore all Focus
events with a mode of NotifyGrab or NotifyUngrab.
Note that this means that with grabs, we don't perfectly.
track the delivery of keyboard events ... since we think
we are getting events in the case where
has_pointer_focus(W) && !(G == None || G==W || G==descendant)
But the X protocol doesn't provide sufficient information
to do this right... example:
F=Ancestor, G=None => F=Ancestor, G=Ancestor
We stop getting events, but receive no notification.
The case of no window manager and keyboard grabs is pretty
rare in any case.
Modification 2:
When tracking has_focus_window(W), ignore all Focus
events with a mode of NotifyGrab or NotifyUngrab.
Modification 3: instead of calculating focus as
has_focus_window(W) || has_pointer_focus(W)
Calculate it as
has_focus(W) || has_pointer_focus(W)
where has_focus(W) is defined as:
has_focus(W): F==W || F==Descendant || G=W
Tracking has_focus(W) is done by
FocusIn: detail != NotifyInferior, mode != NotifyWhileGrabbed:
set has_focus
FocusOut: detail != NotifyInferior, mode != NotifyWhileGrabbed:
clear has_focus
We still need to track has_focus_window(W) for the T4/F4
transitions.

View File

@ -1218,16 +1218,19 @@ gdk_event_translate (GdkDisplay *display,
}
/* Handle focusing (in the case where no window manager is running */
if (toplevel &&
xevent->xcrossing.detail != NotifyInferior &&
xevent->xcrossing.focus && !toplevel->has_focus_window)
if (toplevel && xevent->xcrossing.detail != NotifyInferior)
{
gboolean had_focus = HAS_FOCUS (toplevel);
toplevel->has_pointer = TRUE;
toplevel->has_pointer_focus = TRUE;
if (HAS_FOCUS (toplevel) != had_focus)
generate_focus_event (window, TRUE);
if (xevent->xcrossing.focus && !toplevel->has_focus_window)
{
gboolean had_focus = HAS_FOCUS (toplevel);
toplevel->has_pointer_focus = TRUE;
if (HAS_FOCUS (toplevel) != had_focus)
generate_focus_event (window, TRUE);
}
}
/* Tell XInput stuff about it if appropriate */
@ -1312,16 +1315,19 @@ gdk_event_translate (GdkDisplay *display,
}
/* Handle focusing (in the case where no window manager is running */
if (toplevel &&
xevent->xcrossing.detail != NotifyInferior &&
xevent->xcrossing.focus && !toplevel->has_focus_window)
if (toplevel && xevent->xcrossing.detail != NotifyInferior)
{
gboolean had_focus = HAS_FOCUS (toplevel);
toplevel->has_pointer_focus = FALSE;
if (HAS_FOCUS (toplevel) != had_focus)
generate_focus_event (window, FALSE);
toplevel->has_pointer = FALSE;
if (xevent->xcrossing.focus && !toplevel->has_focus_window)
{
gboolean had_focus = HAS_FOCUS (toplevel);
toplevel->has_pointer_focus = FALSE;
if (HAS_FOCUS (toplevel) != had_focus)
generate_focus_event (window, FALSE);
}
}
event->crossing.type = GDK_LEAVE_NOTIFY;
@ -1404,10 +1410,25 @@ gdk_event_translate (GdkDisplay *display,
switch (xevent->xfocus.detail)
{
case NotifyAncestor:
case NotifyNonlinear:
case NotifyVirtual:
/* When the focus moves from an ancestor of the window to
* the window or a descendent of the window, *and* the
* pointer is inside the window, then we were previously
* receiving keystroke events in the has_pointer_focus
* case and are now receiving them in the
* has_focus_window case.
*/
if (toplevel->has_pointer &&
xevent->xfocus.mode != NotifyGrab &&
xevent->xfocus.mode != NotifyUngrab)
toplevel->has_pointer_focus = FALSE;
/* fall through */
case NotifyNonlinear:
case NotifyNonlinearVirtual:
toplevel->has_focus_window = TRUE;
if (xevent->xfocus.mode != NotifyGrab &&
xevent->xfocus.mode != NotifyUngrab)
toplevel->has_focus_window = TRUE;
/* We pretend that the focus moves to the grab
* window, so we pay attention to NotifyGrab
* NotifyUngrab, and ignore NotifyWhileGrabbed
@ -1420,7 +1441,8 @@ gdk_event_translate (GdkDisplay *display,
* but the pointer focus is ignored while a
* grab is in effect
*/
if (xevent->xfocus.mode != NotifyGrab)
if (xevent->xfocus.mode != NotifyGrab &&
xevent->xfocus.mode != NotifyUngrab)
toplevel->has_pointer_focus = TRUE;
break;
case NotifyInferior:
@ -1447,15 +1469,31 @@ gdk_event_translate (GdkDisplay *display,
switch (xevent->xfocus.detail)
{
case NotifyAncestor:
case NotifyNonlinear:
case NotifyVirtual:
/* When the focus moves from the window or a descendent
* of the window to an ancestor of the window, *and* the
* pointer is inside the window, then we were previously
* receiving keystroke events in the has_focus_window
* case and are now receiving them in the
* has_pointer_focus case.
*/
if (toplevel->has_pointer &&
xevent->xfocus.mode != NotifyGrab &&
xevent->xfocus.mode != NotifyUngrab)
toplevel->has_pointer_focus = TRUE;
/* fall through */
case NotifyNonlinear:
case NotifyNonlinearVirtual:
toplevel->has_focus_window = FALSE;
if (xevent->xfocus.mode != NotifyGrab &&
xevent->xfocus.mode != NotifyUngrab)
toplevel->has_focus_window = FALSE;
if (xevent->xfocus.mode != NotifyWhileGrabbed)
toplevel->has_focus = FALSE;
break;
case NotifyPointer:
if (xevent->xfocus.mode != NotifyUngrab)
if (xevent->xfocus.mode != NotifyGrab &&
xevent->xfocus.mode != NotifyUngrab)
toplevel->has_pointer_focus = FALSE;
break;
case NotifyInferior:

View File

@ -96,9 +96,14 @@ struct _GdkToplevelX11
*/
guint has_focus : 1;
/* Set if !window->has_focus_window, but events are being sent to the
* window because the pointer is in it. (Typically, no window
* manager is running.
/* Set if the pointer is inside this window. (This is needed for
* for focus tracking)
*/
guint has_pointer : 1;
/* Set if the window is a descendent of the focus window and the pointer is
* inside it. (This is the case where the window will receive keystroke
* events even window->has_focus_window is FALSE)
*/
guint has_pointer_focus : 1;