mutter/doc/how-constraints-works.txt
Elijah Newren a7201d27d1 Merge of all the changes on the constraints_experiments branch. This is
2005-11-18  Elijah Newren  <newren@gmail.com>

	Merge of all the changes on the constraints_experiments branch.
	This is just a summary, to get the full ChangeLog of those
	changes (approx. 2000 lines):
	  cvs -q -z3 update -Pd -r constraints_experiments
	  cvs -q -z3 diff -pu -r CONSTRAINTS_EXPERIMENTS_BRANCHPOINT ChangeLog

	Bugs fixed:
	  unfiled - constraints.c is overly complicated[1]
	  unfiled - constraints.c is not robust when all constraints
	            cannot simultaneously be met (constraints need to be
	            prioritized)
	  unfiled - keep-titlebar-onscreen constraint is decoration
	            unaware (since get_outermost_onscreen_positions()
	            forgets to include decorations)
	  unfiled - keyboard snap-moving and snap-resizing snap to hidden
	            edges
	   109553 - gravity w/ simultaneous move & resize doesn't work
	   113601 - maximize vertical and horizontal should toggle and be
	            constrained
	   122196 - windows show up under vertical panels
	   122670 - jerky/random resizing of window via keyboard[2]
	   124582 - keyboard and mouse snap-resizing and snap-moving
	            erroneously moves the window multidimensionally
	   136307 - don't allow apps to resize themselves off the screen
	            (*cough* filechooser *cough*)
	   142016, 143784 - windows should not span multiple xineramas
	            unless placed there by the user
	   143145 - clamp new windows to screensize and force them
	            onscreen, if they'll fit
	   144126 - Handle pathological strut lists sanely[3]
	   149867 - fixed aspect ratio windows are difficult to resize[4]
	   152898 - make screen edges consistent; allow easy slamming of
	            windows into the left, right, and bottom edges of the
	            screen too.
	   154706 - bouncing weirdness at screen edge with keyboard moving
	            or resizing
	   156699 - avoid struts when placing windows, if possible (nasty
	            a11y blocker)
	   302456 - dragging offscreen too restrictive
	   304857 - wireframe moving off the top of the screen is misleading
	   308521 - make uni-directional resizing easier with
	            alt-middle-drag and prevent the occasional super
	            annoying resize-the-wrong-side(s) behavior
	   312007 - snap-resize moves windows with a minimum size
	            constraint
	   312104 - resizing the top of a window can cause the bottom to
	            grow
	   319351 - don't instantly snap on mouse-move-snapping, remove
	            braindeadedness of having order of releasing shift and
	            releasing button press matter so much

	   [1] fixed in my opinion, anyway.
	   [2] Actually, it's not totally fixed--it's just annoying
	       instead of almost completely unusable.  Matthias had a
	       suggestion that may fix the remainder of the problems (see
	       http://tinyurl.com/bwzuu).
	   [3] This bug was originally about not-quite-so-pathological
	       cases but was left open for the worse cases. The code from
	       the branch handles the remainder of the cases mentioned in
	       this bug.
	   [4] Actually, although it's far better there's still some minor
	       issues left: a slight drift that's only noticeable after
	       lots of resizing, and potential problems with partially
	       onscreen constraints due to not clearing any
	       fixed_directions flags (aspect ratio windows get resized in
	       both directions and thus aren't fixed in one of them)

	New feature:
	    81704 - edge resistance for user move and resize operations;
	            in particular 3 different kinds of resistance are
	            implemented:
             	 Pixel-Distance: window movement is resisted when it
	     	   aligns with an edge unless the movement is greater than
	     	   a threshold number of pixels
             	 Timeout: window movement past an edge is prevented until
	     	   a certain amount of time has elapsed during the
	     	   operation since the first request to move it past that
	     	   edge
             	 Keyboard-Buildup: when moving or resizing with the
	     	   keyboard, once a window is aligned with a certain edge
	     	   it cannot move past until the correct direction has
	     	   been pressed enough times (e.g. 2 or 3 times)

	Major changes:
	  - constraints.c has been rewritten; very few lines of code from
	    the old version remain.  There is a comment near the top of
	    the function explaining the basics of how the new framework
	    works.  A more detailed explanation can be found in
	    doc/how-constraints-works.txt
	  - edge-resistance.[ch] are new files implementing edge-resistance.
	  - boxes.[ch] are new files containing low-level error-prone
	    functions used heavily in constraints.c and edge-resistance.c,
	    among various places throughout the code.  testboxes.c
	    contains a thorough testsuite for the boxes.[ch] functions
	    compiled into a program, testboxes.
	  - meta_window_move_resize_internal() *must* be told the gravity
	    of the associated operation (if it's just a move operation,
	    the gravity will be ignored, but for resize and move+resize
	    the correct value is needed)
	  - the craziness of different values that
	    meta_window_move_resize_internal() accepts has been documented
	    in a large comment at the beginning of the function.  It may
	    be possible to clean this up some, but until then things will
	    remain as they were before--caller beware.
	  - screen and xinerama usable areas (i.e. places not covered by
	    e.g. panels) are cached in the workspace now, as are the
	    screen and xinerama edges.  These get updated with the
	    workarea in src/workspace.c:ensure_work_areas_validated()
2005-11-19 14:58: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 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.