diff options
Diffstat (limited to 'src/core/constraints.c')
-rw-r--r-- | src/core/constraints.c | 1382 |
1 files changed, 1382 insertions, 0 deletions
diff --git a/src/core/constraints.c b/src/core/constraints.c new file mode 100644 index 00000000..6abc7d5c --- /dev/null +++ b/src/core/constraints.c @@ -0,0 +1,1382 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco size/position constraints */ + +/* + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003, 2004 Rob Adams + * Copyright (C) 2005, 2006 Elijah Newren + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "constraints.h" +#include "workspace.h" +#include "place.h" +#include "prefs.h" + +#include <stdlib.h> +#include <math.h> + +#if 0 + // This is the short and sweet version of how to hack on this file; see + // doc/how-constraints-works.txt for the gory details. The basics of + // understanding this file can be shown by the steps needed to add a new + // constraint, which are: + // 1) Add a new entry in the ConstraintPriority enum; higher values + // have higher priority + // 2) Write a new function following the format of the example below, + // "constrain_whatever". + // 3) Add your function to the all_constraints and all_constraint_names + // arrays (the latter of which is for debugging purposes) + // + // An example constraint function, constrain_whatever: + // + // /* constrain_whatever does the following: + // * Quits (returning true) if priority is higher than PRIORITY_WHATEVER + // * If check_only is TRUE + // * Returns whether the constraint is satisfied or not + // * otherwise + // * Enforces the constraint + // * Note that the value of PRIORITY_WHATEVER is centralized with the + // * priorities of other constraints in the definition of ConstrainPriority + // * for easier maintenance and shuffling of priorities. + // */ + // static gboolean + // constrain_whatever (MetaWindow *window, + // ConstraintInfo *info, + // ConstraintPriority priority, + // gboolean check_only) + // { + // if (priority > PRIORITY_WHATEVER) + // return TRUE; + // + // /* Determine whether constraint applies; note that if the constraint + // * cannot possibly be satisfied, constraint_applies should be set to + // * false. If we don't do this, all constraints with a lesser priority + // * will be dropped along with this one, and we'd rather apply as many as + // * possible. + // */ + // if (!constraint_applies) + // return TRUE; + // + // /* Determine whether constraint is already satisfied; if we're only + // * checking the status of whether the constraint is satisfied, we end + // * here. + // */ + // if (check_only || constraint_already_satisfied) + // return constraint_already_satisfied; + // + // /* Enforce constraints */ + // return TRUE; /* Note that we exited early if check_only is FALSE; also, + // * we know we can return TRUE here because we exited early + // * if the constraint could not be satisfied; not that the + // * return value is heeded in this case... + // */ + // } +#endif + +typedef enum +{ + PRIORITY_MINIMUM = 0, /* Dummy value used for loop start = min(all priorities) */ + PRIORITY_ASPECT_RATIO = 0, + PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_XINERAMA = 0, + PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA = 1, + PRIORITY_SIZE_HINTS_INCREMENTS = 1, + PRIORITY_MAXIMIZATION = 2, + PRIORITY_FULLSCREEN = 2, + PRIORITY_SIZE_HINTS_LIMITS = 3, + PRIORITY_TITLEBAR_VISIBLE = 4, + PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA = 4, + PRIORITY_MAXIMUM = 4 /* Dummy value used for loop end = max(all priorities) */ +} ConstraintPriority; + +typedef enum +{ + ACTION_MOVE, + ACTION_RESIZE, + ACTION_MOVE_AND_RESIZE +} ActionType; + +typedef struct +{ + MetaRectangle orig; + MetaRectangle current; + MetaFrameGeometry *fgeom; + ActionType action_type; + gboolean is_user_action; + + /* I know that these two things probably look similar at first, but they + * have much different uses. See doc/how-constraints-works.txt for for + * explanation of the differences and similarity between resize_gravity + * and fixed_directions + */ + int resize_gravity; + FixedDirections fixed_directions; + + /* work_area_xinerama - current xinerama region minus struts + * entire_xinerama - current xienrama, including strut regions + */ + MetaRectangle work_area_xinerama; + MetaRectangle entire_xinerama; + + /* Spanning rectangles for the non-covered (by struts) region of the + * screen and also for just the current xinerama + */ + GList *usable_screen_region; + GList *usable_xinerama_region; +} ConstraintInfo; + +static gboolean constrain_maximization (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_fullscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_size_increments (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_size_limits (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_aspect_ratio (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_to_single_xinerama (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_fully_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_titlebar_visible (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_partially_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); + +static void setup_constraint_info (ConstraintInfo *info, + MetaWindow *window, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new); +static void place_window_if_needed (MetaWindow *window, + ConstraintInfo *info); +static void update_onscreen_requirements (MetaWindow *window, + ConstraintInfo *info); +static void extend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom); +static void unextend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom); +static inline void get_size_limits (const MetaWindow *window, + const MetaFrameGeometry *fgeom, + gboolean include_frame, + MetaRectangle *min_size, + MetaRectangle *max_size); + +typedef gboolean (* ConstraintFunc) (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); + +typedef struct { + ConstraintFunc func; + const char* name; +} Constraint; + +static const Constraint all_constraints[] = { + {constrain_maximization, "constrain_maximization"}, + {constrain_fullscreen, "constrain_fullscreen"}, + {constrain_size_increments, "constrain_size_increments"}, + {constrain_size_limits, "constrain_size_limits"}, + {constrain_aspect_ratio, "constrain_aspect_ratio"}, + {constrain_to_single_xinerama, "constrain_to_single_xinerama"}, + {constrain_fully_onscreen, "constrain_fully_onscreen"}, + {constrain_titlebar_visible, "constrain_titlebar_visible"}, + {constrain_partially_onscreen, "constrain_partially_onscreen"}, + {NULL, NULL} +}; + +static gboolean +do_all_constraints (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + const Constraint *constraint; + gboolean satisfied; + + constraint = &all_constraints[0]; + satisfied = TRUE; + while (constraint->func != NULL) + { + satisfied = satisfied && + (*constraint->func) (window, info, priority, check_only); + + if (!check_only) + { + /* Log how the constraint modified the position */ + meta_topic (META_DEBUG_GEOMETRY, + "info->current is %d,%d +%d,%d after %s\n", + info->current.x, info->current.y, + info->current.width, info->current.height, + constraint->name); + } + else if (!satisfied) + { + /* Log which constraint was not satisfied */ + meta_topic (META_DEBUG_GEOMETRY, + "constraint %s not satisfied.\n", + constraint->name); + return FALSE; + } + ++constraint; + } + + return TRUE; +} + +void +meta_window_constrain (MetaWindow *window, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new) +{ + ConstraintInfo info; + ConstraintPriority priority = PRIORITY_MINIMUM; + gboolean satisfied = FALSE; + + /* WARNING: orig and new specify positions and sizes of the inner window, + * not the outer. This is a common gotcha since half the constraints + * deal with inner window position/size and half deal with outer. See + * doc/how-constraints-works.txt for more information. + */ + meta_topic (META_DEBUG_GEOMETRY, + "Constraining %s in move from %d,%d %dx%d to %d,%d %dx%d\n", + window->desc, + orig->x, orig->y, orig->width, orig->height, + new->x, new->y, new->width, new->height); + + setup_constraint_info (&info, + window, + orig_fgeom, + flags, + resize_gravity, + orig, + new); + place_window_if_needed (window, &info); + + while (!satisfied && priority <= PRIORITY_MAXIMUM) { + gboolean check_only = TRUE; + + /* Individually enforce all the high-enough priority constraints */ + do_all_constraints (window, &info, priority, !check_only); + + /* Check if all high-enough priority constraints are simultaneously + * satisfied + */ + satisfied = do_all_constraints (window, &info, priority, check_only); + + /* Drop the least important constraints if we can't satisfy them all */ + priority++; + } + + /* Make sure we use the constrained position */ + *new = info.current; + + /* We may need to update window->require_fully_onscreen, + * window->require_on_single_xinerama, and perhaps other quantities + * if this was a user move or user move-and-resize operation. + */ + update_onscreen_requirements (window, &info); + + /* Ew, what an ugly way to do things. Destructors (in a real OOP language, + * not gobject-style--gobject would be more pain than it's worth) or + * smart pointers would be so much nicer here. *shrug* + */ + if (!orig_fgeom) + g_free (info.fgeom); +} + +static void +setup_constraint_info (ConstraintInfo *info, + MetaWindow *window, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new) +{ + const MetaXineramaScreenInfo *xinerama_info; + MetaWorkspace *cur_workspace; + + info->orig = *orig; + info->current = *new; + + /* Create a fake frame geometry if none really exists */ + if (orig_fgeom && !window->fullscreen) + info->fgeom = orig_fgeom; + else + info->fgeom = g_new0 (MetaFrameGeometry, 1); + + if (flags & META_IS_MOVE_ACTION && flags & META_IS_RESIZE_ACTION) + info->action_type = ACTION_MOVE_AND_RESIZE; + else if (flags & META_IS_RESIZE_ACTION) + info->action_type = ACTION_RESIZE; + else if (flags & META_IS_MOVE_ACTION) + info->action_type = ACTION_MOVE; + else + g_error ("BAD, BAD developer! No treat for you! (Fix your calls to " + "meta_window_move_resize_internal()).\n"); + + info->is_user_action = (flags & META_IS_USER_ACTION); + + info->resize_gravity = resize_gravity; + + /* FIXME: fixed_directions might be more sane if we (a) made it + * depend on the grab_op type instead of current amount of movement + * (thus implying that it only has effect when user_action is true, + * and (b) ignored it for aspect ratio windows -- at least in those + * cases where both directions do actually change size. + */ + info->fixed_directions = FIXED_DIRECTION_NONE; + /* If x directions don't change but either y direction does */ + if ( orig->x == new->x && orig->x + orig->width == new->x + new->width && + (orig->y != new->y || orig->y + orig->height != new->y + new->height)) + { + info->fixed_directions = FIXED_DIRECTION_X; + } + /* If y directions don't change but either x direction does */ + if ( orig->y == new->y && orig->y + orig->height == new->y + new->height && + (orig->x != new->x || orig->x + orig->width != new->x + new->width )) + { + info->fixed_directions = FIXED_DIRECTION_Y; + } + /* The point of fixed directions is just that "move to nearest valid + * position" is sometimes a poorer choice than "move to nearest + * valid position but only change this coordinate" for windows the + * user is explicitly moving. This isn't ever true for things that + * aren't explicit user interaction, though, so just clear it out. + */ + if (!info->is_user_action) + info->fixed_directions = FIXED_DIRECTION_NONE; + + xinerama_info = + meta_screen_get_xinerama_for_rect (window->screen, &info->current); + meta_window_get_work_area_for_xinerama (window, + xinerama_info->number, + &info->work_area_xinerama); + + if (!window->fullscreen || window->fullscreen_monitors[0] == -1) + { + info->entire_xinerama = xinerama_info->rect; + } + else + { + int i = 0; + long monitor; + + monitor = window->fullscreen_monitors[i]; + info->entire_xinerama = + window->screen->xinerama_infos[monitor].rect; + for (i = 1; i <= 3; i++) + { + monitor = window->fullscreen_monitors[i]; + meta_rectangle_union (&info->entire_xinerama, + &window->screen->xinerama_infos[monitor].rect, + &info->entire_xinerama); + } + } + + cur_workspace = window->screen->active_workspace; + info->usable_screen_region = + meta_workspace_get_onscreen_region (cur_workspace); + info->usable_xinerama_region = + meta_workspace_get_onxinerama_region (cur_workspace, + xinerama_info->number); + + /* Workaround braindead legacy apps that don't know how to + * fullscreen themselves properly. + */ + if (meta_prefs_get_force_fullscreen() && + meta_rectangle_equal (new, &xinerama_info->rect) && + window->has_fullscreen_func && + !window->fullscreen) + { + /* + meta_topic (META_DEBUG_GEOMETRY, + */ + meta_warning ( + "Treating resize request of legacy application %s as a " + "fullscreen request\n", + window->desc); + meta_window_make_fullscreen_internal (window); + } + + /* Log all this information for debugging */ + meta_topic (META_DEBUG_GEOMETRY, + "Setting up constraint info:\n" + " orig: %d,%d +%d,%d\n" + " new : %d,%d +%d,%d\n" + " fgeom: %d,%d,%d,%d\n" + " action_type : %s\n" + " is_user_action : %s\n" + " resize_gravity : %s\n" + " fixed_directions: %s\n" + " work_area_xinerama: %d,%d +%d,%d\n" + " entire_xinerama : %d,%d +%d,%d\n", + info->orig.x, info->orig.y, info->orig.width, info->orig.height, + info->current.x, info->current.y, + info->current.width, info->current.height, + info->fgeom->left_width, info->fgeom->right_width, + info->fgeom->top_height, info->fgeom->bottom_height, + (info->action_type == ACTION_MOVE) ? "Move" : + (info->action_type == ACTION_RESIZE) ? "Resize" : + (info->action_type == ACTION_MOVE_AND_RESIZE) ? "Move&Resize" : + "Freakin' Invalid Stupid", + (info->is_user_action) ? "true" : "false", + meta_gravity_to_string (info->resize_gravity), + (info->fixed_directions == FIXED_DIRECTION_NONE) ? "None" : + (info->fixed_directions == FIXED_DIRECTION_X) ? "X fixed" : + (info->fixed_directions == FIXED_DIRECTION_Y) ? "Y fixed" : + "Freakin' Invalid Stupid", + info->work_area_xinerama.x, info->work_area_xinerama.y, + info->work_area_xinerama.width, + info->work_area_xinerama.height, + info->entire_xinerama.x, info->entire_xinerama.y, + info->entire_xinerama.width, info->entire_xinerama.height); +} + +static void +place_window_if_needed(MetaWindow *window, + ConstraintInfo *info) +{ + gboolean did_placement; + + /* Do placement if any, so we go ahead and apply position + * constraints in a move-only context. Don't place + * maximized/minimized/fullscreen windows until they are + * unmaximized, unminimized and unfullscreened. + */ + did_placement = FALSE; + if (!window->placed && + window->calc_placement && + !(window->maximized_horizontally || + window->maximized_vertically) && + !window->minimized && + !window->fullscreen) + { + MetaRectangle placed_rect = info->orig; + MetaWorkspace *cur_workspace; + const MetaXineramaScreenInfo *xinerama_info; + + meta_window_place (window, info->fgeom, info->orig.x, info->orig.y, + &placed_rect.x, &placed_rect.y); + did_placement = TRUE; + + /* placing the window may have changed the xinerama. Find the + * new xinerama and update the ConstraintInfo + */ + xinerama_info = + meta_screen_get_xinerama_for_rect (window->screen, &placed_rect); + info->entire_xinerama = xinerama_info->rect; + meta_window_get_work_area_for_xinerama (window, + xinerama_info->number, + &info->work_area_xinerama); + cur_workspace = window->screen->active_workspace; + info->usable_xinerama_region = + meta_workspace_get_onxinerama_region (cur_workspace, + xinerama_info->number); + + + info->current.x = placed_rect.x; + info->current.y = placed_rect.y; + + /* Since we just barely placed the window, there's no reason to + * consider any of the directions fixed. + */ + info->fixed_directions = FIXED_DIRECTION_NONE; + } + + if (window->placed || did_placement) + { + if (window->maximize_horizontally_after_placement || + window->maximize_vertically_after_placement || + window->fullscreen_after_placement) + { + /* define a sane saved_rect so that the user can unmaximize or + * make unfullscreen to something reasonable. + */ + if (info->current.width >= info->work_area_xinerama.width) + { + info->current.width = .75 * info->work_area_xinerama.width; + info->current.x = info->work_area_xinerama.x + + .125 * info->work_area_xinerama.width; + } + if (info->current.height >= info->work_area_xinerama.height) + { + info->current.height = .75 * info->work_area_xinerama.height; + info->current.y = info->work_area_xinerama.y + + .083 * info->work_area_xinerama.height; + } + + if (window->maximize_horizontally_after_placement || + window->maximize_vertically_after_placement) + meta_window_maximize_internal (window, + (window->maximize_horizontally_after_placement ? + META_MAXIMIZE_HORIZONTAL : 0 ) | + (window->maximize_vertically_after_placement ? + META_MAXIMIZE_VERTICAL : 0), &info->current); + + /* maximization may have changed frame geometry */ + if (window->frame && !window->fullscreen) + meta_frame_calc_geometry (window->frame, info->fgeom); + + if (window->fullscreen_after_placement) + { + window->saved_rect = info->current; + window->fullscreen = TRUE; + window->fullscreen_after_placement = FALSE; + } + + window->maximize_horizontally_after_placement = FALSE; + window->maximize_vertically_after_placement = FALSE; + } + if (window->minimize_after_placement) + { + meta_window_minimize (window); + window->minimize_after_placement = FALSE; + } + } +} + +static void +update_onscreen_requirements (MetaWindow *window, + ConstraintInfo *info) +{ + gboolean old; + + /* We only apply the various onscreen requirements to normal windows */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK) + return; + + /* We don't want to update the requirements for fullscreen windows; + * fullscreen windows are specially handled anyway, and it updating + * the requirements when windows enter fullscreen mode mess up the + * handling of the window when it leaves that mode (especially when + * the application sends a bunch of configurerequest events). See + * #353699. + */ + if (window->fullscreen) + return; + + /* USABILITY NOTE: Naturally, I only want the require_fully_onscreen, + * require_on_single_xinerama, and require_titlebar_visible flags to + * *become false* due to user interactions (which is allowed since + * certain constraints are ignored for user interactions regardless of + * the setting of these flags). However, whether to make these flags + * *become true* due to just an application interaction is a little + * trickier. It's possible that users may find not doing that strange + * since two application interactions that resize in opposite ways don't + * necessarily end up cancelling--but it may also be strange for the user + * to have an application resize the window so that it's onscreen, the + * user forgets about it, and then later the app is able to resize itself + * off the screen. Anyway, for now, I think the latter is the more + * problematic case but this may need to be revisited. + */ + + /* The require onscreen/on-single-xinerama and titlebar_visible + * stuff is relative to the outer window, not the inner + */ + extend_by_frame (&info->current, info->fgeom); + + /* Update whether we want future constraint runs to require the + * window to be on fully onscreen. + */ + old = window->require_fully_onscreen; + window->require_fully_onscreen = + meta_rectangle_contained_in_region (info->usable_screen_region, + &info->current); + if (old ^ window->require_fully_onscreen) + meta_topic (META_DEBUG_GEOMETRY, + "require_fully_onscreen for %s toggled to %s\n", + window->desc, + window->require_fully_onscreen ? "TRUE" : "FALSE"); + + /* Update whether we want future constraint runs to require the + * window to be on a single xinerama. + */ + old = window->require_on_single_xinerama; + window->require_on_single_xinerama = + meta_rectangle_contained_in_region (info->usable_xinerama_region, + &info->current); + if (old ^ window->require_on_single_xinerama) + meta_topic (META_DEBUG_GEOMETRY, + "require_on_single_xinerama for %s toggled to %s\n", + window->desc, + window->require_on_single_xinerama ? "TRUE" : "FALSE"); + + /* Update whether we want future constraint runs to require the + * titlebar to be visible. + */ + if (window->frame && window->decorated) + { + MetaRectangle titlebar_rect; + + titlebar_rect = info->current; + titlebar_rect.height = info->fgeom->top_height; + old = window->require_titlebar_visible; + window->require_titlebar_visible = + meta_rectangle_overlaps_with_region (info->usable_screen_region, + &titlebar_rect); + if (old ^ window->require_titlebar_visible) + meta_topic (META_DEBUG_GEOMETRY, + "require_titlebar_visible for %s toggled to %s\n", + window->desc, + window->require_titlebar_visible ? "TRUE" : "FALSE"); + } + + /* Don't forget to restore the position of the window */ + unextend_by_frame (&info->current, info->fgeom); +} + +static void +extend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom) +{ + rect->x -= fgeom->left_width; + rect->y -= fgeom->top_height; + rect->width += fgeom->left_width + fgeom->right_width; + rect->height += fgeom->top_height + fgeom->bottom_height; +} + +static void +unextend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom) +{ + rect->x += fgeom->left_width; + rect->y += fgeom->top_height; + rect->width -= fgeom->left_width + fgeom->right_width; + rect->height -= fgeom->top_height + fgeom->bottom_height; +} + +static inline void +get_size_limits (const MetaWindow *window, + const MetaFrameGeometry *fgeom, + gboolean include_frame, + MetaRectangle *min_size, + MetaRectangle *max_size) +{ + /* We pack the results into MetaRectangle structs just for convienience; we + * don't actually use the position of those rects. + */ + min_size->width = window->size_hints.min_width; + min_size->height = window->size_hints.min_height; + max_size->width = window->size_hints.max_width; + max_size->height = window->size_hints.max_height; + + if (include_frame) + { + int fw = fgeom->left_width + fgeom->right_width; + int fh = fgeom->top_height + fgeom->bottom_height; + + min_size->width += fw; + min_size->height += fh; + max_size->width += fw; + max_size->height += fh; + } +} + +static gboolean +constrain_maximization (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + MetaRectangle target_size; + MetaRectangle min_size, max_size; + gboolean hminbad, vminbad; + gboolean horiz_equal, vert_equal; + gboolean constraint_already_satisfied; + + if (priority > PRIORITY_MAXIMIZATION) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (!window->maximized_horizontally && !window->maximized_vertically) + return TRUE; + + /* Calculate target_size = maximized size of (window + frame) */ + if (window->maximized_horizontally && window->maximized_vertically) + target_size = info->work_area_xinerama; + else + { + /* Amount of maximization possible in a single direction depends + * on which struts could occlude the window given its current + * position. For example, a vertical partial strut on the right + * is only relevant for a horizontally maximized window when the + * window is at a vertical position where it could be occluded + * by that partial strut. + */ + MetaDirection direction; + GSList *active_workspace_struts; + + if (window->maximized_horizontally) + direction = META_DIRECTION_HORIZONTAL; + else + direction = META_DIRECTION_VERTICAL; + active_workspace_struts = window->screen->active_workspace->all_struts; + + target_size = info->current; + extend_by_frame (&target_size, info->fgeom); + meta_rectangle_expand_to_avoiding_struts (&target_size, + &info->entire_xinerama, + direction, + active_workspace_struts); + } + /* Now make target_size = maximized size of client window */ + unextend_by_frame (&target_size, info->fgeom); + + /* Check min size constraints; max size constraints are ignored for maximized + * windows, as per bug 327543. + */ + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + hminbad = target_size.width < min_size.width && window->maximized_horizontally; + vminbad = target_size.height < min_size.height && window->maximized_vertically; + if (hminbad || vminbad) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + horiz_equal = target_size.x == info->current.x && + target_size.width == info->current.width; + vert_equal = target_size.y == info->current.y && + target_size.height == info->current.height; + constraint_already_satisfied = + (horiz_equal || !window->maximized_horizontally) && + (vert_equal || !window->maximized_vertically); + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + if (window->maximized_horizontally) + { + info->current.x = target_size.x; + info->current.width = target_size.width; + } + if (window->maximized_vertically) + { + info->current.y = target_size.y; + info->current.height = target_size.height; + } + return TRUE; +} + +static gboolean +constrain_fullscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + MetaRectangle min_size, max_size, xinerama; + gboolean too_big, too_small, constraint_already_satisfied; + + if (priority > PRIORITY_FULLSCREEN) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (!window->fullscreen) + return TRUE; + + xinerama = info->entire_xinerama; + + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + too_big = !meta_rectangle_could_fit_rect (&xinerama, &min_size); + too_small = !meta_rectangle_could_fit_rect (&max_size, &xinerama); + if (too_big || too_small) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + constraint_already_satisfied = + meta_rectangle_equal (&info->current, &xinerama); + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + info->current = xinerama; + return TRUE; +} + +static gboolean +constrain_size_increments (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + int bh, hi, bw, wi, extra_height, extra_width; + int new_width, new_height; + gboolean constraint_already_satisfied; + MetaRectangle *start_rect; + + if (priority > PRIORITY_SIZE_HINTS_INCREMENTS) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (META_WINDOW_MAXIMIZED (window) || window->fullscreen || + info->action_type == ACTION_MOVE) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + bh = window->size_hints.base_height; + hi = window->size_hints.height_inc; + bw = window->size_hints.base_width; + wi = window->size_hints.width_inc; + extra_height = (info->current.height - bh) % hi; + extra_width = (info->current.width - bw) % wi; + /* ignore size increments for maximized windows */ + if (window->maximized_horizontally) + extra_width *= 0; + if (window->maximized_vertically) + extra_height *= 0; + /* constraint is satisfied iff there is no extra height or width */ + constraint_already_satisfied = + (extra_height == 0 && extra_width == 0); + + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + new_width = info->current.width - extra_width; + new_height = info->current.height - extra_height; + + /* Adjusting down instead of up (as done in the above two lines) may + * violate minimum size constraints; fix the adjustment if this + * happens. + */ + if (new_width < window->size_hints.min_width) + new_width += ((window->size_hints.min_width - new_width)/wi + 1)*wi; + if (new_height < window->size_hints.min_height) + new_height += ((window->size_hints.min_height - new_height)/hi + 1)*hi; + + /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity + * See bug 448183 + */ + if (info->action_type == ACTION_MOVE_AND_RESIZE) + start_rect = &info->current; + else + start_rect = &info->orig; + + /* Resize to the new size */ + meta_rectangle_resize_with_gravity (start_rect, + &info->current, + info->resize_gravity, + new_width, + new_height); + return TRUE; +} + +static gboolean +constrain_size_limits (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + MetaRectangle min_size, max_size; + gboolean too_big, too_small, constraint_already_satisfied; + int new_width, new_height; + MetaRectangle *start_rect; + + if (priority > PRIORITY_SIZE_HINTS_LIMITS) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't. + * + * Note: The old code didn't apply this constraint for fullscreen or + * maximized windows--but that seems odd to me. *shrug* + */ + if (info->action_type == ACTION_MOVE) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + /* We ignore max-size limits for maximized windows; see #327543 */ + if (window->maximized_horizontally) + max_size.width = MAX (max_size.width, info->current.width); + if (window->maximized_vertically) + max_size.height = MAX (max_size.height, info->current.height); + too_small = !meta_rectangle_could_fit_rect (&info->current, &min_size); + too_big = !meta_rectangle_could_fit_rect (&max_size, &info->current); + constraint_already_satisfied = !too_big && !too_small; + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + new_width = CLAMP (info->current.width, min_size.width, max_size.width); + new_height = CLAMP (info->current.height, min_size.height, max_size.height); + + /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity + * See bug 448183 + */ + if (info->action_type == ACTION_MOVE_AND_RESIZE) + start_rect = &info->current; + else + start_rect = &info->orig; + + meta_rectangle_resize_with_gravity (start_rect, + &info->current, + info->resize_gravity, + new_width, + new_height); + return TRUE; +} + +static gboolean +constrain_aspect_ratio (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + double minr, maxr; + gboolean constraints_are_inconsistent, constraint_already_satisfied; + int fudge, new_width, new_height; + double best_width, best_height; + double alt_width, alt_height; + MetaRectangle *start_rect; + + if (priority > PRIORITY_ASPECT_RATIO) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't. */ + minr = window->size_hints.min_aspect.x / + (double)window->size_hints.min_aspect.y; + maxr = window->size_hints.max_aspect.x / + (double)window->size_hints.max_aspect.y; + constraints_are_inconsistent = minr > maxr; + if (constraints_are_inconsistent || + META_WINDOW_MAXIMIZED (window) || window->fullscreen || + info->action_type == ACTION_MOVE) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is. We + * need the following to hold: + * + * width + * minr <= ------ <= maxr + * height + * + * But we need to allow for some slight fudging since width and height + * are integers instead of floating point numbers (this is particularly + * important when minr == maxr), so we allow width and height to be off + * a little bit from strictly satisfying these equations. For just one + * sided resizing, we have to make the fudge factor a little bigger + * because of how meta_rectangle_resize_with_gravity treats those as + * being a resize increment (FIXME: I should handle real resize + * increments better here...) + */ + switch (info->resize_gravity) + { + case WestGravity: + case NorthGravity: + case SouthGravity: + case EastGravity: + fudge = 2; + break; + + case NorthWestGravity: + case SouthWestGravity: + case CenterGravity: + case NorthEastGravity: + case SouthEastGravity: + case StaticGravity: + default: + fudge = 1; + break; + } + constraint_already_satisfied = + info->current.width - (info->current.height * minr ) > -minr*fudge && + info->current.width - (info->current.height * maxr ) < maxr*fudge; + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + new_width = info->current.width; + new_height = info->current.height; + + switch (info->resize_gravity) + { + case WestGravity: + case EastGravity: + /* Yeah, I suck for doing implicit rounding -- sue me */ + new_height = CLAMP (new_height, new_width / maxr, new_width / minr); + break; + + case NorthGravity: + case SouthGravity: + /* Yeah, I suck for doing implicit rounding -- sue me */ + new_width = CLAMP (new_width, new_height * minr, new_height * maxr); + break; + + case NorthWestGravity: + case SouthWestGravity: + case CenterGravity: + case NorthEastGravity: + case SouthEastGravity: + case StaticGravity: + default: + /* Find what width would correspond to new_height, and what height would + * correspond to new_width */ + alt_width = CLAMP (new_width, new_height * minr, new_height * maxr); + alt_height = CLAMP (new_height, new_width / maxr, new_width / minr); + + /* The line connecting the points (alt_width, new_height) and + * (new_width, alt_height) provide a range of + * valid-for-the-aspect-ratio-constraint sizes. We want the + * size in that range closest to the value requested, i.e. the + * point on the line which is closest to the point (new_width, + * new_height) + */ + meta_rectangle_find_linepoint_closest_to_point (alt_width, new_height, + new_width, alt_height, + new_width, new_height, + &best_width, &best_height); + + /* Yeah, I suck for doing implicit rounding -- sue me */ + new_width = best_width; + new_height = best_height; + + break; + } + + /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity + * See bug 448183 + */ + if (info->action_type == ACTION_MOVE_AND_RESIZE) + start_rect = &info->current; + else + start_rect = &info->orig; + + meta_rectangle_resize_with_gravity (start_rect, + &info->current, + info->resize_gravity, + new_width, + new_height); + + return TRUE; +} + +static gboolean +do_screen_and_xinerama_relative_constraints ( + MetaWindow *window, + GList *region_spanning_rectangles, + ConstraintInfo *info, + gboolean check_only) +{ + gboolean exit_early = FALSE, constraint_satisfied; + MetaRectangle how_far_it_can_be_smushed, min_size, max_size; + +#ifdef WITH_VERBOSE_MODE + if (meta_is_verbose ()) + { + /* First, log some debugging information */ + char spanning_region[1 + 28 * g_list_length (region_spanning_rectangles)]; + + meta_topic (META_DEBUG_GEOMETRY, + "screen/xinerama constraint; region_spanning_rectangles: %s\n", + meta_rectangle_region_to_string (region_spanning_rectangles, ", ", + spanning_region)); + } +#endif + + /* Determine whether constraint applies; exit if it doesn't */ + how_far_it_can_be_smushed = info->current; + get_size_limits (window, info->fgeom, TRUE, &min_size, &max_size); + extend_by_frame (&info->current, info->fgeom); + + if (info->action_type != ACTION_MOVE) + { + if (!(info->fixed_directions & FIXED_DIRECTION_X)) + how_far_it_can_be_smushed.width = min_size.width; + + if (!(info->fixed_directions & FIXED_DIRECTION_Y)) + how_far_it_can_be_smushed.height = min_size.height; + } + if (!meta_rectangle_could_fit_in_region (region_spanning_rectangles, + &how_far_it_can_be_smushed)) + exit_early = TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + constraint_satisfied = + meta_rectangle_contained_in_region (region_spanning_rectangles, + &info->current); + if (exit_early || constraint_satisfied || check_only) + { + unextend_by_frame (&info->current, info->fgeom); + return constraint_satisfied; + } + + /* Enforce constraint */ + + /* Clamp rectangle size for resize or move+resize actions */ + if (info->action_type != ACTION_MOVE) + meta_rectangle_clamp_to_fit_into_region (region_spanning_rectangles, + info->fixed_directions, + &info->current, + &min_size); + + if (info->is_user_action && info->action_type == ACTION_RESIZE) + /* For user resize, clip to the relevant region */ + meta_rectangle_clip_to_region (region_spanning_rectangles, + info->fixed_directions, + &info->current); + else + /* For everything else, shove the rectangle into the relevant region */ + meta_rectangle_shove_into_region (region_spanning_rectangles, + info->fixed_directions, + &info->current); + + unextend_by_frame (&info->current, info->fgeom); + return TRUE; +} + +static gboolean +constrain_to_single_xinerama (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_XINERAMA) + return TRUE; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut) and we can't apply it to frameless windows + * or else users will be unable to move windows such as XMMS across xineramas. + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->screen->n_xinerama_infos == 1 || + !window->require_on_single_xinerama || + !window->frame || + info->is_user_action) + return TRUE; + + /* Have a helper function handle the constraint for us */ + return do_screen_and_xinerama_relative_constraints (window, + info->usable_xinerama_region, + info, + check_only); +} + +static gboolean +constrain_fully_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA) + return TRUE; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut). + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->fullscreen || + !window->require_fully_onscreen || + info->is_user_action) + return TRUE; + + /* Have a helper function handle the constraint for us */ + return do_screen_and_xinerama_relative_constraints (window, + info->usable_screen_region, + info, + check_only); +} + +static gboolean +constrain_titlebar_visible (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + gboolean unconstrained_user_action; + gboolean retval; + int bottom_amount; + int horiz_amount_offscreen, vert_amount_offscreen; + int horiz_amount_onscreen, vert_amount_onscreen; + + if (priority > PRIORITY_TITLEBAR_VISIBLE) + return TRUE; + + /* Allow the titlebar beyond the top of the screen only if the user wasn't + * clicking on the frame to start the move. + */ + unconstrained_user_action = + info->is_user_action && !window->display->grab_frame_action; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut). + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->fullscreen || + !window->require_titlebar_visible || + !window->decorated || + unconstrained_user_action) + return TRUE; + + /* Determine how much offscreen things are allowed. We first need to + * figure out how much must remain on the screen. For that, we use 25% + * window width/height but clamp to the range of (10,75) pixels. This is + * somewhat of a seat of my pants random guess at what might look good. + * Then, the amount that is allowed off is just the window size minus + * this amount (but no less than 0 for tiny windows). + */ + horiz_amount_onscreen = info->current.width / 4; + vert_amount_onscreen = info->current.height / 4; + horiz_amount_onscreen = CLAMP (horiz_amount_onscreen, 10, 75); + vert_amount_onscreen = CLAMP (vert_amount_onscreen, 10, 75); + horiz_amount_offscreen = info->current.width - horiz_amount_onscreen; + vert_amount_offscreen = info->current.height - vert_amount_onscreen; + horiz_amount_offscreen = MAX (horiz_amount_offscreen, 0); + vert_amount_offscreen = MAX (vert_amount_offscreen, 0); + /* Allow the titlebar to touch the bottom panel; If there is no titlebar, + * require vert_amount to remain on the screen. + */ + if (window->frame) + { + bottom_amount = info->current.height + info->fgeom->bottom_height; + vert_amount_onscreen = info->fgeom->top_height; + } + else + bottom_amount = vert_amount_offscreen; + + /* Extend the region, have a helper function handle the constraint, + * then return the region to its original size. + */ + meta_rectangle_expand_region_conditionally (info->usable_screen_region, + horiz_amount_offscreen, + horiz_amount_offscreen, + 0, /* Don't let titlebar off */ + bottom_amount, + horiz_amount_onscreen, + vert_amount_onscreen); + retval = + do_screen_and_xinerama_relative_constraints (window, + info->usable_screen_region, + info, + check_only); + meta_rectangle_expand_region_conditionally (info->usable_screen_region, + -horiz_amount_offscreen, + -horiz_amount_offscreen, + 0, /* Don't let titlebar off */ + -bottom_amount, + horiz_amount_onscreen, + vert_amount_onscreen); + + return retval; +} + +static gboolean +constrain_partially_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + gboolean retval; + int top_amount, bottom_amount; + int horiz_amount_offscreen, vert_amount_offscreen; + int horiz_amount_onscreen, vert_amount_onscreen; + + if (priority > PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA) + return TRUE; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut). + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK) + return TRUE; + + /* Determine how much offscreen things are allowed. We first need to + * figure out how much must remain on the screen. For that, we use 25% + * window width/height but clamp to the range of (10,75) pixels. This is + * somewhat of a seat of my pants random guess at what might look good. + * Then, the amount that is allowed off is just the window size minus + * this amount (but no less than 0 for tiny windows). + */ + horiz_amount_onscreen = info->current.width / 4; + vert_amount_onscreen = info->current.height / 4; + horiz_amount_onscreen = CLAMP (horiz_amount_onscreen, 10, 75); + vert_amount_onscreen = CLAMP (vert_amount_onscreen, 10, 75); + horiz_amount_offscreen = info->current.width - horiz_amount_onscreen; + vert_amount_offscreen = info->current.height - vert_amount_onscreen; + horiz_amount_offscreen = MAX (horiz_amount_offscreen, 0); + vert_amount_offscreen = MAX (vert_amount_offscreen, 0); + top_amount = vert_amount_offscreen; + /* Allow the titlebar to touch the bottom panel; If there is no titlebar, + * require vert_amount to remain on the screen. + */ + if (window->frame) + { + bottom_amount = info->current.height + info->fgeom->bottom_height; + vert_amount_onscreen = info->fgeom->top_height; + } + else + bottom_amount = vert_amount_offscreen; + + /* Extend the region, have a helper function handle the constraint, + * then return the region to its original size. + */ + meta_rectangle_expand_region_conditionally (info->usable_screen_region, + horiz_amount_offscreen, + horiz_amount_offscreen, + top_amount, + bottom_amount, + horiz_amount_onscreen, + vert_amount_onscreen); + retval = + do_screen_and_xinerama_relative_constraints (window, + info->usable_screen_region, + info, + check_only); + meta_rectangle_expand_region_conditionally (info->usable_screen_region, + -horiz_amount_offscreen, + -horiz_amount_offscreen, + -top_amount, + -bottom_amount, + horiz_amount_onscreen, + vert_amount_onscreen); + + return retval; +} |