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.