/* Metacity size/position constraints */ /* * Copyright (C) 2002, 2003 Red Hat, Inc. * Copyright (C) 2003, 2004 Rob Adams * Copyright (C) 2005 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 #include "constraints.h" #include "workspace.h" #include "place.h" #include #include #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_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_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_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; /* 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); ConstraintPriority priority = PRIORITY_MINIMUM; gboolean satisfied = FALSE; 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) { 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_AND_RESIZE; 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; info->fixed_directions = 0; /* 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; } meta_window_get_work_area_current_xinerama (window, &info->work_area_xinerama); const MetaXineramaScreenInfo *xinerama_info = meta_screen_get_xinerama_for_window (window->screen, window); info->entire_xinerama = xinerama_info->rect; MetaWorkspace *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); /* 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 == 0) ? "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/fullscreen windows until they are unmaximized * and unfullscreened */ did_placement = FALSE; if (!window->placed && window->calc_placement && !META_WINDOW_MAXIMIZED (window) && !window->fullscreen) { MetaRectangle placed_rect = info->orig; 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 */ const MetaXineramaScreenInfo *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); MetaWorkspace *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 = 0; } if ((window->maximize_horizontally_after_placement || window->maximize_vertically_after_placement) && (window->placed || did_placement)) { /* define a sane saved_rect so that the user can unmaximize 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, META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL, &info->current); else if (window->maximize_horizontally_after_placement) { info->current.x = info->work_area_xinerama.x + info->fgeom->left_width; info->current.width = info->work_area_xinerama.width - info->fgeom->left_width - info->fgeom->right_width; } else if (window->maximize_vertically_after_placement); { info->current.y = info->work_area_xinerama.y + info->fgeom->top_height; info->current.height = info->work_area_xinerama.height - info->fgeom->top_height - info->fgeom->bottom_height; } /* maximization may have changed frame geometry */ if (window->frame && !window->fullscreen) meta_frame_calc_geometry (window->frame, info->fgeom); window->maximize_horizontally_after_placement = FALSE; window->maximize_vertically_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; /* USABILITY NOTE: Naturally, I only want the require_fully_onscreen and * require_on_single_xinerama 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'm think the latter is the more problematic * case but this may need to be revisited. */ /* The require onscreen/on-single-xinerama 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"); /* 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) { if (priority > PRIORITY_MAXIMIZATION) return TRUE; /* Determine whether constraint applies; exit if it doesn't */ if (!window->maximized_horizontally && !window->maximized_vertically) return TRUE; MetaRectangle min_size, max_size; MetaRectangle work_area = info->work_area_xinerama; unextend_by_frame (&work_area, info->fgeom); get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); gboolean hminbad, vminbad, hmaxbad, vmaxbad; hminbad = work_area.width < min_size.width && window->maximized_horizontally; vminbad = work_area.height < min_size.height && window->maximized_vertically; hmaxbad = work_area.width > max_size.width && window->maximized_horizontally; vmaxbad = work_area.height > max_size.height && window->maximized_vertically; if (hminbad || vminbad || hmaxbad || vmaxbad) return TRUE; /* Determine whether constraint is already satisfied; exit if it is */ gboolean horiz_equal, vert_equal; horiz_equal = work_area.x == info->current.x && work_area.width == info->current.width; vert_equal = work_area.y == info->current.y && work_area.height == info->current.height; gboolean 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 = work_area.x; info->current.width = work_area.width; } if (window->maximized_vertically) { info->current.y = work_area.y; info->current.height = work_area.height; } return TRUE; } static gboolean constrain_fullscreen (MetaWindow *window, ConstraintInfo *info, ConstraintPriority priority, gboolean check_only) { if (priority > PRIORITY_FULLSCREEN) return TRUE; /* Determine whether constraint applies; exit if it doesn't */ if (!window->fullscreen) return TRUE; MetaRectangle min_size, max_size; MetaRectangle xinerama = info->entire_xinerama; get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); gboolean too_big = !meta_rectangle_could_fit_rect (&xinerama, &min_size); gboolean 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 */ gboolean 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) { 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 */ int bh, hi, bw, wi, extra_height, extra_width; 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; if (window->maximized_horizontally) extra_width *= 0; if (window->maximized_vertically) extra_height *= 0; gboolean constraint_already_satisfied = (extra_height == 0 && extra_width == 0); if (check_only || constraint_already_satisfied) return constraint_already_satisfied; /*** Enforce constraint ***/ /* Shrink to base + N * inc */ meta_rectangle_resize_with_gravity (&info->orig, &info->current, info->resize_gravity, info->current.width - extra_width, info->current.height - extra_height); return TRUE; } static gboolean constrain_size_limits (MetaWindow *window, ConstraintInfo *info, ConstraintPriority priority, gboolean check_only) { 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 */ MetaRectangle min_size, max_size; get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); gboolean too_big = !meta_rectangle_could_fit_rect (&info->current, &min_size); gboolean too_small = !meta_rectangle_could_fit_rect (&max_size, &info->current); gboolean constraint_already_satisfied = !too_big && !too_small; if (check_only || constraint_already_satisfied) return constraint_already_satisfied; /*** Enforce constraint ***/ int new_width, new_height; new_width = CLAMP (info->current.width, min_size.width, max_size.width); new_height = CLAMP (info->current.height, min_size.height, max_size.height); meta_rectangle_resize_with_gravity (&info->orig, &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) { if (priority > PRIORITY_ASPECT_RATIO) return TRUE; /* Determine whether constraint applies; exit if it doesn't. */ double minr, maxr; 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; gboolean 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...) */ int fudge; 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; } gboolean 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 ***/ int new_width, new_height; double best_width, best_height; double alt_width, alt_height; 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; } meta_rectangle_resize_with_gravity (&info->orig, &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; /* First, log some debugging information */ char spanning_region[1 + 28 * g_list_length (region_spanning_rectangles)]; (void) spanning_region; /* Avoid stupid & incorrect compiler warnings... */ meta_topic (META_DEBUG_GEOMETRY, "screen/xinerama constraint; region_spanning_rectangles: %s\n", meta_rectangle_region_to_string (region_spanning_rectangles, ", ", spanning_region)); /* Determine whether constraint applies; exit if it doesn't */ MetaRectangle how_far_it_can_be_smushed, min_size, max_size; 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 */ gboolean 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 */ gboolean retval = do_screen_and_xinerama_relative_constraints (window, info->usable_xinerama_region, info, check_only); /* Free up the data we allocated */ return retval; } 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->require_fully_onscreen || info->is_user_action) return TRUE; /* Have a helper function handle the constraint for us */ gboolean retval = do_screen_and_xinerama_relative_constraints (window, info->usable_screen_region, info, check_only); return retval; } static gboolean constrain_partially_onscreen (MetaWindow *window, ConstraintInfo *info, ConstraintPriority priority, gboolean check_only) { 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. */ int horiz_amount = info->current.width / 4; int vert_amount = info->current.height / 4; horiz_amount = CLAMP (horiz_amount, 10, 75); vert_amount = CLAMP (vert_amount, 10, 75); horiz_amount = info->current.width - horiz_amount; vert_amount = info->current.height - vert_amount; /* Extend the region, have a helper function handle the constraint, * then return the region to its original size. */ meta_rectangle_expand_region (info->usable_screen_region, horiz_amount, horiz_amount, vert_amount, vert_amount); gboolean retval = do_screen_and_xinerama_relative_constraints (window, info->usable_screen_region, info, check_only); meta_rectangle_expand_region (info->usable_screen_region, -horiz_amount, -horiz_amount, -vert_amount, -vert_amount); return retval; }