mirror of
https://github.com/brl/mutter.git
synced 2024-11-23 00:20:42 -05:00
2f4a68c8c3
The order and way include macros were structured was chaotic, with no real common thread between files. Try to tidy up the mess with some common scheme, to make things look less messy.
1707 lines
66 KiB
C
1707 lines
66 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
/* Mutter 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "core/constraints.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#include "backends/meta-backend-private.h"
|
|
#include "backends/meta-logical-monitor.h"
|
|
#include "backends/meta-monitor-manager-private.h"
|
|
#include "core/boxes-private.h"
|
|
#include "core/meta-workspace-manager-private.h"
|
|
#include "core/place.h"
|
|
#include "core/workspace-private.h"
|
|
#include "meta/prefs.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_MONITOR = 0,
|
|
PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA = 1,
|
|
PRIORITY_SIZE_HINTS_INCREMENTS = 1,
|
|
PRIORITY_MAXIMIZATION = 2,
|
|
PRIORITY_TILING = 2,
|
|
PRIORITY_FULLSCREEN = 2,
|
|
PRIORITY_SIZE_HINTS_LIMITS = 3,
|
|
PRIORITY_TITLEBAR_VISIBLE = 4,
|
|
PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA = 4,
|
|
PRIORITY_CUSTOM_RULE = 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;
|
|
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_monitor - current monitor region minus struts
|
|
* entire_monitor - current monitor, including strut regions
|
|
*/
|
|
MetaRectangle work_area_monitor;
|
|
MetaRectangle entire_monitor;
|
|
|
|
/* Spanning rectangles for the non-covered (by struts) region of the
|
|
* screen and also for just the current monitor
|
|
*/
|
|
GList *usable_screen_region;
|
|
GList *usable_monitor_region;
|
|
} ConstraintInfo;
|
|
|
|
static gboolean do_screen_and_monitor_relative_constraints (MetaWindow *window,
|
|
GList *region_spanning_rectangles,
|
|
ConstraintInfo *info,
|
|
gboolean check_only);
|
|
static gboolean constrain_custom_rule (MetaWindow *window,
|
|
ConstraintInfo *info,
|
|
ConstraintPriority priority,
|
|
gboolean check_only);
|
|
static gboolean constrain_modal_dialog (MetaWindow *window,
|
|
ConstraintInfo *info,
|
|
ConstraintPriority priority,
|
|
gboolean check_only);
|
|
static gboolean constrain_maximization (MetaWindow *window,
|
|
ConstraintInfo *info,
|
|
ConstraintPriority priority,
|
|
gboolean check_only);
|
|
static gboolean constrain_tiling (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_monitor (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,
|
|
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);
|
|
|
|
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_custom_rule, "constrain_custom_rule"},
|
|
{constrain_modal_dialog, "constrain_modal_dialog"},
|
|
{constrain_maximization, "constrain_maximization"},
|
|
{constrain_tiling, "constrain_tiling"},
|
|
{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_monitor, "constrain_to_single_monitor"},
|
|
{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,
|
|
MetaMoveResizeFlags flags,
|
|
int resize_gravity,
|
|
const MetaRectangle *orig,
|
|
MetaRectangle *new)
|
|
{
|
|
ConstraintInfo info;
|
|
ConstraintPriority priority = PRIORITY_MINIMUM;
|
|
gboolean satisfied = FALSE;
|
|
|
|
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,
|
|
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_monitor, and perhaps other quantities
|
|
* if this was a user move or user move-and-resize operation.
|
|
*/
|
|
update_onscreen_requirements (window, &info);
|
|
}
|
|
|
|
static void
|
|
setup_constraint_info (ConstraintInfo *info,
|
|
MetaWindow *window,
|
|
MetaMoveResizeFlags flags,
|
|
int resize_gravity,
|
|
const MetaRectangle *orig,
|
|
MetaRectangle *new)
|
|
{
|
|
MetaBackend *backend = meta_get_backend ();
|
|
MetaMonitorManager *monitor_manager =
|
|
meta_backend_get_monitor_manager (backend);
|
|
MetaLogicalMonitor *logical_monitor;
|
|
MetaWorkspace *cur_workspace;
|
|
|
|
info->orig = *orig;
|
|
info->current = *new;
|
|
|
|
if (info->current.width < 1)
|
|
info->current.width = 1;
|
|
if (info->current.height < 1)
|
|
info->current.height = 1;
|
|
|
|
if (flags & META_MOVE_RESIZE_MOVE_ACTION && flags & META_MOVE_RESIZE_RESIZE_ACTION)
|
|
info->action_type = ACTION_MOVE_AND_RESIZE;
|
|
else if (flags & META_MOVE_RESIZE_RESIZE_ACTION)
|
|
info->action_type = ACTION_RESIZE;
|
|
else if (flags & META_MOVE_RESIZE_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_MOVE_RESIZE_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;
|
|
|
|
logical_monitor =
|
|
meta_monitor_manager_get_logical_monitor_from_rect (monitor_manager,
|
|
&info->current);
|
|
meta_window_get_work_area_for_logical_monitor (window,
|
|
logical_monitor,
|
|
&info->work_area_monitor);
|
|
|
|
if (!window->fullscreen || !meta_window_has_fullscreen_monitors (window))
|
|
{
|
|
info->entire_monitor = logical_monitor->rect;
|
|
}
|
|
else
|
|
{
|
|
info->entire_monitor = window->fullscreen_monitors.top->rect;
|
|
meta_rectangle_union (&info->entire_monitor,
|
|
&window->fullscreen_monitors.bottom->rect,
|
|
&info->entire_monitor);
|
|
meta_rectangle_union (&info->entire_monitor,
|
|
&window->fullscreen_monitors.left->rect,
|
|
&info->entire_monitor);
|
|
meta_rectangle_union (&info->entire_monitor,
|
|
&window->fullscreen_monitors.right->rect,
|
|
&info->entire_monitor);
|
|
}
|
|
|
|
cur_workspace = window->display->workspace_manager->active_workspace;
|
|
info->usable_screen_region =
|
|
meta_workspace_get_onscreen_region (cur_workspace);
|
|
info->usable_monitor_region =
|
|
meta_workspace_get_onmonitor_region (cur_workspace, logical_monitor);
|
|
|
|
/* 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"
|
|
" action_type : %s\n"
|
|
" is_user_action : %s\n"
|
|
" resize_gravity : %s\n"
|
|
" fixed_directions: %s\n"
|
|
" work_area_monitor: %d,%d +%d,%d\n"
|
|
" entire_monitor : %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->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_monitor.x, info->work_area_monitor.y,
|
|
info->work_area_monitor.width,
|
|
info->work_area_monitor.height,
|
|
info->entire_monitor.x, info->entire_monitor.y,
|
|
info->entire_monitor.width, info->entire_monitor.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)
|
|
{
|
|
MetaBackend *backend = meta_get_backend ();
|
|
MetaMonitorManager *monitor_manager =
|
|
meta_backend_get_monitor_manager (backend);
|
|
MetaRectangle orig_rect;
|
|
MetaRectangle placed_rect;
|
|
MetaWorkspace *cur_workspace;
|
|
MetaLogicalMonitor *logical_monitor;
|
|
|
|
placed_rect = (MetaRectangle) {
|
|
.x = window->rect.x,
|
|
.y = window->rect.y,
|
|
.width = info->current.width,
|
|
.height = info->current.height
|
|
};
|
|
|
|
orig_rect = info->orig;
|
|
|
|
meta_window_place (window, orig_rect.x, orig_rect.y,
|
|
&placed_rect.x, &placed_rect.y);
|
|
did_placement = TRUE;
|
|
|
|
/* placing the window may have changed the monitor. Find the
|
|
* new monitor and update the ConstraintInfo
|
|
*/
|
|
logical_monitor =
|
|
meta_monitor_manager_get_logical_monitor_from_rect (monitor_manager,
|
|
&placed_rect);
|
|
info->entire_monitor = logical_monitor->rect;
|
|
meta_window_get_work_area_for_logical_monitor (window,
|
|
logical_monitor,
|
|
&info->work_area_monitor);
|
|
cur_workspace = window->display->workspace_manager->active_workspace;
|
|
info->usable_monitor_region =
|
|
meta_workspace_get_onmonitor_region (cur_workspace, logical_monitor);
|
|
|
|
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)
|
|
{
|
|
/* define a sane saved_rect so that the user can unmaximize to
|
|
* something reasonable.
|
|
*/
|
|
if (info->current.width >= info->work_area_monitor.width)
|
|
{
|
|
info->current.width = .75 * info->work_area_monitor.width;
|
|
info->current.x = info->work_area_monitor.x +
|
|
.125 * info->work_area_monitor.width;
|
|
}
|
|
if (info->current.height >= info->work_area_monitor.height)
|
|
{
|
|
info->current.height = .75 * info->work_area_monitor.height;
|
|
info->current.y = info->work_area_monitor.y +
|
|
.083 * info->work_area_monitor.height;
|
|
}
|
|
|
|
/* idle_move_resize() uses the unconstrained_rect, so make sure it
|
|
* uses the placed coordinates (bug #556696).
|
|
*/
|
|
window->unconstrained_rect = info->current;
|
|
|
|
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);
|
|
|
|
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_monitor, 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.
|
|
*/
|
|
|
|
/* 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 monitor.
|
|
*/
|
|
old = window->require_on_single_monitor;
|
|
window->require_on_single_monitor =
|
|
meta_rectangle_contained_in_region (info->usable_monitor_region,
|
|
&info->current);
|
|
if (old != window->require_on_single_monitor)
|
|
meta_topic (META_DEBUG_GEOMETRY,
|
|
"require_on_single_monitor for %s toggled to %s\n",
|
|
window->desc,
|
|
window->require_on_single_monitor ? "TRUE" : "FALSE");
|
|
|
|
/* Update whether we want future constraint runs to require the
|
|
* titlebar to be visible.
|
|
*/
|
|
if (window->frame && window->decorated)
|
|
{
|
|
MetaRectangle titlebar_rect, frame_rect;
|
|
|
|
meta_window_get_titlebar_rect (window, &titlebar_rect);
|
|
meta_window_get_frame_rect (window, &frame_rect);
|
|
|
|
/* translate into screen coordinates */
|
|
titlebar_rect.x = frame_rect.x;
|
|
titlebar_rect.y = frame_rect.y;
|
|
|
|
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");
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
get_size_limits (MetaWindow *window,
|
|
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->x = min_size->y = max_size->x = max_size->y = 0;
|
|
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;
|
|
|
|
meta_window_client_rect_to_frame_rect (window, min_size, min_size);
|
|
meta_window_client_rect_to_frame_rect (window, max_size, max_size);
|
|
}
|
|
|
|
static void
|
|
placement_rule_flip_horizontally (MetaPlacementRule *placement_rule)
|
|
{
|
|
if (placement_rule->anchor & META_PLACEMENT_ANCHOR_LEFT)
|
|
{
|
|
placement_rule->anchor &= ~META_PLACEMENT_ANCHOR_LEFT;
|
|
placement_rule->anchor |= META_PLACEMENT_ANCHOR_RIGHT;
|
|
}
|
|
else if (placement_rule->anchor & META_PLACEMENT_ANCHOR_RIGHT)
|
|
{
|
|
placement_rule->anchor &= ~META_PLACEMENT_ANCHOR_RIGHT;
|
|
placement_rule->anchor |= META_PLACEMENT_ANCHOR_LEFT;
|
|
}
|
|
|
|
if (placement_rule->gravity & META_PLACEMENT_GRAVITY_LEFT)
|
|
{
|
|
placement_rule->gravity &= ~META_PLACEMENT_GRAVITY_LEFT;
|
|
placement_rule->gravity |= META_PLACEMENT_GRAVITY_RIGHT;
|
|
}
|
|
else if (placement_rule->gravity & META_PLACEMENT_GRAVITY_RIGHT)
|
|
{
|
|
placement_rule->gravity &= ~META_PLACEMENT_GRAVITY_RIGHT;
|
|
placement_rule->gravity |= META_PLACEMENT_GRAVITY_LEFT;
|
|
}
|
|
}
|
|
|
|
static void
|
|
placement_rule_flip_vertically (MetaPlacementRule *placement_rule)
|
|
{
|
|
if (placement_rule->anchor & META_PLACEMENT_ANCHOR_TOP)
|
|
{
|
|
placement_rule->anchor &= ~META_PLACEMENT_ANCHOR_TOP;
|
|
placement_rule->anchor |= META_PLACEMENT_ANCHOR_BOTTOM;
|
|
}
|
|
else if (placement_rule->anchor & META_PLACEMENT_ANCHOR_BOTTOM)
|
|
{
|
|
placement_rule->anchor &= ~META_PLACEMENT_ANCHOR_BOTTOM;
|
|
placement_rule->anchor |= META_PLACEMENT_ANCHOR_TOP;
|
|
}
|
|
|
|
if (placement_rule->gravity & META_PLACEMENT_GRAVITY_TOP)
|
|
{
|
|
placement_rule->gravity &= ~META_PLACEMENT_GRAVITY_TOP;
|
|
placement_rule->gravity |= META_PLACEMENT_GRAVITY_BOTTOM;
|
|
}
|
|
else if (placement_rule->gravity & META_PLACEMENT_GRAVITY_BOTTOM)
|
|
{
|
|
placement_rule->gravity &= ~META_PLACEMENT_GRAVITY_BOTTOM;
|
|
placement_rule->gravity |= META_PLACEMENT_GRAVITY_TOP;
|
|
}
|
|
}
|
|
|
|
static void
|
|
try_flip_window_position (MetaWindow *window,
|
|
ConstraintInfo *info,
|
|
MetaPlacementRule *placement_rule,
|
|
MetaPlacementConstraintAdjustment constraint_adjustment,
|
|
MetaRectangle *rect,
|
|
MetaRectangle *intersection)
|
|
{
|
|
MetaPlacementRule flipped_rule = *placement_rule;;
|
|
MetaRectangle flipped_rect;
|
|
MetaRectangle flipped_intersection;
|
|
|
|
switch (constraint_adjustment)
|
|
{
|
|
case META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X:
|
|
placement_rule_flip_horizontally (&flipped_rule);
|
|
break;
|
|
case META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y:
|
|
placement_rule_flip_vertically (&flipped_rule);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
flipped_rect = info->current;
|
|
meta_window_process_placement (window, &flipped_rule,
|
|
&flipped_rect.x, &flipped_rect.y);
|
|
meta_rectangle_intersect (&flipped_rect, &info->work_area_monitor,
|
|
&flipped_intersection);
|
|
|
|
if ((constraint_adjustment == META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X &&
|
|
flipped_intersection.width == flipped_rect.width) ||
|
|
(constraint_adjustment == META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y &&
|
|
flipped_intersection.height == flipped_rect.height))
|
|
{
|
|
*placement_rule = flipped_rule;
|
|
*rect = flipped_rect;
|
|
*intersection = flipped_intersection;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
is_custom_rule_satisfied (ConstraintInfo *info,
|
|
MetaPlacementRule *placement_rule,
|
|
MetaRectangle *intersection)
|
|
{
|
|
uint32_t x_constrain_actions, y_constrain_actions;
|
|
|
|
x_constrain_actions = (META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_X |
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X);
|
|
y_constrain_actions = (META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_Y |
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y);
|
|
if ((placement_rule->constraint_adjustment & x_constrain_actions &&
|
|
info->current.width != intersection->width) ||
|
|
(placement_rule->constraint_adjustment & y_constrain_actions &&
|
|
info->current.height != intersection->height))
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
constrain_custom_rule (MetaWindow *window,
|
|
ConstraintInfo *info,
|
|
ConstraintPriority priority,
|
|
gboolean check_only)
|
|
{
|
|
MetaPlacementRule *placement_rule;
|
|
MetaRectangle intersection;
|
|
gboolean constraint_satisfied;
|
|
MetaPlacementRule current_rule;
|
|
MetaWindow *parent;
|
|
MetaRectangle parent_rect;
|
|
|
|
if (priority > PRIORITY_CUSTOM_RULE)
|
|
return TRUE;
|
|
|
|
placement_rule = meta_window_get_placement_rule (window);
|
|
if (!placement_rule)
|
|
return TRUE;
|
|
|
|
if (window->placement_rule_constrained)
|
|
{
|
|
parent = meta_window_get_transient_for (window);
|
|
meta_window_get_frame_rect (parent, &parent_rect);
|
|
info->current.x =
|
|
parent_rect.x + window->constrained_placement_rule_offset_x;
|
|
info->current.y =
|
|
parent_rect.y + window->constrained_placement_rule_offset_y;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
meta_rectangle_intersect (&info->current, &info->work_area_monitor,
|
|
&intersection);
|
|
|
|
constraint_satisfied = is_custom_rule_satisfied (info,
|
|
placement_rule,
|
|
&intersection);
|
|
|
|
if (check_only)
|
|
return constraint_satisfied;
|
|
|
|
current_rule = *placement_rule;
|
|
|
|
if (constraint_satisfied)
|
|
goto done;
|
|
|
|
if (info->current.width != intersection.width &&
|
|
(current_rule.constraint_adjustment &
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X))
|
|
{
|
|
try_flip_window_position (window, info, ¤t_rule,
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X,
|
|
&info->current, &intersection);
|
|
}
|
|
if (info->current.height != intersection.height &&
|
|
(current_rule.constraint_adjustment &
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y))
|
|
{
|
|
try_flip_window_position (window, info, ¤t_rule,
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y,
|
|
&info->current, &intersection);
|
|
}
|
|
|
|
meta_rectangle_intersect (&info->current, &info->work_area_monitor,
|
|
&intersection);
|
|
constraint_satisfied = is_custom_rule_satisfied (info,
|
|
placement_rule,
|
|
&intersection);
|
|
|
|
if (constraint_satisfied)
|
|
goto done;
|
|
|
|
if (current_rule.constraint_adjustment &
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_X)
|
|
{
|
|
if (info->current.x != intersection.x)
|
|
info->current.x = intersection.x;
|
|
else if (info->current.width != intersection.width)
|
|
info->current.x -= info->current.width - intersection.width;
|
|
}
|
|
if (current_rule.constraint_adjustment &
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_Y)
|
|
{
|
|
if (info->current.y != intersection.y)
|
|
info->current.y = intersection.y;
|
|
else if (info->current.height != intersection.height)
|
|
info->current.y -= info->current.height - intersection.height;
|
|
}
|
|
|
|
meta_rectangle_intersect (&info->current, &info->work_area_monitor,
|
|
&intersection);
|
|
constraint_satisfied = is_custom_rule_satisfied (info,
|
|
placement_rule,
|
|
&intersection);
|
|
|
|
if (constraint_satisfied)
|
|
goto done;
|
|
|
|
if (current_rule.constraint_adjustment &
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_RESIZE_X)
|
|
{
|
|
info->current.x = intersection.x;
|
|
info->current.width = intersection.width;
|
|
}
|
|
if (current_rule.constraint_adjustment &
|
|
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_RESIZE_Y)
|
|
{
|
|
info->current.y = intersection.y;
|
|
info->current.height = intersection.height;
|
|
}
|
|
|
|
done:
|
|
window->placement_rule_constrained = TRUE;
|
|
|
|
parent = meta_window_get_transient_for (window);
|
|
meta_window_get_frame_rect (parent, &parent_rect);
|
|
window->constrained_placement_rule_offset_x = info->current.x - parent_rect.x;
|
|
window->constrained_placement_rule_offset_y = info->current.y - parent_rect.y;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
constrain_modal_dialog (MetaWindow *window,
|
|
ConstraintInfo *info,
|
|
ConstraintPriority priority,
|
|
gboolean check_only)
|
|
{
|
|
int x, y;
|
|
MetaWindow *parent = meta_window_get_transient_for (window);
|
|
MetaRectangle child_rect, parent_rect;
|
|
gboolean constraint_already_satisfied;
|
|
|
|
if (!parent ||
|
|
!meta_window_is_attached_dialog (window) ||
|
|
meta_window_get_placement_rule (window))
|
|
return TRUE;
|
|
|
|
/* We want to center the dialog on the parent, including the decorations
|
|
for both of them. info->current is in client X window coordinates, so we need
|
|
to convert them to frame coordinates, apply the centering and then
|
|
convert back to client.
|
|
*/
|
|
|
|
child_rect = info->current;
|
|
|
|
meta_window_get_frame_rect (parent, &parent_rect);
|
|
|
|
child_rect.x = parent_rect.x + (parent_rect.width / 2 - child_rect.width / 2);
|
|
child_rect.y = parent_rect.y + (parent_rect.height / 2 - child_rect.height / 2);
|
|
x = child_rect.x;
|
|
y = child_rect.y;
|
|
|
|
constraint_already_satisfied = (x == info->current.x) && (y == info->current.y);
|
|
|
|
if (check_only || constraint_already_satisfied)
|
|
return constraint_already_satisfied;
|
|
|
|
info->current.y = y;
|
|
info->current.x = x;
|
|
/* The calculated position above may need adjustment to make sure the
|
|
* dialog does not end up partially off-screen */
|
|
return do_screen_and_monitor_relative_constraints (window,
|
|
info->usable_screen_region,
|
|
info,
|
|
check_only);
|
|
}
|
|
|
|
static gboolean
|
|
constrain_maximization (MetaWindow *window,
|
|
ConstraintInfo *info,
|
|
ConstraintPriority priority,
|
|
gboolean check_only)
|
|
{
|
|
MetaWorkspaceManager *workspace_manager = window->display->workspace_manager;
|
|
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) ||
|
|
META_WINDOW_TILED_SIDE_BY_SIDE (window))
|
|
return TRUE;
|
|
|
|
/* Calculate target_size = maximized size of (window + frame) */
|
|
if (META_WINDOW_TILED_MAXIMIZED (window))
|
|
{
|
|
meta_window_get_tile_area (window, window->tile_mode, &target_size);
|
|
}
|
|
else if (META_WINDOW_MAXIMIZED (window))
|
|
{
|
|
target_size = info->work_area_monitor;
|
|
}
|
|
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 = workspace_manager->active_workspace->all_struts;
|
|
|
|
target_size = info->current;
|
|
meta_rectangle_expand_to_avoiding_struts (&target_size,
|
|
&info->entire_monitor,
|
|
direction,
|
|
active_workspace_struts);
|
|
}
|
|
|
|
/* Check min size constraints; max size constraints are ignored for maximized
|
|
* windows, as per bug 327543.
|
|
*/
|
|
get_size_limits (window, &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_tiling (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_TILING)
|
|
return TRUE;
|
|
|
|
/* Determine whether constraint applies; exit if it doesn't */
|
|
if (!META_WINDOW_TILED_SIDE_BY_SIDE (window))
|
|
return TRUE;
|
|
|
|
/* Calculate target_size - as the tile previews need this as well, we
|
|
* use an external function for the actual calculation
|
|
*/
|
|
meta_window_get_tile_area (window, window->tile_mode, &target_size);
|
|
|
|
/* Check min size constraints; max size constraints are ignored as for
|
|
* maximized windows.
|
|
*/
|
|
get_size_limits (window, &min_size, &max_size);
|
|
hminbad = target_size.width < min_size.width;
|
|
vminbad = target_size.height < min_size.height;
|
|
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 && vert_equal;
|
|
if (check_only || constraint_already_satisfied)
|
|
return constraint_already_satisfied;
|
|
|
|
/*** Enforce constraint ***/
|
|
info->current.x = target_size.x;
|
|
info->current.width = target_size.width;
|
|
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, monitor;
|
|
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;
|
|
|
|
monitor = info->entire_monitor;
|
|
|
|
get_size_limits (window, &min_size, &max_size);
|
|
too_big = !meta_rectangle_could_fit_rect (&monitor, &min_size);
|
|
too_small = !meta_rectangle_could_fit_rect (&max_size, &monitor);
|
|
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, &monitor);
|
|
if (check_only || constraint_already_satisfied)
|
|
return constraint_already_satisfied;
|
|
|
|
/*** Enforce constraint ***/
|
|
info->current = monitor;
|
|
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;
|
|
MetaRectangle client_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 ||
|
|
META_WINDOW_TILED_SIDE_BY_SIDE (window) ||
|
|
info->action_type == ACTION_MOVE)
|
|
return TRUE;
|
|
|
|
meta_window_frame_rect_to_client_rect (window, &info->current, &client_rect);
|
|
|
|
/* 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 = (client_rect.height - bh) % hi;
|
|
extra_width = (client_rect.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 = client_rect.width - extra_width;
|
|
new_height = client_rect.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;
|
|
|
|
{
|
|
client_rect.width = new_width;
|
|
client_rect.height = new_height;
|
|
meta_window_client_rect_to_frame_rect (window, &client_rect, &client_rect);
|
|
new_width = client_rect.width;
|
|
new_height = client_rect.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;
|
|
|
|
/* 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, &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;
|
|
MetaRectangle client_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 ||
|
|
META_WINDOW_TILED_SIDE_BY_SIDE (window) ||
|
|
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;
|
|
}
|
|
|
|
meta_window_frame_rect_to_client_rect (window, &info->current, &client_rect);
|
|
|
|
constraint_already_satisfied =
|
|
client_rect.width - (client_rect.height * minr ) > -minr*fudge &&
|
|
client_rect.width - (client_rect.height * maxr ) < maxr*fudge;
|
|
if (check_only || constraint_already_satisfied)
|
|
return constraint_already_satisfied;
|
|
|
|
/*** Enforce constraint ***/
|
|
new_width = client_rect.width;
|
|
new_height = client_rect.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;
|
|
}
|
|
|
|
{
|
|
client_rect.width = new_width;
|
|
client_rect.height = new_height;
|
|
meta_window_client_rect_to_frame_rect (window, &client_rect, &client_rect);
|
|
new_width = client_rect.width;
|
|
new_height = client_rect.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
|
|
do_screen_and_monitor_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/monitor 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, &min_size, &max_size);
|
|
|
|
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)
|
|
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);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
constrain_to_single_monitor (MetaWindow *window,
|
|
ConstraintInfo *info,
|
|
ConstraintPriority priority,
|
|
gboolean check_only)
|
|
{
|
|
MetaBackend *backend = meta_get_backend ();
|
|
MetaMonitorManager *monitor_manager =
|
|
meta_backend_get_monitor_manager (backend);
|
|
|
|
if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_MONITOR)
|
|
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 monitors.
|
|
*/
|
|
if (window->type == META_WINDOW_DESKTOP ||
|
|
window->type == META_WINDOW_DOCK ||
|
|
meta_monitor_manager_get_num_logical_monitors (monitor_manager) == 1 ||
|
|
!window->require_on_single_monitor ||
|
|
!window->frame ||
|
|
info->is_user_action ||
|
|
meta_window_get_placement_rule (window))
|
|
return TRUE;
|
|
|
|
/* Have a helper function handle the constraint for us */
|
|
return do_screen_and_monitor_relative_constraints (window,
|
|
info->usable_monitor_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 ||
|
|
meta_window_get_placement_rule (window))
|
|
return TRUE;
|
|
|
|
/* Have a helper function handle the constraint for us */
|
|
return do_screen_and_monitor_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 ||
|
|
unconstrained_user_action ||
|
|
meta_window_get_placement_rule (window))
|
|
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)
|
|
{
|
|
MetaFrameBorders borders;
|
|
meta_frame_calc_borders (window->frame, &borders);
|
|
|
|
bottom_amount = info->current.height + borders.visible.bottom;
|
|
vert_amount_onscreen = borders.visible.top;
|
|
}
|
|
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_monitor_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 ||
|
|
meta_window_get_placement_rule (window))
|
|
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)
|
|
{
|
|
MetaFrameBorders borders;
|
|
meta_frame_calc_borders (window->frame, &borders);
|
|
|
|
bottom_amount = info->current.height + borders.visible.bottom;
|
|
vert_amount_onscreen = borders.visible.top;
|
|
}
|
|
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_monitor_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;
|
|
}
|