summaryrefslogtreecommitdiff
path: root/docs/focus_tracking.txt
blob: f3576c180c44634e1049308686d63c2719c1162c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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 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.