diff --git a/ChangeLog b/ChangeLog index 0f41b156d8..305c35434c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2005-05-04 Owen Taylor + + * 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 * tests/testcairo.c (draw): Replace cairo_show_surface() uses with diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 0f41b156d8..305c35434c 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,18 @@ +2005-05-04 Owen Taylor + + * 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 * tests/testcairo.c (draw): Replace cairo_show_surface() uses with diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 0f41b156d8..305c35434c 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,18 @@ +2005-05-04 Owen Taylor + + * 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 * tests/testcairo.c (draw): Replace cairo_show_surface() uses with diff --git a/docs/focus_tracking.txt b/docs/focus_tracking.txt new file mode 100644 index 0000000000..c0fcf08a4f --- /dev/null +++ b/docs/focus_tracking.txt @@ -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. diff --git a/gdk/x11/gdkevents-x11.c b/gdk/x11/gdkevents-x11.c index f3327fadcb..7cc35187ca 100644 --- a/gdk/x11/gdkevents-x11.c +++ b/gdk/x11/gdkevents-x11.c @@ -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: diff --git a/gdk/x11/gdkwindow-x11.h b/gdk/x11/gdkwindow-x11.h index 608c0172c8..f95505aaff 100644 --- a/gdk/x11/gdkwindow-x11.h +++ b/gdk/x11/gdkwindow-x11.h @@ -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;