summaryrefslogtreecommitdiff
path: root/doc/how-constraints-works.txt
blob: 327e5fe8e180dc7a0a4b3e905d09e89fc5238c1d (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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
File contents:
  Basic Ideas
  Important points to remember
  Explanation of fields in the ConstraintInfo struct
  Gory details of resize_gravity vs. fixed_directions

IMPORTANT NOTE: There's a big comment at the top of constraints.c
explaining how to add extra constraints or tweak others.  Read it.  I put
that information there because it may be enough information by itself for
people to hack on constraints.c.  I won't duplicate that information in
this file; this file is for deeper details.


---------------------------------------------------------------------------
Basic Ideas
---------------------------------------------------------------------------
There are a couple basic ideas behind how this constraints.c code works and
why it works that way:

  1) Split the low-level error-prone operations into a special file
  2) Add robustness by prioritizing constraints
  3) Make use of a minimal spanning set of rectangles for the
     "onscreen region" (screen minus struts).
  4) Constraints can be user-action vs app-action oriented
  5) Avoid over-complification  ;-)

Some more details explaining these basic ideas:

  1) Split tedious operations out

     boxes.[ch] have been added which contain many common, tedious, and
     error-prone operations.  I find that this separation helps a lot for
     managing the complexity and ensuring that things work correctly.
     Also, note that testboxes.c thoroughly tests all functionality in
     boxes.[ch] and a testboxes program is automatically compiled.

     Note that functions have also been added to this file to handle some
     of the tedium necessary for edge resistance as well.

  2) Prioritize constraints

     In the old code, if each and every constraint could not be
     simultaneously satisfied, then it would result in some
     difficult-to-predict set of constraints being violated.  This was
     because constraints were applied in order, with the possibility for
     each making changes that violated previous constraints, with no
     checking done at the end.

     Now, all constraints have an associated priority, defined in the
     ConstraintPriority enum near the top of constraints.c.  The
     constraints are all applied, and then are all checked; if not all are
     satisfied then the least important constraints are dropped and the
     process is repeated.  This ensures that the most important constraints
     are satisfied.

     A special note to make here is that if any one given constraint is
     impossible to satisfy even individually (e.g. if minimum size hints
     specify a larger window than the screen size, making the
     fully-onscreen constraint impossible to satisfy) then we treat the
     constraint as being satisfied.  This sounds counter-intuitive, but the
     idea is that we want to satisfy as many constraints as possible and if
     we treat it as a violation then all constraints with a lesser priority
     also get dropped along with the impossible to satisfy one.

  3) Using maximal/spanning rectangles

     The constraints rely heavily on something I call spanning rectangles
     (which Soeren referred to as maximal rectangles, a name which I think
     I like better but I don't want to go change all the code now).  These
     spanning rectangles have the property that a window will fit on the
     screen if and only if it fits within at least one of the rectangles.
     Soeren had an alternative way of describing these rectangles, namely
     that they were rectangles with the property that if you made any of
     them larger in any direction, they would overlap with struts or be
     offscreen (with the implicit assumption that there are enough of these
     rectangles that combined they cover all relevant parts of the screen).
     Note that, by necessity, these spanning/maximal rectangles will often
     overlap each other.

     Such a list makes it relatively easy to define operations like
     window-is-onscreen or clamp-window-to-region or
     shove-window-into-region.  Since we have a on-single-xinerama
     constraint in addition to the onscreen constraint(s), we cache
     number_xineramas + 1 of these lists in the workspace.  These lists
     then only need to be updated whenever the workarea is (e.g. when strut
     list change or screen or xinerama size changes).

  4) Constraints can be user-action vs app-action oriented

     Such differentiation requires special care for the constraints to be
     consistent; e.g. if the user does something and one constraint
     applies, then the app does something you have to be careful that the
     constraint on the app action doesn't result in some jarring motion.

     In particular, the constraints currently allow offscreen movement or
     resizing for user actions only.  The way consistency is handled is
     that at the end of the constraints, update_onscreen_requirements()
     checks to see if the window is offscreen or split across xineramas and
     updates window->require_fully_onscreen and
     window->require_on_single_xinerama appropriately.

  5) Avoid over-complification

     The previous code tried to reform the constraints into terms of a
     single variable.  This made the code rather difficult to
     understand. ("This is a rather complicated fix for an obscure bug
     that happened when resizing a window and encountering a constraint
     such as the top edge of the screen.")  It also failed, even on the
     very example for which it used as justification for the complexity
     (bug 312104 -- when keyboard resizing the top of the window,
     Metacity extends the bottom once the titlebar hits the top panel),
     though the reason why it failed is somewhat mysterious as it should
     have worked.  Further, it didn't really reform the constraints in
     terms of a single variable -- there was both an x_move_delta and an
     x_resize_delta, and the existence of both caused bug 109553
     (gravity with simultaneous move and resize doesn't work)


---------------------------------------------------------------------------
Important points to remember
---------------------------------------------------------------------------

  - Inner vs Outer window

    Note that because of how configure requests work and
    meta_window_move_resize_internal() and friends are set up, that the
    rectangles passed to meta_window_constrain() are with respect to inner
    window positions instead of outer window positions (meaning that window
    manager decorations are not included in the position/size).  For the
    constraints that need to be enforced with respect to outer window
    positions, you'll need to make use of the extend_by_frame() and
    unextend_by_frame() functions.

  - meta_window_move_resize_internal() accepts a really hairy set of
    inputs.  See the huge comment at the beginning of that function.
    constraints gets screwed up if that function can't sanitize the input,
    so be very careful about that.  It used to be pretty busted.


---------------------------------------------------------------------------
Explanation of fields in the ConstraintInfo strut
---------------------------------------------------------------------------

As of the time of this writing, ConstraintInfo had the following fields:
  orig
  current
  fgeom
  action_type
  is_user_action
  resize_gravity
  fixed_directions
  work_area_xinerama
  entire_xinerama
  usable_screen_region
  usable_xinerama_region

A brief description of each and/or pointers to more information are found
below:
  orig
    The previous position and size of the window, ignoring any window
    decorations
  current
    The requested position and size of the window, ignoring any window
    decorations.  This rectangle gets modified by the various constraints
    to specify the allowed position closest to the requested position.
  fgeom
    The geometry of the window frame (i.e. "decorations"), if it exists.
    Otherwise, it's a dummy 0-size frame for convenience (i.e. this pointer
    is guaranteed to be non-NULL so you don't have to do the stupid check).
  action_type
    Whether the action being constrained is a move, resize, or a combined
    move and resize.  Some constraints can run faster with this information
    (e.g. constraining size increment hints or min size hints don't need to
    do anything for pure move operations).  This may also be used for
    providing slightly different behavior (e.g. clip-to-region instead of
    shove-into-region for resize vs. moving operations), but doesn't
    currently have a lot of use for this.
  is_user_action
    Used to determine whether the action being constrained is a user
    action.  If so, certain parts of the constraint may be relaxed.  Note
    that this requires care to get right; see item 4 of the basic ideas
    section for more details.
  resize_gravity
    The gravity used in the resize operation, used in order to make sure
    windows are resized correctly if constraints specify that their size
    must be modified.  Explained further in the resize_gravity
    vs. fixed_directions section.
  fixed_directions
    There may be multiple solutions to shoving a window back onscreen.
    Typically, the shortest distance used is the solution picked, but if
    e.g. an application only moved its window in a single direction, it's
    more desirable that the window is shoved back in that direction than in
    a different one.  fixed_directions facilitates that.  Explained further
    in the resize_gravity vs. fixed_directions section.
  work_area_xinerama
    This region is defined in the workspace and just cached here for
    convenience.  It is basically the area obtained by taking the current
    xinerama, treating all partial struts as full struts, and then
    subtracting all struts from the current xinerama region.  Useful
    e.g. for enforcing maximization constraints.
  entire_xinerama
    Just a cache of the rectangle corresponding to the entire current
    xinerama, including struts.  Useful e.g. for enforcing fullscreen
    constraints.
  usable_screen_region
    The set of maximal/spanning rectangles for the entire screen; this
    region doesn't overlap with any struts and helps to enforce
    e.g. onscreen constraints.
  usable_xinerama_region
    The set of maximal/spanning rectangles for the current xinerama; this
    region doesn't overlap with any struts on the xinerama and helps to
    enforce e.g. the on-single-xinerama constraint.


---------------------------------------------------------------------------
Gory details of resize_gravity vs. fixed_directions
---------------------------------------------------------------------------

Note that although resize_gravity and fixed_directions look similar, they
are used for different purposes:

  - resize_gravity is only for resize operations and is used for
    constraints unrelated to keeping a window within a certain region
  - fixed_directions is for both move and resize operations and is
    specifically for keeping a window within a specified region.

Examples of where each are used:

  - If a window is simultaneously moved and resized to the southeast corner
    with SouthEastGravity, but it turns out that the window was sized to
    something smaller than the minimum size hint, then the size_hints
    constraint should resize the window using the resize_gravity to ensure
    that the southeast corner doesn't move.
  - If an application resizes itself so that it grows downward only (which
    I note could be using any of three different gravities, most likely
    NorthWest), and happens to put the southeast part of the window under a
    partial strut, then the window needs to be forced back on screen.
    (Yes, shoved onscreen and not clipped; see bug 136307).  It may be the
    case that moving the window to the left results in less movement of the
    window than moving the window up, which, in the absence of fixed
    directions would cause us to chose moving to the left.  But since the
    user knows that only the height of the window is changing, they would
    find moving to the left weird (especially if this were a dialog that
    had been centered on its parent).  It'd be better to shove the window
    upwards so we make sure to keep the left and right sides fixed in this
    case.  Note that moving the window upwards (or leftwards) is probably
    totally against the gravity in this case; but that's okay because
    gravity typically assumes there's more than enough onscreen space for
    the resize and we only override the gravity when that assumption is
    wrong.

For the paranoid, a fixed directions might give an impossible to fulfill
constraint (I don't think that's true currently in the code, but I haven't
thought it through in a while).  If this ever becomes a problem, it should
be relatively simple to throw out the fixed directions when this happens
and rerun the constraint.  Of course, it might be better to rethink things
to just avoid such a problem.

The nitty gritty of what gets fixed:
  User move:
    in x direction - y direction fixed
    in y direction - x direction fixed
    in both dirs.  - neither direction fixed
  User resize: (note that for clipping, only 1 side ever changed)
    in x direction - y direction fixed (technically opposite x side fixed too)
    in y direction - x direction fixed (technically opposite y side fixed too)
    in both dirs.  - neither direction fixed
  App move:
    in x direction - y direction fixed
    in y direction - x direction fixed
    in both dirs.  - neither direction fixed
  App resize
    in x direction - y direction fixed
    in y direction - x direction fixed
    in 2 parallel directions (center side gravity) - other dir. fixed
    in 2 orthogonal directions (corner gravity) - neither dir. fixed
    in 3 or 4 directions (a center-like gravity) - neither dir. fixed
  Move & resize
    Treat like resize case though this will usually mean all four sides
    change and result in neither direction being fixed
  Note that in all cases, if neither direction moves it is likely do to a
  change in struts and thus neither direction should be fixed despite the
  lack of movement.