mutter/doc/how-constraints-works.txt
Jonas Ådahl 0dac91cffc Add MetaGravity and replace X11 equivalent with it
MetaGravity is an enum, where the values match the X11 macros used for
gravity, with the exception that `ForgetGravity` was renamed
`META_GRAVITY_NONE` to have less of a obscure name.

The motivation for this is to rely less on libX11 data types and macros
in generic code.

https://gitlab.gnome.org/GNOME/mutter/merge_requests/705
2020-02-29 21:01:50 +00:00

284 lines
14 KiB
Plaintext

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 META_GRAVITY_SOUTH_EAST, 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.