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 explicitly 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.