You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1666 lines
67 KiB

  1. /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
  2. /* Muffin size/position constraints */
  3. /*
  4. * Copyright (C) 2002, 2003 Red Hat, Inc.
  5. * Copyright (C) 2003, 2004 Rob Adams
  6. * Copyright (C) 2005, 2006 Elijah Newren
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License as
  10. * published by the Free Software Foundation; either version 2 of the
  11. * License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful, but
  14. * WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA
  21. * 02110-1335, USA.
  22. */
  23. #include <config.h>
  24. #include "boxes-private.h"
  25. #include "constraints.h"
  26. #include "workspace-private.h"
  27. #include "place.h"
  28. #include <meta/prefs.h>
  29. #include "xprops.h"
  30. #include <stdlib.h>
  31. #include <math.h>
  32. #if 0
  33. // This is the short and sweet version of how to hack on this file; see
  34. // doc/how-constraints-works.txt for the gory details. The basics of
  35. // understanding this file can be shown by the steps needed to add a new
  36. // constraint, which are:
  37. // 1) Add a new entry in the ConstraintPriority enum; higher values
  38. // have higher priority
  39. // 2) Write a new function following the format of the example below,
  40. // "constrain_whatever".
  41. // 3) Add your function to the all_constraints and all_constraint_names
  42. // arrays (the latter of which is for debugging purposes)
  43. //
  44. // An example constraint function, constrain_whatever:
  45. //
  46. // /* constrain_whatever does the following:
  47. // * Quits (returning true) if priority is higher than PRIORITY_WHATEVER
  48. // * If check_only is TRUE
  49. // * Returns whether the constraint is satisfied or not
  50. // * otherwise
  51. // * Enforces the constraint
  52. // * Note that the value of PRIORITY_WHATEVER is centralized with the
  53. // * priorities of other constraints in the definition of ConstrainPriority
  54. // * for easier maintenance and shuffling of priorities.
  55. // */
  56. // static gboolean
  57. // constrain_whatever (MetaWindow *window,
  58. // ConstraintInfo *info,
  59. // ConstraintPriority priority,
  60. // gboolean check_only)
  61. // {
  62. // if (priority > PRIORITY_WHATEVER)
  63. // return TRUE;
  64. //
  65. // /* Determine whether constraint applies; note that if the constraint
  66. // * cannot possibly be satisfied, constraint_applies should be set to
  67. // * false. If we don't do this, all constraints with a lesser priority
  68. // * will be dropped along with this one, and we'd rather apply as many as
  69. // * possible.
  70. // */
  71. // if (!constraint_applies)
  72. // return TRUE;
  73. //
  74. // /* Determine whether constraint is already satisfied; if we're only
  75. // * checking the status of whether the constraint is satisfied, we end
  76. // * here.
  77. // */
  78. // if (check_only || constraint_already_satisfied)
  79. // return constraint_already_satisfied;
  80. //
  81. // /* Enforce constraints */
  82. // return TRUE; /* Note that we exited early if check_only is FALSE; also,
  83. // * we know we can return TRUE here because we exited early
  84. // * if the constraint could not be satisfied; not that the
  85. // * return value is heeded in this case...
  86. // */
  87. // }
  88. #endif
  89. typedef enum
  90. {
  91. PRIORITY_MINIMUM = 0, /* Dummy value used for loop start = min(all priorities) */
  92. PRIORITY_ASPECT_RATIO = 0,
  93. PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_MONITOR = 0,
  94. PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA = 1,
  95. PRIORITY_SIZE_HINTS_INCREMENTS = 1,
  96. PRIORITY_MAXIMIZATION = 2,
  97. PRIORITY_TILING = 2,
  98. PRIORITY_FULLSCREEN = 2,
  99. PRIORITY_SIZE_HINTS_LIMITS = 3,
  100. PRIORITY_TITLEBAR_VISIBLE = 4,
  101. PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA = 4,
  102. PRIORITY_MAXIMUM = 4 /* Dummy value used for loop end = max(all priorities) */
  103. } ConstraintPriority;
  104. typedef enum
  105. {
  106. ACTION_MOVE,
  107. ACTION_RESIZE,
  108. ACTION_MOVE_AND_RESIZE
  109. } ActionType;
  110. typedef struct
  111. {
  112. MetaRectangle orig;
  113. MetaRectangle current;
  114. MetaFrameBorders *borders;
  115. gboolean must_free_borders;
  116. ActionType action_type;
  117. gboolean is_user_action;
  118. /* I know that these two things probably look similar at first, but they
  119. * have much different uses. See doc/how-constraints-works.txt for for
  120. * explanation of the differences and similarity between resize_gravity
  121. * and fixed_directions
  122. */
  123. int resize_gravity;
  124. FixedDirections fixed_directions;
  125. /* work_area_monitor - current monitor region minus struts
  126. * entire_monitor - current monitor, including strut regions
  127. */
  128. MetaRectangle work_area_monitor;
  129. MetaRectangle entire_monitor;
  130. /* Spanning rectangles for the non-covered (by struts) region of the
  131. * screen and also for just the current monitor
  132. */
  133. GList *usable_screen_region;
  134. GList *usable_monitor_region;
  135. } ConstraintInfo;
  136. static gboolean do_screen_and_monitor_relative_constraints (MetaWindow *window,
  137. GList *region_spanning_rectangles,
  138. ConstraintInfo *info,
  139. gboolean check_only);
  140. static gboolean constrain_modal_dialog (MetaWindow *window,
  141. ConstraintInfo *info,
  142. ConstraintPriority priority,
  143. gboolean check_only);
  144. static gboolean constrain_maximization (MetaWindow *window,
  145. ConstraintInfo *info,
  146. ConstraintPriority priority,
  147. gboolean check_only);
  148. static gboolean constrain_tiling (MetaWindow *window,
  149. ConstraintInfo *info,
  150. ConstraintPriority priority,
  151. gboolean check_only);
  152. static gboolean constrain_fullscreen (MetaWindow *window,
  153. ConstraintInfo *info,
  154. ConstraintPriority priority,
  155. gboolean check_only);
  156. static gboolean constrain_size_increments (MetaWindow *window,
  157. ConstraintInfo *info,
  158. ConstraintPriority priority,
  159. gboolean check_only);
  160. static gboolean constrain_size_limits (MetaWindow *window,
  161. ConstraintInfo *info,
  162. ConstraintPriority priority,
  163. gboolean check_only);
  164. static gboolean constrain_aspect_ratio (MetaWindow *window,
  165. ConstraintInfo *info,
  166. ConstraintPriority priority,
  167. gboolean check_only);
  168. static gboolean constrain_to_single_monitor (MetaWindow *window,
  169. ConstraintInfo *info,
  170. ConstraintPriority priority,
  171. gboolean check_only);
  172. static gboolean constrain_fully_onscreen (MetaWindow *window,
  173. ConstraintInfo *info,
  174. ConstraintPriority priority,
  175. gboolean check_only);
  176. static gboolean constrain_titlebar_visible (MetaWindow *window,
  177. ConstraintInfo *info,
  178. ConstraintPriority priority,
  179. gboolean check_only);
  180. static gboolean constrain_partially_onscreen (MetaWindow *window,
  181. ConstraintInfo *info,
  182. ConstraintPriority priority,
  183. gboolean check_only);
  184. static void setup_constraint_info (ConstraintInfo *info,
  185. MetaWindow *window,
  186. MetaFrameBorders *orig_borders,
  187. MetaMoveResizeFlags flags,
  188. int resize_gravity,
  189. const MetaRectangle *orig,
  190. MetaRectangle *new);
  191. static void place_window_if_needed (MetaWindow *window,
  192. ConstraintInfo *info);
  193. static void update_onscreen_requirements (MetaWindow *window,
  194. ConstraintInfo *info);
  195. static inline void get_size_limits (const MetaWindow *window,
  196. const MetaFrameBorders *borders,
  197. gboolean include_frame,
  198. MetaRectangle *min_size,
  199. MetaRectangle *max_size);
  200. typedef gboolean (* ConstraintFunc) (MetaWindow *window,
  201. ConstraintInfo *info,
  202. ConstraintPriority priority,
  203. gboolean check_only);
  204. typedef struct {
  205. ConstraintFunc func;
  206. const char* name;
  207. } Constraint;
  208. static const Constraint all_constraints[] = {
  209. {constrain_modal_dialog, "constrain_modal_dialog"},
  210. {constrain_maximization, "constrain_maximization"},
  211. {constrain_tiling, "constrain_tiling"},
  212. {constrain_fullscreen, "constrain_fullscreen"},
  213. {constrain_size_increments, "constrain_size_increments"},
  214. {constrain_size_limits, "constrain_size_limits"},
  215. {constrain_aspect_ratio, "constrain_aspect_ratio"},
  216. {constrain_to_single_monitor, "constrain_to_single_monitor"},
  217. {constrain_fully_onscreen, "constrain_fully_onscreen"},
  218. {constrain_titlebar_visible, "constrain_titlebar_visible"},
  219. {constrain_partially_onscreen, "constrain_partially_onscreen"},
  220. {NULL, NULL}
  221. };
  222. static gboolean
  223. do_all_constraints (MetaWindow *window,
  224. ConstraintInfo *info,
  225. ConstraintPriority priority,
  226. gboolean check_only)
  227. {
  228. const Constraint *constraint;
  229. gboolean satisfied;
  230. constraint = &all_constraints[0];
  231. satisfied = TRUE;
  232. while (constraint->func != NULL)
  233. {
  234. satisfied = satisfied &&
  235. (*constraint->func) (window, info, priority, check_only);
  236. if (!check_only)
  237. {
  238. /* Log how the constraint modified the position */
  239. meta_topic (META_DEBUG_GEOMETRY,
  240. "info->current is %d,%d +%d,%d after %s\n",
  241. info->current.x, info->current.y,
  242. info->current.width, info->current.height,
  243. constraint->name);
  244. }
  245. else if (!satisfied)
  246. {
  247. /* Log which constraint was not satisfied */
  248. meta_topic (META_DEBUG_GEOMETRY,
  249. "constraint %s not satisfied.\n",
  250. constraint->name);
  251. return FALSE;
  252. }
  253. ++constraint;
  254. }
  255. return TRUE;
  256. }
  257. LOCAL_SYMBOL void
  258. meta_window_constrain (MetaWindow *window,
  259. MetaFrameBorders *orig_borders,
  260. MetaMoveResizeFlags flags,
  261. int resize_gravity,
  262. const MetaRectangle *orig,
  263. MetaRectangle *new)
  264. {
  265. ConstraintInfo info;
  266. ConstraintPriority priority = PRIORITY_MINIMUM;
  267. gboolean satisfied = FALSE;
  268. /* WARNING: orig and new specify positions and sizes of the inner window,
  269. * not the outer. This is a common gotcha since half the constraints
  270. * deal with inner window position/size and half deal with outer. See
  271. * doc/how-constraints-works.txt for more information.
  272. */
  273. meta_topic (META_DEBUG_GEOMETRY,
  274. "Constraining %s in move from %d,%d %dx%d to %d,%d %dx%d\n",
  275. window->desc,
  276. orig->x, orig->y, orig->width, orig->height,
  277. new->x, new->y, new->width, new->height);
  278. setup_constraint_info (&info,
  279. window,
  280. orig_borders,
  281. flags,
  282. resize_gravity,
  283. orig,
  284. new);
  285. place_window_if_needed (window, &info);
  286. while (!satisfied && priority <= PRIORITY_MAXIMUM) {
  287. gboolean check_only = TRUE;
  288. /* Individually enforce all the high-enough priority constraints */
  289. do_all_constraints (window, &info, priority, !check_only);
  290. /* Check if all high-enough priority constraints are simultaneously
  291. * satisfied
  292. */
  293. satisfied = do_all_constraints (window, &info, priority, check_only);
  294. /* Drop the least important constraints if we can't satisfy them all */
  295. priority++;
  296. }
  297. /* Make sure we use the constrained position */
  298. *new = info.current;
  299. /* We may need to update window->require_fully_onscreen,
  300. * window->require_on_single_monitor, and perhaps other quantities
  301. * if this was a user move or user move-and-resize operation.
  302. */
  303. update_onscreen_requirements (window, &info);
  304. /* Ew, what an ugly way to do things. Destructors (in a real OOP language,
  305. * not gobject-style--gobject would be more pain than it's worth) or
  306. * smart pointers would be so much nicer here. *shrug*
  307. */
  308. if (info.must_free_borders)
  309. g_free (info.borders);
  310. }
  311. static void
  312. setup_constraint_info (ConstraintInfo *info,
  313. MetaWindow *window,
  314. MetaFrameBorders *orig_borders,
  315. MetaMoveResizeFlags flags,
  316. int resize_gravity,
  317. const MetaRectangle *orig,
  318. MetaRectangle *new)
  319. {
  320. const MetaMonitorInfo *monitor_info;
  321. MetaWorkspace *cur_workspace;
  322. info->orig = *orig;
  323. info->current = *new;
  324. /* Create a fake frame geometry if none really exists */
  325. if (orig_borders && !window->fullscreen)
  326. {
  327. info->borders = orig_borders;
  328. info->must_free_borders = FALSE;
  329. }
  330. else
  331. {
  332. info->borders = g_new0 (MetaFrameBorders, 1);
  333. info->must_free_borders = TRUE;
  334. }
  335. if (flags & META_IS_MOVE_ACTION && flags & META_IS_RESIZE_ACTION)
  336. info->action_type = ACTION_MOVE_AND_RESIZE;
  337. else if (flags & META_IS_RESIZE_ACTION)
  338. info->action_type = ACTION_RESIZE;
  339. else if (flags & META_IS_MOVE_ACTION)
  340. info->action_type = ACTION_MOVE;
  341. else
  342. g_error ("BAD, BAD developer! No treat for you! (Fix your calls to "
  343. "meta_window_move_resize_internal()).\n");
  344. info->is_user_action = (flags & META_IS_USER_ACTION);
  345. info->resize_gravity = resize_gravity;
  346. /* FIXME: fixed_directions might be more sane if we (a) made it
  347. * depend on the grab_op type instead of current amount of movement
  348. * (thus implying that it only has effect when user_action is true,
  349. * and (b) ignored it for aspect ratio windows -- at least in those
  350. * cases where both directions do actually change size.
  351. */
  352. info->fixed_directions = FIXED_DIRECTION_NONE;
  353. /* If x directions don't change but either y direction does */
  354. if ( orig->x == new->x && orig->x + orig->width == new->x + new->width &&
  355. (orig->y != new->y || orig->y + orig->height != new->y + new->height))
  356. {
  357. info->fixed_directions = FIXED_DIRECTION_X;
  358. }
  359. /* If y directions don't change but either x direction does */
  360. if ( orig->y == new->y && orig->y + orig->height == new->y + new->height &&
  361. (orig->x != new->x || orig->x + orig->width != new->x + new->width ))
  362. {
  363. info->fixed_directions = FIXED_DIRECTION_Y;
  364. }
  365. /* The point of fixed directions is just that "move to nearest valid
  366. * position" is sometimes a poorer choice than "move to nearest
  367. * valid position but only change this coordinate" for windows the
  368. * user is explicitly moving. This isn't ever true for things that
  369. * aren't explicit user interaction, though, so just clear it out.
  370. */
  371. if (!info->is_user_action)
  372. info->fixed_directions = FIXED_DIRECTION_NONE;
  373. monitor_info =
  374. meta_screen_get_monitor_for_rect (window->screen, &info->current);
  375. meta_window_get_work_area_for_monitor (window,
  376. monitor_info->number,
  377. &info->work_area_monitor);
  378. if (!window->fullscreen || window->fullscreen_monitors[0] == -1)
  379. {
  380. info->entire_monitor = monitor_info->rect;
  381. }
  382. else
  383. {
  384. int i = 0;
  385. long monitor;
  386. monitor = window->fullscreen_monitors[i];
  387. info->entire_monitor =
  388. window->screen->monitor_infos[monitor].rect;
  389. for (i = 1; i <= 3; i++)
  390. {
  391. monitor = window->fullscreen_monitors[i];
  392. meta_rectangle_union (&info->entire_monitor,
  393. &window->screen->monitor_infos[monitor].rect,
  394. &info->entire_monitor);
  395. }
  396. }
  397. cur_workspace = window->screen->active_workspace;
  398. info->usable_screen_region =
  399. meta_workspace_get_onscreen_region (cur_workspace);
  400. info->usable_monitor_region =
  401. meta_workspace_get_onmonitor_region (cur_workspace,
  402. monitor_info->number);
  403. /* Workaround braindead legacy apps that don't know how to
  404. * fullscreen themselves properly.
  405. */
  406. if (meta_prefs_get_force_fullscreen() &&
  407. (window->decorated && !meta_window_is_client_decorated (window)) &&
  408. meta_rectangle_equal (new, &monitor_info->rect) &&
  409. window->has_fullscreen_func &&
  410. !window->fullscreen)
  411. {
  412. /*
  413. meta_topic (META_DEBUG_GEOMETRY,
  414. */
  415. meta_warning (
  416. "Treating resize request of legacy application %s as a "
  417. "fullscreen request\n",
  418. window->desc);
  419. meta_window_make_fullscreen_internal (window);
  420. }
  421. /* Log all this information for debugging */
  422. meta_topic (META_DEBUG_GEOMETRY,
  423. "Setting up constraint info:\n"
  424. " orig: %d,%d +%d,%d\n"
  425. " new : %d,%d +%d,%d\n"
  426. " action_type : %s\n"
  427. " is_user_action : %s\n"
  428. " resize_gravity : %s\n"
  429. " fixed_directions: %s\n"
  430. " work_area_monitor: %d,%d +%d,%d\n"
  431. " entire_monitor : %d,%d +%d,%d\n",
  432. info->orig.x, info->orig.y, info->orig.width, info->orig.height,
  433. info->current.x, info->current.y,
  434. info->current.width, info->current.height,
  435. (info->action_type == ACTION_MOVE) ? "Move" :
  436. (info->action_type == ACTION_RESIZE) ? "Resize" :
  437. (info->action_type == ACTION_MOVE_AND_RESIZE) ? "Move&Resize" :
  438. "Freakin' Invalid Stupid",
  439. (info->is_user_action) ? "true" : "false",
  440. meta_gravity_to_string (info->resize_gravity),
  441. (info->fixed_directions == FIXED_DIRECTION_NONE) ? "None" :
  442. (info->fixed_directions == FIXED_DIRECTION_X) ? "X fixed" :
  443. (info->fixed_directions == FIXED_DIRECTION_Y) ? "Y fixed" :
  444. "Freakin' Invalid Stupid",
  445. info->work_area_monitor.x, info->work_area_monitor.y,
  446. info->work_area_monitor.width,
  447. info->work_area_monitor.height,
  448. info->entire_monitor.x, info->entire_monitor.y,
  449. info->entire_monitor.width, info->entire_monitor.height);
  450. }
  451. static void
  452. place_window_if_needed(MetaWindow *window,
  453. ConstraintInfo *info)
  454. {
  455. gboolean did_placement;
  456. /* Do placement if any, so we go ahead and apply position
  457. * constraints in a move-only context. Don't place
  458. * maximized/minimized/fullscreen windows until they are
  459. * unmaximized, unminimized and unfullscreened.
  460. */
  461. did_placement = FALSE;
  462. if (!window->placed &&
  463. window->calc_placement &&
  464. !(window->maximized_horizontally ||
  465. window->maximized_vertically) &&
  466. !window->minimized &&
  467. !window->fullscreen)
  468. {
  469. MetaRectangle placed_rect = info->orig;
  470. MetaWorkspace *cur_workspace;
  471. const MetaMonitorInfo *monitor_info;
  472. meta_window_place (window, info->borders, info->orig.x, info->orig.y,
  473. &placed_rect.x, &placed_rect.y);
  474. did_placement = TRUE;
  475. /* placing the window may have changed the monitor. Find the
  476. * new monitor and update the ConstraintInfo
  477. */
  478. monitor_info =
  479. meta_screen_get_monitor_for_rect (window->screen, &placed_rect);
  480. info->entire_monitor = monitor_info->rect;
  481. meta_window_get_work_area_for_monitor (window,
  482. monitor_info->number,
  483. &info->work_area_monitor);
  484. cur_workspace = window->screen->active_workspace;
  485. info->usable_monitor_region =
  486. meta_workspace_get_onmonitor_region (cur_workspace,
  487. monitor_info->number);
  488. info->current.x = placed_rect.x;
  489. info->current.y = placed_rect.y;
  490. /* Since we just barely placed the window, there's no reason to
  491. * consider any of the directions fixed.
  492. */
  493. info->fixed_directions = FIXED_DIRECTION_NONE;
  494. }
  495. if (window->placed || did_placement)
  496. {
  497. if (window->maximize_horizontally_after_placement ||
  498. window->maximize_vertically_after_placement ||
  499. window->fullscreen_after_placement)
  500. {
  501. /* define a sane saved_rect so that the user can unmaximize or
  502. * make unfullscreen to something reasonable.
  503. */
  504. if (info->current.width >= info->work_area_monitor.width)
  505. {
  506. info->current.width = .75 * info->work_area_monitor.width;
  507. info->current.x = info->work_area_monitor.x +
  508. .125 * info->work_area_monitor.width;
  509. }
  510. if (info->current.height >= info->work_area_monitor.height)
  511. {
  512. info->current.height = .75 * info->work_area_monitor.height;
  513. info->current.y = info->work_area_monitor.y +
  514. .083 * info->work_area_monitor.height;
  515. }
  516. /* idle_move_resize() uses the user_rect,
  517. * so make sure it uses the placed coordinates.
  518. */
  519. window->user_rect = info->current;
  520. if (window->maximize_horizontally_after_placement ||
  521. window->maximize_vertically_after_placement)
  522. meta_window_maximize_internal (window,
  523. (window->maximize_horizontally_after_placement ?
  524. META_MAXIMIZE_HORIZONTAL : 0 ) |
  525. (window->maximize_vertically_after_placement ?
  526. META_MAXIMIZE_VERTICAL : 0), &info->current);
  527. /* maximization may have changed frame geometry */
  528. if (!window->fullscreen)
  529. meta_frame_calc_borders (window->frame, info->borders);
  530. if (window->fullscreen_after_placement)
  531. {
  532. window->saved_rect = info->current;
  533. window->fullscreen = TRUE;
  534. window->fullscreen_after_placement = FALSE;
  535. g_object_notify (G_OBJECT (window), "fullscreen");
  536. }
  537. window->maximize_horizontally_after_placement = FALSE;
  538. window->maximize_vertically_after_placement = FALSE;
  539. }
  540. if (window->minimize_after_placement)
  541. {
  542. meta_window_minimize (window);
  543. window->minimize_after_placement = FALSE;
  544. }
  545. if (window->tile_after_placement)
  546. {
  547. window->tile_after_placement = FALSE;
  548. gulong *tile_info = NULL;
  549. int nitems;
  550. if (meta_prop_get_cardinal_list (window->display,
  551. window->xwindow,
  552. window->display->atom__NET_WM_WINDOW_TILE_INFO,
  553. &tile_info, &nitems))
  554. {
  555. if (nitems == 8)
  556. {
  557. window->tile_mode = (MetaTileMode) tile_info[0];
  558. meta_window_move_resize_frame (window,
  559. TRUE,
  560. tile_info[2],
  561. tile_info[3],
  562. tile_info[4],
  563. tile_info[5]);
  564. window->custom_snap_size = tile_info[7] == 1;
  565. window->tile_monitor_number = tile_info[6];
  566. if (tile_info[1] == META_WINDOW_TILE_TYPE_SNAPPED)
  567. window->snap_queued = TRUE;
  568. else
  569. window->snap_queued = FALSE;
  570. meta_window_real_tile (window, TRUE);
  571. meta_XFree (tile_info);
  572. }
  573. }
  574. }
  575. }
  576. }
  577. static void
  578. update_onscreen_requirements (MetaWindow *window,
  579. ConstraintInfo *info)
  580. {
  581. gboolean old;
  582. /* We only apply the various onscreen requirements to normal windows */
  583. if (window->type == META_WINDOW_DESKTOP ||
  584. window->type == META_WINDOW_DOCK)
  585. return;
  586. /* We don't want to update the requirements for fullscreen windows;
  587. * fullscreen windows are specially handled anyway, and it updating
  588. * the requirements when windows enter fullscreen mode mess up the
  589. * handling of the window when it leaves that mode (especially when
  590. * the application sends a bunch of configurerequest events). See
  591. * #353699.
  592. */
  593. if (window->fullscreen)
  594. return;
  595. /* USABILITY NOTE: Naturally, I only want the require_fully_onscreen,
  596. * require_on_single_monitor, and require_titlebar_visible flags to
  597. * *become false* due to user interactions (which is allowed since
  598. * certain constraints are ignored for user interactions regardless of
  599. * the setting of these flags). However, whether to make these flags
  600. * *become true* due to just an application interaction is a little
  601. * trickier. It's possible that users may find not doing that strange
  602. * since two application interactions that resize in opposite ways don't
  603. * necessarily end up cancelling--but it may also be strange for the user
  604. * to have an application resize the window so that it's onscreen, the
  605. * user forgets about it, and then later the app is able to resize itself
  606. * off the screen. Anyway, for now, I think the latter is the more
  607. * problematic case but this may need to be revisited.
  608. */
  609. /* The require onscreen/on-single-monitor and titlebar_visible
  610. * stuff is relative to the outer window, not the inner
  611. */
  612. meta_window_extend_by_frame (window, &info->current, info->borders);
  613. /* Update whether we want future constraint runs to require the
  614. * window to be on fully onscreen.
  615. */
  616. old = window->require_fully_onscreen;
  617. window->require_fully_onscreen =
  618. meta_rectangle_contained_in_region (info->usable_screen_region,
  619. &info->current);
  620. if (old ^ window->require_fully_onscreen)
  621. meta_topic (META_DEBUG_GEOMETRY,
  622. "require_fully_onscreen for %s toggled to %s\n",
  623. window->desc,
  624. window->require_fully_onscreen ? "TRUE" : "FALSE");
  625. /* Update whether we want future constraint runs to require the
  626. * window to be on a single monitor.
  627. */
  628. old = window->require_on_single_monitor;
  629. window->require_on_single_monitor =
  630. meta_rectangle_contained_in_region (info->usable_monitor_region,
  631. &info->current);
  632. if (old ^ window->require_on_single_monitor)
  633. meta_topic (META_DEBUG_GEOMETRY,
  634. "require_on_single_monitor for %s toggled to %s\n",
  635. window->desc,
  636. window->require_on_single_monitor ? "TRUE" : "FALSE");
  637. /* Update whether we want future constraint runs to require the
  638. * titlebar to be visible.
  639. */
  640. MetaRectangle titlebar_rect;
  641. meta_window_get_titlebar_rect (window, &titlebar_rect);
  642. titlebar_rect.x += info->current.x;
  643. titlebar_rect.y += info->current.y;
  644. old = window->require_titlebar_visible;
  645. window->require_titlebar_visible =
  646. meta_rectangle_overlaps_with_region (info->usable_screen_region,
  647. &titlebar_rect);
  648. if (old ^ window->require_titlebar_visible)
  649. meta_topic (META_DEBUG_GEOMETRY,
  650. "require_titlebar_visible for %s toggled to %s\n",
  651. window->desc,
  652. window->require_titlebar_visible ? "TRUE" : "FALSE");
  653. /* Don't forget to restore the position of the window */
  654. meta_window_unextend_by_frame (window, &info->current, info->borders);
  655. }
  656. static inline void
  657. get_size_limits (const MetaWindow *window,
  658. const MetaFrameBorders *borders,
  659. gboolean include_frame,
  660. MetaRectangle *min_size,
  661. MetaRectangle *max_size)
  662. {
  663. /* We pack the results into MetaRectangle structs just for convienience; we
  664. * don't actually use the position of those rects.
  665. */
  666. min_size->width = window->size_hints.min_width;
  667. min_size->height = window->size_hints.min_height;
  668. max_size->width = window->size_hints.max_width;
  669. max_size->height = window->size_hints.max_height;
  670. if (include_frame)
  671. {
  672. int fw = borders->visible.left + borders->visible.right;
  673. int fh = borders->visible.top + borders->visible.bottom;
  674. min_size->width += fw;
  675. min_size->height += fh;
  676. /* Do check to avoid overflow (e.g. max_size->width & max_size->height
  677. * may be set to G_MAXINT by meta_set_normal_hints()).
  678. */
  679. if (max_size->width < (G_MAXINT - fw))
  680. max_size->width += fw;
  681. else
  682. max_size->width = G_MAXINT;
  683. if (max_size->height < (G_MAXINT - fh))
  684. max_size->height += fh;
  685. else
  686. max_size->height = G_MAXINT;
  687. }
  688. }
  689. static gboolean
  690. constrain_modal_dialog (MetaWindow *window,
  691. ConstraintInfo *info,
  692. ConstraintPriority priority,
  693. gboolean check_only)
  694. {
  695. int x, y;
  696. MetaWindow *parent = meta_window_get_transient_for (window);
  697. gboolean constraint_already_satisfied;
  698. if (!meta_window_is_attached_dialog (window))
  699. return TRUE;
  700. x = parent->rect.x + (parent->rect.width / 2 - info->current.width / 2);
  701. y = parent->rect.y + (parent->rect.height / 2 - info->current.height / 2);
  702. if (parent->frame)
  703. {
  704. x += parent->frame->rect.x;
  705. y += parent->frame->rect.y;
  706. }
  707. constraint_already_satisfied = (x == info->current.x) && (y == info->current.y);
  708. if (check_only || constraint_already_satisfied)
  709. return constraint_already_satisfied;
  710. info->current.y = y;
  711. info->current.x = x;
  712. /* The calculated position above may need adjustment to make sure the
  713. * dialog does not end up partially off-screen */
  714. return do_screen_and_monitor_relative_constraints (window,
  715. info->usable_screen_region,
  716. info,
  717. check_only);
  718. }
  719. static gboolean
  720. constrain_maximization (MetaWindow *window,
  721. ConstraintInfo *info,
  722. ConstraintPriority priority,
  723. gboolean check_only)
  724. {
  725. MetaRectangle target_size;
  726. MetaRectangle min_size, max_size;
  727. gboolean hminbad, vminbad;
  728. gboolean horiz_equal, vert_equal;
  729. gboolean constraint_already_satisfied;
  730. if (priority > PRIORITY_MAXIMIZATION)
  731. return TRUE;
  732. /* Determine whether constraint applies; exit if it doesn't */
  733. if ((!window->maximized_horizontally && !window->maximized_vertically) ||
  734. META_WINDOW_TILED_OR_SNAPPED (window))
  735. return TRUE;
  736. /* Calculate target_size = maximized size of (window + frame) */
  737. if (META_WINDOW_MAXIMIZED (window) && g_list_length (window->workspace->snapped_windows) == 0)
  738. {
  739. target_size = info->work_area_monitor;
  740. }
  741. else
  742. {
  743. /* Amount of maximization possible in a single direction depends
  744. * on which struts could occlude the window given its current
  745. * position. For example, a vertical partial strut on the right
  746. * is only relevant for a horizontally maximized window when the
  747. * window is at a vertical position where it could be occluded
  748. * by that partial strut.
  749. */
  750. MetaDirection direction;
  751. GSList *active_workspace_struts;
  752. if (window->maximized_horizontally)
  753. direction = META_DIRECTION_HORIZONTAL;
  754. else
  755. direction = META_DIRECTION_VERTICAL;
  756. active_workspace_struts = window->screen->active_workspace->all_struts;
  757. if (g_list_length (window->screen->active_workspace->snapped_windows) > 0) {
  758. GList *tmp = window->screen->active_workspace->snapped_windows;
  759. GSList *snapped_windows_as_struts = NULL;
  760. while (tmp) {
  761. if (tmp->data == window || META_WINDOW (tmp->data)->minimized ||
  762. meta_window_get_monitor (window) != meta_window_get_monitor (META_WINDOW (tmp->data))) {
  763. tmp = tmp->next;
  764. continue;
  765. }
  766. MetaStrut *strut = g_slice_new0 (MetaStrut);
  767. MetaSide side;
  768. MetaRectangle rect;
  769. meta_window_get_outer_rect (META_WINDOW (tmp->data), &rect);
  770. side = meta_window_get_tile_side (META_WINDOW (tmp->data));
  771. strut->rect = rect;
  772. strut->side = side;
  773. snapped_windows_as_struts = g_slist_prepend (snapped_windows_as_struts, strut);
  774. tmp = tmp->next;
  775. }
  776. target_size = info->current;
  777. meta_window_extend_by_frame (window, &target_size, info->borders);
  778. meta_rectangle_expand_to_snapped_borders (&target_size,
  779. &info->entire_monitor,
  780. active_workspace_struts,
  781. snapped_windows_as_struts,
  782. &window->user_rect);
  783. g_slist_free (snapped_windows_as_struts);
  784. } else {
  785. target_size = info->current;
  786. meta_window_extend_by_frame (window, &target_size, info->borders);
  787. meta_rectangle_expand_to_avoiding_struts (&target_size,
  788. &info->entire_monitor,
  789. direction,
  790. active_workspace_struts);
  791. }
  792. }
  793. /* Now make target_size = maximized size of client window */
  794. meta_window_unextend_by_frame (window, &target_size, info->borders);
  795. /* Check min size constraints; max size constraints are ignored for maximized
  796. * windows, as per bug 327543.
  797. */
  798. get_size_limits (window, info->borders, FALSE, &min_size, &max_size);
  799. hminbad = target_size.width < min_size.width && window->maximized_horizontally;
  800. vminbad = target_size.height < min_size.height && window->maximized_vertically;
  801. if (hminbad || vminbad)
  802. return TRUE;
  803. /* Determine whether constraint is already satisfied; exit if it is */
  804. horiz_equal = target_size.x == info->current.x &&
  805. target_size.width == info->current.width;
  806. vert_equal = target_size.y == info->current.y &&
  807. target_size.height == info->current.height;
  808. constraint_already_satisfied =
  809. (horiz_equal || !window->maximized_horizontally) &&
  810. (vert_equal || !window->maximized_vertically);
  811. if (check_only || constraint_already_satisfied)
  812. return constraint_already_satisfied;
  813. /*** Enforce constraint ***/
  814. if (window->maximized_horizontally)
  815. {
  816. info->current.x = target_size.x;
  817. info->current.width = target_size.width;
  818. }
  819. if (window->maximized_vertically)
  820. {
  821. info->current.y = target_size.y;
  822. info->current.height = target_size.height;
  823. }
  824. return TRUE;
  825. }
  826. static gboolean
  827. constrain_tiling (MetaWindow *window,
  828. ConstraintInfo *info,
  829. ConstraintPriority priority,
  830. gboolean check_only)
  831. {
  832. MetaRectangle target_size;
  833. MetaRectangle min_size, max_size;
  834. MetaRectangle actual_position;
  835. gboolean hminbad, vminbad;
  836. gboolean horiz_equal, vert_equal;
  837. gboolean constraint_already_satisfied;
  838. if (priority > PRIORITY_TILING)
  839. return TRUE;
  840. /* Determine whether constraint applies; exit if it doesn't */
  841. if (!META_WINDOW_TILED_OR_SNAPPED (window) || window->resizing_tile_type != META_WINDOW_TILE_TYPE_NONE)
  842. return TRUE;
  843. /* Calculate target_size - as the tile previews need this as well, we
  844. * use an external function for the actual calculation
  845. */
  846. if (window->tile_mode != META_TILE_NONE)
  847. meta_window_get_current_tile_area (window, &target_size);
  848. else
  849. return TRUE;
  850. meta_window_get_outer_rect (window, &actual_position);
  851. if (window->custom_snap_size) {
  852. switch (window->tile_mode) {
  853. case META_TILE_LEFT:
  854. target_size.width = BOX_RIGHT (actual_position) - target_size.x;
  855. break;
  856. case META_TILE_RIGHT:
  857. target_size.width = BOX_RIGHT (target_size) - BOX_LEFT (actual_position);
  858. target_size.x = BOX_LEFT (actual_position);
  859. break;
  860. case META_TILE_TOP:
  861. target_size.height = BOX_BOTTOM (actual_position) - BOX_TOP (target_size);
  862. break;
  863. case META_TILE_BOTTOM:
  864. target_size.height = BOX_BOTTOM (target_size) - BOX_TOP (actual_position);
  865. target_size.y = BOX_TOP (actual_position);
  866. break;
  867. case META_TILE_ULC:
  868. if (GRAB_OP (window) == META_GRAB_OP_RESIZING_S ||
  869. GRAB_OP (window) == META_GRAB_OP_KEYBOARD_RESIZING_S) {
  870. target_size.width = window->snapped_rect.width;
  871. target_size.height = BOX_BOTTOM (actual_position) - BOX_TOP (target_size);
  872. } else if (GRAB_OP (window) == META_GRAB_OP_RESIZING_E ||
  873. GRAB_OP (window) == META_GRAB_OP_KEYBOARD_RESIZING_E) {
  874. target_size.width = BOX_RIGHT (actual_position) - target_size.x;
  875. target_size.height = window->snapped_rect.height;
  876. } else {
  877. target_size.width = BOX_RIGHT (actual_position) - target_size.x;
  878. target_size.height = BOX_BOTTOM (actual_position) - BOX_TOP (target_size);
  879. }
  880. break;
  881. case META_TILE_LLC:
  882. if (GRAB_OP (window) == META_GRAB_OP_RESIZING_N ||
  883. GRAB_OP (window) == META_GRAB_OP_KEYBOARD_RESIZING_N) {
  884. target_size.width = window->snapped_rect.width;
  885. target_size.height = BOX_BOTTOM (target_size) - BOX_TOP (actual_position);
  886. target_size.y = BOX_TOP (actual_position);
  887. } else if (GRAB_OP (window) == META_GRAB_OP_RESIZING_E ||
  888. GRAB_OP (window) == META_GRAB_OP_KEYBOARD_RESIZING_E) {
  889. target_size.width = BOX_RIGHT (actual_position) - target_size.x;
  890. target_size.height = window->snapped_rect.height;
  891. target_size.y = window->snapped_rect.y;
  892. } else {
  893. target_size.width = BOX_RIGHT (actual_position) - target_size.x;
  894. target_size.height = BOX_BOTTOM (target_size) - BOX_TOP (actual_position);
  895. target_size.y = BOX_TOP (actual_position);
  896. }
  897. break;
  898. case META_TILE_URC:
  899. if (GRAB_OP (window) == META_GRAB_OP_RESIZING_W ||
  900. GRAB_OP (window) == META_GRAB_OP_KEYBOARD_RESIZING_W) {
  901. target_size.width = BOX_RIGHT (target_size) - BOX_LEFT (actual_position);
  902. target_size.x = BOX_LEFT (actual_position);
  903. target_size.height = window->snapped_rect.height;
  904. } else if (GRAB_OP (window) == META_GRAB_OP_RESIZING_S ||
  905. GRAB_OP (window) == META_GRAB_OP_KEYBOARD_RESIZING_S) {
  906. target_size.width = window->snapped_rect.width;
  907. target_size.x = window->snapped_rect.x;
  908. target_size.height = BOX_BOTTOM (actual_position) - BOX_TOP (target_size);
  909. } else {
  910. target_size.width = BOX_RIGHT (target_size) - BOX_LEFT (actual_position);
  911. target_size.x = BOX_LEFT (actual_position);
  912. target_size.height = BOX_BOTTOM (actual_position) - BOX_TOP (target_size);
  913. }
  914. break;
  915. case META_TILE_LRC:
  916. if (GRAB_OP (window) == META_GRAB_OP_RESIZING_W ||
  917. GRAB_OP (window) == META_GRAB_OP_KEYBOARD_RESIZING_W) {
  918. target_size.width = BOX_RIGHT (target_size) - BOX_LEFT (actual_position);
  919. target_size.x = BOX_LEFT (actual_position);
  920. target_size.height = window->snapped_rect.height;
  921. target_size.y = window->snapped_rect.y;
  922. } else if (GRAB_OP (window) == META_GRAB_OP_RESIZING_N ||
  923. GRAB_OP (window) == META_GRAB_OP_KEYBOARD_RESIZING_N) {
  924. target_size.width = window->snapped_rect.width;
  925. target_size.x = window->snapped_rect.x;
  926. target_size.height = BOX_BOTTOM (target_size) - BOX_TOP (actual_position);
  927. target_size.y = BOX_TOP (actual_position);
  928. } else {
  929. target_size.width = BOX_RIGHT (target_size) - BOX_LEFT (actual_position);
  930. target_size.x = BOX_LEFT (actual_position);
  931. target_size.height = BOX_BOTTOM (target_size) - BOX_TOP (actual_position);
  932. target_size.y = BOX_TOP (actual_position);
  933. }
  934. break;
  935. default:
  936. break;
  937. }
  938. }
  939. meta_window_unextend_by_frame (window, &target_size, info->borders);
  940. /* Check min size constraints; max size constraints are ignored as for
  941. * maximized windows.
  942. */
  943. get_size_limits (window, info->borders, FALSE, &min_size, &max_size);
  944. hminbad = target_size.width < min_size.width;
  945. vminbad = target_size.height < min_size.height;
  946. if (hminbad || vminbad)
  947. return TRUE;
  948. /* Determine whether constraint is already satisfied; exit if it is */
  949. horiz_equal = target_size.x == info->current.x &&
  950. target_size.width == info->current.width;
  951. vert_equal = target_size.y == info->current.y &&
  952. target_size.height == info->current.height;
  953. constraint_already_satisfied = horiz_equal && vert_equal;
  954. if (check_only || constraint_already_satisfied)
  955. return constraint_already_satisfied;
  956. /*** Enforce constraint ***/
  957. info->current.x = target_size.x;
  958. info->current.width = target_size.width;
  959. info->current.y = target_size.y;
  960. info->current.height = target_size.height;
  961. return TRUE;
  962. }
  963. static gboolean
  964. constrain_fullscreen (MetaWindow *window,
  965. ConstraintInfo *info,
  966. ConstraintPriority priority,
  967. gboolean check_only)
  968. {
  969. MetaRectangle min_size, max_size, monitor;
  970. gboolean too_big, too_small, constraint_already_satisfied;
  971. if (priority > PRIORITY_FULLSCREEN)
  972. return TRUE;
  973. /* Determine whether constraint applies; exit if it doesn't */
  974. if (!window->fullscreen)
  975. return TRUE;
  976. monitor = info->entire_monitor;
  977. get_size_limits (window, info->borders, FALSE, &min_size, &max_size);
  978. too_big = !meta_rectangle_could_fit_rect (&monitor, &min_size);
  979. too_small = !meta_rectangle_could_fit_rect (&max_size, &monitor);
  980. if (too_big || too_small)
  981. return TRUE;
  982. /* Determine whether constraint is already satisfied; exit if it is */
  983. constraint_already_satisfied =
  984. meta_rectangle_equal (&info->current, &monitor);
  985. if (check_only || constraint_already_satisfied)
  986. return constraint_already_satisfied;
  987. /*** Enforce constraint ***/
  988. info->current = monitor;
  989. return TRUE;
  990. }
  991. static gboolean
  992. constrain_size_increments (MetaWindow *window,
  993. ConstraintInfo *info,
  994. ConstraintPriority priority,
  995. gboolean check_only)
  996. {
  997. int bh, hi, bw, wi, extra_height, extra_width;
  998. int new_width, new_height;
  999. gboolean constraint_already_satisfied;
  1000. MetaRectangle *start_rect;
  1001. if (priority > PRIORITY_SIZE_HINTS_INCREMENTS)
  1002. return TRUE;
  1003. /* Determine whether constraint applies; exit if it doesn't */
  1004. if (META_WINDOW_MAXIMIZED (window) || window->fullscreen ||
  1005. META_WINDOW_TILED_OR_SNAPPED (window) ||
  1006. info->action_type == ACTION_MOVE ||
  1007. window->resizing_tile_type != META_WINDOW_TILE_TYPE_NONE)
  1008. return TRUE;
  1009. /* Determine whether constraint is already satisfied; exit if it is */
  1010. bh = window->size_hints.base_height;
  1011. hi = window->size_hints.height_inc;
  1012. bw = window->size_hints.base_width;
  1013. wi = window->size_hints.width_inc;
  1014. extra_height = (info->current.height - bh) % hi;
  1015. extra_width = (info->current.width - bw) % wi;
  1016. /* ignore size increments for maximized windows */
  1017. if (window->maximized_horizontally)
  1018. extra_width *= 0;
  1019. if (window->maximized_vertically)
  1020. extra_height *= 0;
  1021. /* constraint is satisfied iff there is no extra height or width */
  1022. constraint_already_satisfied =
  1023. (extra_height == 0 && extra_width == 0);
  1024. if (check_only || constraint_already_satisfied)
  1025. return constraint_already_satisfied;
  1026. /*** Enforce constraint ***/
  1027. new_width = info->current.width - extra_width;
  1028. new_height = info->current.height - extra_height;
  1029. /* Adjusting down instead of up (as done in the above two lines) may
  1030. * violate minimum size constraints; fix the adjustment if this
  1031. * happens.
  1032. */
  1033. if (new_width < window->size_hints.min_width)
  1034. new_width += ((window->size_hints.min_width - new_width)/wi + 1)*wi;
  1035. if (new_height < window->size_hints.min_height)
  1036. new_height += ((window->size_hints.min_height - new_height)/hi + 1)*hi;
  1037. /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity
  1038. * See bug 448183
  1039. */
  1040. if (info->action_type == ACTION_MOVE_AND_RESIZE)
  1041. start_rect = &info->current;
  1042. else
  1043. start_rect = &info->orig;
  1044. /* Resize to the new size */
  1045. meta_rectangle_resize_with_gravity (start_rect,
  1046. &info->current,
  1047. info->resize_gravity,
  1048. new_width,
  1049. new_height);
  1050. return TRUE;
  1051. }
  1052. static gboolean
  1053. constrain_size_limits (MetaWindow *window,
  1054. ConstraintInfo *info,
  1055. ConstraintPriority priority,
  1056. gboolean check_only)
  1057. {
  1058. MetaRectangle min_size, max_size;
  1059. gboolean too_big, too_small, constraint_already_satisfied;
  1060. int new_width, new_height;
  1061. MetaRectangle *start_rect;
  1062. if (priority > PRIORITY_SIZE_HINTS_LIMITS)
  1063. return TRUE;
  1064. /* Determine whether constraint applies; exit if it doesn't.
  1065. *
  1066. * Note: The old code didn't apply this constraint for fullscreen or
  1067. * maximized windows--but that seems odd to me. *shrug*
  1068. */
  1069. if (info->action_type == ACTION_MOVE)
  1070. return TRUE;
  1071. /* Determine whether constraint is already satisfied; exit if it is */
  1072. get_size_limits (window, info->borders, FALSE, &min_size, &max_size);
  1073. /* We ignore max-size limits for maximized windows; see #327543 */
  1074. if (window->maximized_horizontally)
  1075. max_size.width = MAX (max_size.width, info->current.width);
  1076. if (window->maximized_vertically)
  1077. max_size.height = MAX (max_size.height, info->current.height);
  1078. too_small = !meta_rectangle_could_fit_rect (&info->current, &min_size);
  1079. too_big = !meta_rectangle_could_fit_rect (&max_size, &info->current);
  1080. constraint_already_satisfied = !too_big && !too_small;
  1081. if (check_only || constraint_already_satisfied)
  1082. return constraint_already_satisfied;
  1083. /*** Enforce constraint ***/
  1084. new_width = CLAMP (info->current.width, min_size.width, max_size.width);
  1085. new_height = CLAMP (info->current.height, min_size.height, max_size.height);
  1086. /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity
  1087. * See bug 448183
  1088. */
  1089. if (info->action_type == ACTION_MOVE_AND_RESIZE)
  1090. start_rect = &info->current;
  1091. else
  1092. start_rect = &info->orig;
  1093. meta_rectangle_resize_with_gravity (start_rect,
  1094. &info->current,
  1095. info->resize_gravity,
  1096. new_width,
  1097. new_height);
  1098. return TRUE;
  1099. }
  1100. static gboolean
  1101. constrain_aspect_ratio (MetaWindow *window,
  1102. ConstraintInfo *info,
  1103. ConstraintPriority priority,
  1104. gboolean check_only)
  1105. {
  1106. double minr, maxr;
  1107. gboolean constraints_are_inconsistent, constraint_already_satisfied;
  1108. int fudge, new_width, new_height;
  1109. double best_width, best_height;
  1110. double alt_width, alt_height;
  1111. MetaRectangle *start_rect;
  1112. if (priority > PRIORITY_ASPECT_RATIO)
  1113. return TRUE;
  1114. /* Determine whether constraint applies; exit if it doesn't. */
  1115. minr = window->size_hints.min_aspect.x /
  1116. (double)window->size_hints.min_aspect.y;
  1117. maxr = window->size_hints.max_aspect.x /
  1118. (double)window->size_hints.max_aspect.y;
  1119. constraints_are_inconsistent = minr > maxr;
  1120. if (constraints_are_inconsistent ||
  1121. META_WINDOW_MAXIMIZED (window) || window->fullscreen ||
  1122. META_WINDOW_TILED_OR_SNAPPED (window) ||
  1123. info->action_type == ACTION_MOVE)
  1124. return TRUE;
  1125. /* Determine whether constraint is already satisfied; exit if it is. We
  1126. * need the following to hold:
  1127. *
  1128. * width
  1129. * minr <= ------ <= maxr
  1130. * height
  1131. *
  1132. * But we need to allow for some slight fudging since width and height
  1133. * are integers instead of floating point numbers (this is particularly
  1134. * important when minr == maxr), so we allow width and height to be off
  1135. * a little bit from strictly satisfying these equations. For just one
  1136. * sided resizing, we have to make the fudge factor a little bigger
  1137. * because of how meta_rectangle_resize_with_gravity treats those as
  1138. * being a resize increment (FIXME: I should handle real resize
  1139. * increments better here...)
  1140. */
  1141. switch (info->resize_gravity)
  1142. {
  1143. case WestGravity:
  1144. case NorthGravity:
  1145. case SouthGravity:
  1146. case EastGravity:
  1147. fudge = 2;
  1148. break;
  1149. case NorthWestGravity:
  1150. case SouthWestGravity:
  1151. case CenterGravity:
  1152. case NorthEastGravity:
  1153. case SouthEastGravity:
  1154. case StaticGravity:
  1155. default:
  1156. fudge = 1;
  1157. break;
  1158. }
  1159. constraint_already_satisfied =
  1160. info->current.width - (info->current.height * minr ) > -minr*fudge &&
  1161. info->current.width - (info->current.height * maxr ) < maxr*fudge;
  1162. if (check_only || constraint_already_satisfied)
  1163. return constraint_already_satisfied;
  1164. /*** Enforce constraint ***/
  1165. new_width = info->current.width;
  1166. new_height = info->current.height;
  1167. switch (info->resize_gravity)
  1168. {
  1169. case WestGravity:
  1170. case EastGravity:
  1171. /* Yeah, I suck for doing implicit rounding -- sue me */
  1172. new_height = CLAMP (new_height, new_width / maxr, new_width / minr);
  1173. break;
  1174. case NorthGravity:
  1175. case SouthGravity:
  1176. /* Yeah, I suck for doing implicit rounding -- sue me */
  1177. new_width = CLAMP (new_width, new_height * minr, new_height * maxr);
  1178. break;
  1179. case NorthWestGravity:
  1180. case SouthWestGravity:
  1181. case CenterGravity:
  1182. case NorthEastGravity:
  1183. case SouthEastGravity:
  1184. case StaticGravity:
  1185. default:
  1186. /* Find what width would correspond to new_height, and what height would
  1187. * correspond to new_width */
  1188. alt_width = CLAMP (new_width, new_height * minr, new_height * maxr);
  1189. alt_height = CLAMP (new_height, new_width / maxr, new_width / minr);
  1190. /* The line connecting the points (alt_width, new_height) and
  1191. * (new_width, alt_height) provide a range of
  1192. * valid-for-the-aspect-ratio-constraint sizes. We want the
  1193. * size in that range closest to the value requested, i.e. the
  1194. * point on the line which is closest to the point (new_width,
  1195. * new_height)
  1196. */
  1197. meta_rectangle_find_linepoint_closest_to_point (alt_width, new_height,
  1198. new_width, alt_height,
  1199. new_width, new_height,
  1200. &best_width, &best_height);
  1201. /* Yeah, I suck for doing implicit rounding -- sue me */
  1202. new_width = best_width;
  1203. new_height = best_height;
  1204. break;
  1205. }
  1206. /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity
  1207. * See bug 448183
  1208. */
  1209. if (info->action_type == ACTION_MOVE_AND_RESIZE)
  1210. start_rect = &info->current;
  1211. else
  1212. start_rect = &info->orig;
  1213. meta_rectangle_resize_with_gravity (start_rect,
  1214. &info->current,
  1215. info->resize_gravity,
  1216. new_width,
  1217. new_height);
  1218. return TRUE;
  1219. }
  1220. static gboolean
  1221. do_screen_and_monitor_relative_constraints (
  1222. MetaWindow *window,
  1223. GList *region_spanning_rectangles,
  1224. ConstraintInfo *info,
  1225. gboolean check_only)
  1226. {
  1227. gboolean exit_early = FALSE, constraint_satisfied;
  1228. MetaRectangle how_far_it_can_be_smushed, min_size, max_size;
  1229. #ifdef WITH_VERBOSE_MODE
  1230. if (meta_is_verbose ())
  1231. {
  1232. /* First, log some debugging information */
  1233. char spanning_region[1 + 28 * g_list_length (region_spanning_rectangles)];
  1234. meta_topic (META_DEBUG_GEOMETRY,
  1235. "screen/monitor constraint; region_spanning_rectangles: %s\n",
  1236. meta_rectangle_region_to_string (region_spanning_rectangles, ", ",
  1237. spanning_region));
  1238. }
  1239. #endif
  1240. /* Determine whether constraint applies; exit if it doesn't */
  1241. how_far_it_can_be_smushed = info->current;
  1242. get_size_limits (window, info->borders, TRUE, &min_size, &max_size);
  1243. meta_window_extend_by_frame (window, &info->current, info->borders);
  1244. if (info->action_type != ACTION_MOVE)
  1245. {
  1246. if (!(info->fixed_directions & FIXED_DIRECTION_X))
  1247. how_far_it_can_be_smushed.width = min_size.width;
  1248. if (!(info->fixed_directions & FIXED_DIRECTION_Y))
  1249. how_far_it_can_be_smushed.height = min_size.height;
  1250. }
  1251. if (!meta_rectangle_could_fit_in_region (region_spanning_rectangles,
  1252. &how_far_it_can_be_smushed))
  1253. exit_early = TRUE;
  1254. /* Determine whether constraint is already satisfied; exit if it is */
  1255. constraint_satisfied =
  1256. meta_rectangle_contained_in_region (region_spanning_rectangles,
  1257. &info->current);
  1258. if (exit_early || constraint_satisfied || check_only)
  1259. {
  1260. meta_window_unextend_by_frame (window, &info->current, info->borders);
  1261. return constraint_satisfied;
  1262. }
  1263. /* Enforce constraint */
  1264. /* Clamp rectangle size for resize or move+resize actions */
  1265. if (info->action_type != ACTION_MOVE)
  1266. meta_rectangle_clamp_to_fit_into_region (region_spanning_rectangles,
  1267. info->fixed_directions,
  1268. &info->current,
  1269. &min_size);
  1270. if (info->is_user_action && info->action_type == ACTION_RESIZE)
  1271. /* For user resize, clip to the relevant region */
  1272. meta_rectangle_clip_to_region (region_spanning_rectangles,
  1273. info->fixed_directions,
  1274. &info->current);
  1275. else
  1276. {
  1277. /* For everything else, shove the rectangle into the relevant region */
  1278. meta_rectangle_shove_into_region (region_spanning_rectangles,
  1279. info->fixed_directions,
  1280. &info->current);
  1281. }
  1282. meta_window_unextend_by_frame (window, &info->current, info->borders);
  1283. return TRUE;
  1284. }
  1285. static gboolean
  1286. constrain_to_single_monitor (MetaWindow *window,
  1287. ConstraintInfo *info,
  1288. ConstraintPriority priority,
  1289. gboolean check_only)
  1290. {
  1291. if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_MONITOR)
  1292. return TRUE;
  1293. /* Exit early if we know the constraint won't apply--note that this constraint
  1294. * is only meant for normal windows (e.g. we don't want docks to be shoved
  1295. * "onscreen" by their own strut) and we can't apply it to frameless windows
  1296. * or else users will be unable to move windows such as XMMS across monitors.
  1297. */
  1298. if (window->type == META_WINDOW_DESKTOP ||
  1299. window->type == META_WINDOW_DOCK ||
  1300. window->screen->n_monitor_infos == 1 ||
  1301. !window->require_on_single_monitor ||
  1302. (!window->frame && !meta_window_is_client_decorated (window)) ||
  1303. info->is_user_action)
  1304. return TRUE;
  1305. /* Have a helper function handle the constraint for us */
  1306. return do_screen_and_monitor_relative_constraints (window,
  1307. info->usable_monitor_region,
  1308. info,
  1309. check_only);
  1310. }
  1311. static gboolean
  1312. constrain_fully_onscreen (MetaWindow *window,
  1313. ConstraintInfo *info,
  1314. ConstraintPriority priority,
  1315. gboolean check_only)
  1316. {
  1317. if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA)
  1318. return TRUE;
  1319. /* Exit early if we know the constraint won't apply--note that this constraint
  1320. * is only meant for normal windows (e.g. we don't want docks to be shoved
  1321. * "onscreen" by their own strut).
  1322. */
  1323. if (window->type == META_WINDOW_DESKTOP ||
  1324. window->type == META_WINDOW_DOCK ||
  1325. window->fullscreen ||
  1326. !window->require_fully_onscreen ||
  1327. info->is_user_action)
  1328. return TRUE;
  1329. /* Have a helper function handle the constraint for us */
  1330. return do_screen_and_monitor_relative_constraints (window,
  1331. info->usable_screen_region,
  1332. info,
  1333. check_only);
  1334. }
  1335. static gboolean
  1336. constrain_titlebar_visible (MetaWindow *window,
  1337. ConstraintInfo *info,
  1338. ConstraintPriority priority,
  1339. gboolean check_only)
  1340. {
  1341. gboolean unconstrained_user_action;
  1342. gboolean retval;
  1343. int bottom_amount;
  1344. int horiz_amount_offscreen, vert_amount_offscreen;
  1345. int horiz_amount_onscreen, vert_amount_onscreen;
  1346. int scale = meta_prefs_get_ui_scale ();
  1347. if (priority > PRIORITY_TITLEBAR_VISIBLE)
  1348. return TRUE;
  1349. /* Allow the titlebar beyond the top of the screen only if the user wasn't
  1350. * clicking on the frame to start the move.
  1351. */
  1352. unconstrained_user_action =
  1353. info->is_user_action && !window->display->grab_frame_action;
  1354. /* Exit early if we know the constraint won't apply--note that this constraint
  1355. * is only meant for normal windows (e.g. we don't want docks to be shoved
  1356. * "onscreen" by their own strut).
  1357. */
  1358. if (window->type == META_WINDOW_DESKTOP ||
  1359. window->type == META_WINDOW_DOCK ||
  1360. window->fullscreen ||
  1361. !window->require_titlebar_visible ||
  1362. unconstrained_user_action)
  1363. return TRUE;
  1364. /* Determine how much offscreen things are allowed. We first need to
  1365. * figure out how much must remain on the screen. For that, we use 25%
  1366. * window width/height but clamp to the range of (10,75) pixels. This is
  1367. * somewhat of a seat of my pants random guess at what might look good.
  1368. * Then, the amount that is allowed off is just the window size minus
  1369. * this amount (but no less than 0 for tiny windows).
  1370. */
  1371. horiz_amount_onscreen = info->current.width / 4;
  1372. vert_amount_onscreen = info->current.height / 4;
  1373. horiz_amount_onscreen = CLAMP (horiz_amount_onscreen, 10 * scale, 75 * scale);
  1374. vert_amount_onscreen = CLAMP (vert_amount_onscreen, 10 * scale, 75 * scale);
  1375. horiz_amount_offscreen = info->current.width - horiz_amount_onscreen;
  1376. vert_amount_offscreen = info->current.height - vert_amount_onscreen;
  1377. horiz_amount_offscreen = MAX (horiz_amount_offscreen, 0);
  1378. vert_amount_offscreen = MAX (vert_amount_offscreen, 0);
  1379. /* Allow the titlebar to touch the bottom panel; If there is no titlebar,
  1380. * require vert_amount to remain on the screen.
  1381. */
  1382. if (window->frame)
  1383. {
  1384. bottom_amount = info->current.height + info->borders->visible.bottom;
  1385. vert_amount_onscreen = info->borders->visible.top;
  1386. }
  1387. else if (meta_window_is_client_decorated (window))
  1388. {
  1389. vert_amount_onscreen = CSD_TITLEBAR_HEIGHT * scale; /* Hardcoded for now, we don't get this from Gtk */
  1390. bottom_amount = vert_amount_offscreen = MAX ((info->current.height - (vert_amount_onscreen * 2)), 0);
  1391. }
  1392. else
  1393. bottom_amount = vert_amount_offscreen;
  1394. /* Extend the region, have a helper function handle the constraint,
  1395. * then return the region to its original size.
  1396. */
  1397. meta_rectangle_expand_region_conditionally (info->usable_screen_region,
  1398. horiz_amount_offscreen,
  1399. horiz_amount_offscreen,
  1400. 0, /* Don't let titlebar off */
  1401. bottom_amount,
  1402. horiz_amount_onscreen,
  1403. vert_amount_onscreen);
  1404. retval =
  1405. do_screen_and_monitor_relative_constraints (window,
  1406. info->usable_screen_region,
  1407. info,
  1408. check_only);
  1409. meta_rectangle_expand_region_conditionally (info->usable_screen_region,
  1410. -horiz_amount_offscreen,
  1411. -horiz_amount_offscreen,
  1412. 0, /* Don't let titlebar off */
  1413. -bottom_amount,
  1414. horiz_amount_onscreen,
  1415. vert_amount_onscreen);
  1416. return retval;
  1417. }
  1418. static gboolean
  1419. constrain_partially_onscreen (MetaWindow *window,
  1420. ConstraintInfo *info,
  1421. ConstraintPriority priority,
  1422. gboolean check_only)
  1423. {
  1424. gboolean retval;
  1425. int top_amount, bottom_amount;
  1426. int horiz_amount_offscreen, vert_amount_offscreen;
  1427. int horiz_amount_onscreen, vert_amount_onscreen;
  1428. int scale = meta_prefs_get_ui_scale ();
  1429. if (priority > PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA)
  1430. return TRUE;
  1431. /* Exit early if we know the constraint won't apply--note that this constraint
  1432. * is only meant for normal windows (e.g. we don't want docks to be shoved
  1433. * "onscreen" by their own strut).
  1434. */
  1435. if (window->type == META_WINDOW_DESKTOP ||
  1436. window->type == META_WINDOW_DOCK)
  1437. return TRUE;
  1438. /* Determine how much offscreen things are allowed. We first need to
  1439. * figure out how much must remain on the screen. For that, we use 25%
  1440. * window width/height but clamp to the range of (10,75) pixels. This is
  1441. * somewhat of a seat of my pants random guess at what might look good.
  1442. * Then, the amount that is allowed off is just the window size minus
  1443. * this amount (but no less than 0 for tiny windows).
  1444. */
  1445. horiz_amount_onscreen = info->current.width / 4;
  1446. vert_amount_onscreen = info->current.height / 4;
  1447. horiz_amount_onscreen = CLAMP (horiz_amount_onscreen, 10 * scale, 75 * scale);
  1448. vert_amount_onscreen = CLAMP (vert_amount_onscreen, 10 * scale, 75 * scale);
  1449. horiz_amount_offscreen = info->current.width - horiz_amount_onscreen;
  1450. vert_amount_offscreen = info->current.height - vert_amount_onscreen;
  1451. horiz_amount_offscreen = MAX (horiz_amount_offscreen, 0);
  1452. vert_amount_offscreen = MAX (vert_amount_offscreen, 0);
  1453. top_amount = vert_amount_offscreen;
  1454. /* Allow the titlebar to touch the bottom panel; If there is no titlebar,
  1455. * require vert_amount to remain on the screen.
  1456. */
  1457. if (window->frame)
  1458. {
  1459. bottom_amount = info->current.height + info->borders->visible.bottom;
  1460. vert_amount_onscreen = info->borders->visible.top;
  1461. }
  1462. else if (meta_window_is_client_decorated (window))
  1463. {
  1464. top_amount = vert_amount_onscreen = CSD_TITLEBAR_HEIGHT * scale; /* Hardcoded for now, we don't get this from Gtk */
  1465. bottom_amount = vert_amount_offscreen = MAX ((info->current.height - (vert_amount_onscreen * 2)), 0);
  1466. }
  1467. else
  1468. bottom_amount = vert_amount_offscreen;
  1469. /* Extend the region, have a helper function handle the constraint,
  1470. * then return the region to its original size.
  1471. */
  1472. meta_rectangle_expand_region_conditionally (info->usable_screen_region,
  1473. horiz_amount_offscreen,
  1474. horiz_amount_offscreen,
  1475. top_amount,
  1476. bottom_amount,
  1477. horiz_amount_onscreen,
  1478. vert_amount_onscreen);
  1479. retval =
  1480. do_screen_and_monitor_relative_constraints (window,
  1481. info->usable_screen_region,
  1482. info,
  1483. check_only);
  1484. meta_rectangle_expand_region_conditionally (info->usable_screen_region,
  1485. -horiz_amount_offscreen,
  1486. -horiz_amount_offscreen,
  1487. -top_amount,
  1488. -bottom_amount,
  1489. horiz_amount_onscreen,
  1490. vert_amount_onscreen);
  1491. return retval;
  1492. }