1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /* Edge resistance for move/resize operations */
4 
5 /*
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 
24 #include <config.h>
25 #include "edge-resistance.h"
26 #include "boxes-private.h"
27 #include "display-private.h"
28 #include "workspace-private.h"
29 
30 /* A simple macro for whether a given window's edges are potentially
31  * relevant for resistance/snapping during a move/resize operation
32  */
33 #define WINDOW_EDGES_RELEVANT(window, display) \
34   meta_window_should_be_showing (window) &&    \
35   window->screen == display->grab_screen &&    \
36   window         != display->grab_window &&    \
37   window->type   != META_WINDOW_DESKTOP &&     \
38   window->type   != META_WINDOW_MENU    &&     \
39   window->type   != META_WINDOW_SPLASHSCREEN
40 
41 struct ResistanceDataForAnEdge
42 {
43   gboolean     timeout_setup;
44   guint        timeout_id;
45   int          timeout_edge_pos;
46   gboolean     timeout_over;
47   GSourceFunc  timeout_func;
48   MetaWindow  *window;
49   int          keyboard_buildup;
50 };
51 typedef struct ResistanceDataForAnEdge ResistanceDataForAnEdge;
52 
53 struct MetaEdgeResistanceData
54 {
55   GArray *left_edges;
56   GArray *right_edges;
57   GArray *top_edges;
58   GArray *bottom_edges;
59 
60   ResistanceDataForAnEdge left_data;
61   ResistanceDataForAnEdge right_data;
62   ResistanceDataForAnEdge top_data;
63   ResistanceDataForAnEdge bottom_data;
64 };
65 
66 static void compute_resistance_and_snapping_edges (MetaDisplay *display);
67 
68 /* !WARNING!: this function can return invalid indices (namely, either -1 or
69  * edges->len); this is by design, but you need to remember this.
70  */
71 static int
find_index_of_edge_near_position(const GArray * edges,int position,gboolean want_interval_min,gboolean horizontal)72 find_index_of_edge_near_position (const GArray *edges,
73                                   int           position,
74                                   gboolean      want_interval_min,
75                                   gboolean      horizontal)
76 {
77   /* This is basically like a binary search, except that we're trying to
78    * find a range instead of an exact value.  So, if we have in our array
79    *   Value: 3  27 316 316 316 505 522 800 1213
80    *   Index: 0   1   2   3   4   5   6   7    8
81    * and we call this function with position=500 & want_interval_min=TRUE
82    * then we should get 5 (because 505 is the first value bigger than 500).
83    * If we call this function with position=805 and want_interval_min=FALSE
84    * then we should get 7 (because 800 is the last value smaller than 800).
85    * A couple more, to make things clear:
86    *    position  want_interval_min  correct_answer
87    *         316               TRUE               2
88    *         316              FALSE               4
89    *           2              FALSE              -1
90    *        2000               TRUE               9
91    */
92   int low, high, mid;
93   int compare;
94   MetaEdge *edge;
95 
96   /* Initialize mid, edge, & compare in the off change that the array only
97    * has one element.
98    */
99   mid  = 0;
100   edge = g_array_index (edges, MetaEdge*, mid);
101   compare = horizontal ? edge->rect.x : edge->rect.y;
102 
103   /* Begin the search... */
104   low  = 0;
105   high = edges->len - 1;
106   while (low < high)
107     {
108       mid = low + (high - low)/2;
109       edge = g_array_index (edges, MetaEdge*, mid);
110       compare = horizontal ? edge->rect.x : edge->rect.y;
111 
112       if (compare == position)
113         break;
114 
115       if (compare > position)
116         high = mid - 1;
117       else
118         low = mid + 1;
119     }
120 
121   /* mid should now be _really_ close to the index we want, so we start
122    * linearly searching.  However, note that we don't know if mid is less
123    * than or greater than what we need and it's possible that there are
124    * several equal values equal to what we were searching for and we ended
125    * up in the middle of them instead of at the end.  So we may need to
126    * move mid multiple locations over.
127    */
128   if (want_interval_min)
129     {
130       while (compare >= position && mid > 0)
131         {
132           mid--;
133           edge = g_array_index (edges, MetaEdge*, mid);
134           compare = horizontal ? edge->rect.x : edge->rect.y;
135         }
136       while (compare < position && mid < (int)edges->len - 1)
137         {
138           mid++;
139           edge = g_array_index (edges, MetaEdge*, mid);
140           compare = horizontal ? edge->rect.x : edge->rect.y;
141         }
142 
143       /* Special case for no values in array big enough */
144       if (compare < position)
145         return edges->len;
146 
147       /* Return the found value */
148       return mid;
149     }
150   else
151     {
152       while (compare <= position && mid < (int)edges->len - 1)
153         {
154           mid++;
155           edge = g_array_index (edges, MetaEdge*, mid);
156           compare = horizontal ? edge->rect.x : edge->rect.y;
157         }
158       while (compare > position && mid > 0)
159         {
160           mid--;
161           edge = g_array_index (edges, MetaEdge*, mid);
162           compare = horizontal ? edge->rect.x : edge->rect.y;
163         }
164 
165       /* Special case for no values in array small enough */
166       if (compare > position)
167         return -1;
168 
169       /* Return the found value */
170       return mid;
171     }
172 }
173 
174 static gboolean
points_on_same_side(int ref,int pt1,int pt2)175 points_on_same_side (int ref, int pt1, int pt2)
176 {
177   return (pt1 - ref) * (pt2 - ref) > 0;
178 }
179 
180 static int
find_nearest_position(const GArray * edges,int position,int old_position,const MetaRectangle * new_rect,gboolean horizontal,gboolean only_forward)181 find_nearest_position (const GArray        *edges,
182                        int                  position,
183                        int                  old_position,
184                        const MetaRectangle *new_rect,
185                        gboolean             horizontal,
186                        gboolean             only_forward)
187 {
188   /* This is basically just a binary search except that we're looking
189    * for the value closest to position, rather than finding that
190    * actual value.  Also, we ignore any edges that aren't relevant
191    * given the horizontal/vertical position of new_rect.
192    */
193   int low, high, mid;
194   int compare;
195   MetaEdge *edge;
196   int best, best_dist, i;
197   gboolean edges_align;
198 
199   /* Initialize mid in the off chance that the array only
200    * has one element.
201    */
202   mid  = 0;
203 
204   /* Begin the search... */
205   low  = 0;
206   high = edges->len - 1;
207   while (low < high)
208     {
209       mid = low + (high - low)/2;
210       edge = g_array_index (edges, MetaEdge*, mid);
211       compare = horizontal ? edge->rect.x : edge->rect.y;
212 
213       if (compare == position)
214         break;
215 
216       if (compare > position)
217         high = mid - 1;
218       else
219         low = mid + 1;
220     }
221 
222   /* mid should now be _really_ close to the index we want, so we
223    * start searching nearby for something that overlaps and is closer
224    * than the original position.
225    */
226   best = old_position;
227   best_dist = INT_MAX;
228 
229   /* Start the search at mid */
230   edge = g_array_index (edges, MetaEdge*, mid);
231   compare = horizontal ? edge->rect.x : edge->rect.y;
232   edges_align = meta_rectangle_edge_aligns (new_rect, edge);
233   if (edges_align &&
234       (!only_forward || !points_on_same_side (position, compare, old_position)))
235     {
236       int dist = ABS (compare - position);
237       if (dist < best_dist)
238         {
239           best = compare;
240           best_dist = dist;
241         }
242     }
243 
244   /* Now start searching higher than mid */
245   for (i = mid + 1; i < (int)edges->len; i++)
246     {
247       edge = g_array_index (edges, MetaEdge*, i);
248       compare = horizontal ? edge->rect.x : edge->rect.y;
249 
250       edges_align = horizontal ?
251         meta_rectangle_vert_overlap (&edge->rect, new_rect) :
252         meta_rectangle_horiz_overlap (&edge->rect, new_rect);
253 
254       if (edges_align &&
255           (!only_forward ||
256            !points_on_same_side (position, compare, old_position)))
257         {
258           int dist = ABS (compare - position);
259           if (dist < best_dist)
260             {
261               best = compare;
262               best_dist = dist;
263             }
264           break;
265         }
266     }
267 
268   /* Now start searching lower than mid */
269   for (i = mid-1; i >= 0; i--)
270     {
271       edge = g_array_index (edges, MetaEdge*, i);
272       compare = horizontal ? edge->rect.x : edge->rect.y;
273 
274       edges_align = horizontal ?
275         meta_rectangle_vert_overlap (&edge->rect, new_rect) :
276         meta_rectangle_horiz_overlap (&edge->rect, new_rect);
277 
278       if (edges_align &&
279           (!only_forward ||
280            !points_on_same_side (position, compare, old_position)))
281         {
282           int dist = ABS (compare - position);
283           if (dist < best_dist)
284             {
285               best = compare;
286             }
287           break;
288         }
289     }
290 
291   /* Return the best one found */
292   return best;
293 }
294 
295 static gboolean
movement_towards_edge(MetaSide side,int increment)296 movement_towards_edge (MetaSide side, int increment)
297 {
298   switch (side)
299     {
300     case META_SIDE_LEFT:
301     case META_SIDE_TOP:
302       return increment < 0;
303     case META_SIDE_RIGHT:
304     case META_SIDE_BOTTOM:
305       return increment > 0;
306     default:
307       g_assert_not_reached ();
308     }
309 }
310 
311 static gboolean
edge_resistance_timeout(gpointer data)312 edge_resistance_timeout (gpointer data)
313 {
314   ResistanceDataForAnEdge *resistance_data = data;
315 
316   resistance_data->timeout_over = TRUE;
317   resistance_data->timeout_id = 0;
318   (*resistance_data->timeout_func)(resistance_data->window);
319 
320   return FALSE;
321 }
322 
323 static int
apply_edge_resistance(MetaWindow * window,int old_pos,int new_pos,const MetaRectangle * old_rect,const MetaRectangle * new_rect,GArray * edges,ResistanceDataForAnEdge * resistance_data,GSourceFunc timeout_func,gboolean xdir,gboolean keyboard_op)324 apply_edge_resistance (MetaWindow                *window,
325                        int                        old_pos,
326                        int                        new_pos,
327                        const MetaRectangle       *old_rect,
328                        const MetaRectangle       *new_rect,
329                        GArray                    *edges,
330                        ResistanceDataForAnEdge   *resistance_data,
331                        GSourceFunc                timeout_func,
332                        gboolean                   xdir,
333                        gboolean                   keyboard_op)
334 {
335   int i, begin, end;
336   int last_edge;
337   gboolean increasing = new_pos > old_pos;
338   int      increment = increasing ? 1 : -1;
339 
340   const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW    = 16;
341   const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW   =  0;
342   const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_MONITOR   = 32;
343   const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_MONITOR  =  0;
344   const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN    = 32;
345   const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN   =  0;
346   const int TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW   =   0;
347   const int TIMEOUT_RESISTANCE_LENGTH_MS_MONITOR =   0;
348   const int TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN   =   0;
349 
350   /* Edge resistance can be disabled in gettings. */
351   if (!meta_prefs_get_edge_resistance_window ())
352     return new_pos;
353 
354   /* Quit if no movement was specified */
355   if (old_pos == new_pos)
356     return new_pos;
357 
358   /* Remove the old timeout if it's no longer relevant */
359   if (resistance_data->timeout_setup &&
360       ((resistance_data->timeout_edge_pos > old_pos &&
361         resistance_data->timeout_edge_pos > new_pos)  ||
362        (resistance_data->timeout_edge_pos < old_pos &&
363         resistance_data->timeout_edge_pos < new_pos)))
364     {
365       resistance_data->timeout_setup = FALSE;
366       if (resistance_data->timeout_id != 0)
367         {
368           g_source_remove (resistance_data->timeout_id);
369           resistance_data->timeout_id = 0;
370         }
371     }
372 
373   /* Get the range of indices in the edge array that we move past/to. */
374   begin = find_index_of_edge_near_position (edges, old_pos,  increasing, xdir);
375   end   = find_index_of_edge_near_position (edges, new_pos, !increasing, xdir);
376 
377   /* begin and end can be outside the array index, if the window is partially
378    * off the screen
379    */
380   last_edge = edges->len - 1;
381   begin = CLAMP (begin, 0, last_edge);
382   end   = CLAMP (end,   0, last_edge);
383 
384   /* Loop over all these edges we're moving past/to. */
385   i = begin;
386   while ((increasing  && i <= end) ||
387          (!increasing && i >= end))
388     {
389       gboolean  edges_align;
390       MetaEdge *edge = g_array_index (edges, MetaEdge*, i);
391       int       compare = xdir ? edge->rect.x : edge->rect.y;
392 
393       /* Find out if this edge is relevant */
394       edges_align = meta_rectangle_edge_aligns (new_rect, edge)  ||
395                     meta_rectangle_edge_aligns (old_rect, edge);
396 
397       /* Nothing to do unless the edges align */
398       if (!edges_align)
399         {
400           /* Go to the next edge in the range */
401           i += increment;
402           continue;
403         }
404 
405       /* Rest is easier to read if we split on keyboard vs. mouse op */
406       if (keyboard_op)
407         {
408           if ((old_pos < compare && compare < new_pos) ||
409               (old_pos > compare && compare > new_pos))
410             return compare;
411         }
412       else /* mouse op */
413         {
414           int threshold;
415 
416           /* TIMEOUT RESISTANCE: If the edge is relevant and we're moving
417            * towards it, then we may want to have some kind of time delay
418            * before the user can move past this edge.
419            */
420           if (movement_towards_edge (edge->side_type, increment))
421             {
422               /* First, determine the length of time for the resistance */
423               int timeout_length_ms = 0;
424               switch (edge->edge_type)
425                 {
426                 case META_EDGE_WINDOW:
427                   timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW;
428                   break;
429                 case META_EDGE_MONITOR:
430                   timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_MONITOR;
431                   break;
432                 case META_EDGE_SCREEN:
433                   timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN;
434                   break;
435                 }
436 
437               if (!resistance_data->timeout_setup &&
438                   timeout_length_ms != 0)
439                 {
440                   resistance_data->timeout_id =
441                     g_timeout_add (timeout_length_ms,
442                                    edge_resistance_timeout,
443                                    resistance_data);
444                   resistance_data->timeout_setup = TRUE;
445                   resistance_data->timeout_edge_pos = compare;
446                   resistance_data->timeout_over = FALSE;
447                   resistance_data->timeout_func = timeout_func;
448                   resistance_data->window = window;
449                 }
450               if (!resistance_data->timeout_over &&
451                   timeout_length_ms != 0)
452                 return compare;
453             }
454 
455           /* PIXEL DISTANCE MOUSE RESISTANCE: If the edge matters and the
456            * user hasn't moved at least threshold pixels past this edge,
457            * stop movement at this edge.  (Note that this is different from
458            * keyboard resistance precisely because keyboard move ops are
459            * relative to previous positions, whereas mouse move ops are
460            * relative to differences in mouse position and mouse position
461            * is an absolute quantity rather than a relative quantity)
462            */
463 
464           /* First, determine the threshold */
465           threshold = 0;
466           switch (edge->edge_type)
467             {
468             case META_EDGE_WINDOW:
469               if (movement_towards_edge (edge->side_type, increment))
470                 threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW;
471               else
472                 threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW;
473               break;
474             case META_EDGE_MONITOR:
475               if (movement_towards_edge (edge->side_type, increment))
476                 threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_MONITOR;
477               else
478                 threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_MONITOR;
479               break;
480             case META_EDGE_SCREEN:
481               if (movement_towards_edge (edge->side_type, increment))
482                 threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN;
483               else
484                 threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN;
485               break;
486             }
487 
488           if (ABS (compare - new_pos) < threshold)
489             return compare;
490         }
491 
492       /* Go to the next edge in the range */
493       i += increment;
494     }
495 
496   return new_pos;
497 }
498 
499 static int
apply_edge_snapping(int old_pos,int new_pos,const MetaRectangle * new_rect,GArray * edges,gboolean xdir,gboolean keyboard_op)500 apply_edge_snapping (int                  old_pos,
501                      int                  new_pos,
502                      const MetaRectangle *new_rect,
503                      GArray              *edges,
504                      gboolean             xdir,
505                      gboolean             keyboard_op)
506 {
507   int snap_to;
508 
509   if (old_pos == new_pos)
510     return new_pos;
511 
512   snap_to = find_nearest_position (edges,
513                                    new_pos,
514                                    old_pos,
515                                    new_rect,
516                                    xdir,
517                                    keyboard_op);
518 
519   /* If mouse snap-moving, the user could easily accidentally move just a
520    * couple pixels in a direction they didn't mean to move; so ignore snap
521    * movement in those cases unless it's only a small number of pixels
522    * anyway.
523    */
524   if (!keyboard_op &&
525       ABS (snap_to - old_pos) >= 8 &&
526       ABS (new_pos - old_pos) < 8)
527     return old_pos;
528   else
529     /* Otherwise, return the snapping position found */
530     return snap_to;
531 }
532 
533 /* This function takes the position (including any frame) of the window and
534  * a proposed new position (ignoring edge resistance/snapping), and then
535  * applies edge resistance to EACH edge (separately) updating new_outer.
536  * It returns true if new_outer is modified, false otherwise.
537  *
538  * display->grab_edge_resistance_data MUST already be setup or calling this
539  * function will cause a crash.
540  */
541 static gboolean
apply_edge_resistance_to_each_side(MetaDisplay * display,MetaWindow * window,const MetaRectangle * old_outer,MetaRectangle * new_outer,GSourceFunc timeout_func,gboolean auto_snap,gboolean keyboard_op,gboolean is_resize)542 apply_edge_resistance_to_each_side (MetaDisplay         *display,
543                                     MetaWindow          *window,
544                                     const MetaRectangle *old_outer,
545                                     MetaRectangle       *new_outer,
546                                     GSourceFunc          timeout_func,
547                                     gboolean             auto_snap,
548                                     gboolean             keyboard_op,
549                                     gboolean             is_resize)
550 {
551   MetaEdgeResistanceData *edge_data;
552   MetaRectangle           modified_rect;
553   gboolean                modified;
554   int new_left, new_right, new_top, new_bottom;
555 
556   if (display->grab_edge_resistance_data == NULL)
557     compute_resistance_and_snapping_edges (display);
558 
559   edge_data = display->grab_edge_resistance_data;
560 
561   if (auto_snap)
562     {
563       /* Do the auto snapping instead of normal edge resistance; in all
564        * cases, we allow snapping to opposite kinds of edges (e.g. left
565        * sides of windows to both left and right edges.
566        */
567 
568       new_left   = apply_edge_snapping (BOX_LEFT (*old_outer),
569                                         BOX_LEFT (*new_outer),
570                                         new_outer,
571                                         edge_data->left_edges,
572                                         TRUE,
573                                         keyboard_op);
574 
575       new_right  = apply_edge_snapping (BOX_RIGHT (*old_outer),
576                                         BOX_RIGHT (*new_outer),
577                                         new_outer,
578                                         edge_data->right_edges,
579                                         TRUE,
580                                         keyboard_op);
581 
582       new_top    = apply_edge_snapping (BOX_TOP (*old_outer),
583                                         BOX_TOP (*new_outer),
584                                         new_outer,
585                                         edge_data->top_edges,
586                                         FALSE,
587                                         keyboard_op);
588 
589       new_bottom = apply_edge_snapping (BOX_BOTTOM (*old_outer),
590                                         BOX_BOTTOM (*new_outer),
591                                         new_outer,
592                                         edge_data->bottom_edges,
593                                         FALSE,
594                                         keyboard_op);
595     }
596   else
597     {
598       /* Disable edge resistance for resizes when windows have size
599        * increment hints; see #346782.  For all other cases, apply
600        * them.
601        */
602       if (!is_resize || window->size_hints.width_inc == 1)
603         {
604           /* Now, apply the normal horizontal edge resistance */
605           new_left   = apply_edge_resistance (window,
606                                               BOX_LEFT (*old_outer),
607                                               BOX_LEFT (*new_outer),
608                                               old_outer,
609                                               new_outer,
610                                               edge_data->left_edges,
611                                               &edge_data->left_data,
612                                               timeout_func,
613                                               TRUE,
614                                               keyboard_op);
615           new_right  = apply_edge_resistance (window,
616                                               BOX_RIGHT (*old_outer),
617                                               BOX_RIGHT (*new_outer),
618                                               old_outer,
619                                               new_outer,
620                                               edge_data->right_edges,
621                                               &edge_data->right_data,
622                                               timeout_func,
623                                               TRUE,
624                                               keyboard_op);
625         }
626       else
627         {
628           new_left  = new_outer->x;
629           new_right = new_outer->x + new_outer->width;
630         }
631       /* Same for vertical resizes... */
632       if (!is_resize || window->size_hints.height_inc == 1)
633         {
634           new_top    = apply_edge_resistance (window,
635                                               BOX_TOP (*old_outer),
636                                               BOX_TOP (*new_outer),
637                                               old_outer,
638                                               new_outer,
639                                               edge_data->top_edges,
640                                               &edge_data->top_data,
641                                               timeout_func,
642                                               FALSE,
643                                               keyboard_op);
644           new_bottom = apply_edge_resistance (window,
645                                               BOX_BOTTOM (*old_outer),
646                                               BOX_BOTTOM (*new_outer),
647                                               old_outer,
648                                               new_outer,
649                                               edge_data->bottom_edges,
650                                               &edge_data->bottom_data,
651                                               timeout_func,
652                                               FALSE,
653                                               keyboard_op);
654         }
655       else
656         {
657           new_top    = new_outer->y;
658           new_bottom = new_outer->y + new_outer->height;
659         }
660     }
661 
662   /* Determine whether anything changed, and save the changes */
663   modified_rect = meta_rect (new_left,
664                              new_top,
665                              new_right - new_left,
666                              new_bottom - new_top);
667   modified = !meta_rectangle_equal (new_outer, &modified_rect);
668   *new_outer = modified_rect;
669   return modified;
670 }
671 
672 LOCAL_SYMBOL void
meta_display_cleanup_edges(MetaDisplay * display)673 meta_display_cleanup_edges (MetaDisplay *display)
674 {
675   guint i,j;
676   MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data;
677   GHashTable *edges_to_be_freed;
678 
679   if (edge_data == NULL) /* Not currently cached */
680     return;
681 
682   /* We first need to clean out any window edges */
683   edges_to_be_freed = g_hash_table_new_full (g_direct_hash, g_direct_equal,
684                                              free, NULL);
685   for (i = 0; i < 4; i++)
686     {
687       GArray *tmp = NULL;
688       MetaSide side;
689       switch (i)
690         {
691         case 0:
692           tmp = edge_data->left_edges;
693           side = META_SIDE_LEFT;
694           break;
695         case 1:
696           tmp = edge_data->right_edges;
697           side = META_SIDE_RIGHT;
698           break;
699         case 2:
700           tmp = edge_data->top_edges;
701           side = META_SIDE_TOP;
702           break;
703         case 3:
704           tmp = edge_data->bottom_edges;
705           side = META_SIDE_BOTTOM;
706           break;
707         default:
708           g_assert_not_reached ();
709         }
710 
711       for (j = 0; j < tmp->len; j++)
712         {
713           MetaEdge *edge = g_array_index (tmp, MetaEdge*, j);
714           if (edge->edge_type == META_EDGE_WINDOW &&
715               edge->side_type == side)
716             {
717               /* The same edge will appear in two arrays, and we can't free
718                * it yet we still need to compare edge->side_type for the other
719                * array that it is in.  So store it in a hash table for later
720                * freeing.  Could also do this in a simple linked list.
721                */
722               g_hash_table_insert (edges_to_be_freed, edge, edge);
723             }
724         }
725     }
726 
727   /* Now free all the window edges (the key destroy function is free) */
728   g_hash_table_destroy (edges_to_be_freed);
729 
730   /* Now free the arrays and data */
731   g_array_free (edge_data->left_edges, TRUE);
732   g_array_free (edge_data->right_edges, TRUE);
733   g_array_free (edge_data->top_edges, TRUE);
734   g_array_free (edge_data->bottom_edges, TRUE);
735   edge_data->left_edges = NULL;
736   edge_data->right_edges = NULL;
737   edge_data->top_edges = NULL;
738   edge_data->bottom_edges = NULL;
739 
740   /* Cleanup the timeouts */
741   if (edge_data->left_data.timeout_setup && edge_data->left_data.timeout_id != 0) {
742     g_source_remove (edge_data->left_data.timeout_id);
743     edge_data->left_data.timeout_id = 0;
744   }
745   if (edge_data->right_data.timeout_setup && edge_data->right_data.timeout_id != 0) {
746     g_source_remove (edge_data->right_data.timeout_id);
747     edge_data->right_data.timeout_id = 0;
748   }
749   if (edge_data->top_data.timeout_setup && edge_data->top_data.timeout_id != 0) {
750     g_source_remove (edge_data->top_data.timeout_id);
751     edge_data->top_data.timeout_id = 0;
752   }
753   if (edge_data->bottom_data.timeout_setup && edge_data->bottom_data.timeout_id != 0) {
754     g_source_remove (edge_data->bottom_data.timeout_id);
755     edge_data->bottom_data.timeout_id = 0;
756   }
757 
758   free (display->grab_edge_resistance_data);
759   display->grab_edge_resistance_data = NULL;
760 }
761 
762 static int
stupid_sort_requiring_extra_pointer_dereference(gconstpointer a,gconstpointer b)763 stupid_sort_requiring_extra_pointer_dereference (gconstpointer a,
764                                                  gconstpointer b)
765 {
766   const MetaEdge * const *a_edge = a;
767   const MetaEdge * const *b_edge = b;
768   return meta_rectangle_edge_cmp_ignore_type (*a_edge, *b_edge);
769 }
770 
771 static void
cache_edges(MetaDisplay * display,GList * window_edges,GList * monitor_edges,GList * screen_edges)772 cache_edges (MetaDisplay *display,
773              GList *window_edges,
774              GList *monitor_edges,
775              GList *screen_edges)
776 {
777   MetaEdgeResistanceData *edge_data;
778   GList *tmp;
779   int num_left, num_right, num_top, num_bottom;
780   int i;
781 
782   /*
783    * 0th: Print debugging information to the log about the edges
784    */
785 #ifdef WITH_VERBOSE_MODE
786   if (meta_is_verbose())
787     {
788       int max_edges = MAX (MAX( g_list_length (window_edges),
789                                 g_list_length (monitor_edges)),
790                            g_list_length (screen_edges));
791       char big_buffer[(EDGE_LENGTH+2)*max_edges];
792 
793       meta_rectangle_edge_list_to_string (window_edges, ", ", big_buffer);
794       meta_topic (META_DEBUG_EDGE_RESISTANCE,
795                   "Window edges for resistance  : %s\n", big_buffer);
796 
797       meta_rectangle_edge_list_to_string (monitor_edges, ", ", big_buffer);
798       meta_topic (META_DEBUG_EDGE_RESISTANCE,
799                   "Monitor edges for resistance: %s\n", big_buffer);
800 
801       meta_rectangle_edge_list_to_string (screen_edges, ", ", big_buffer);
802       meta_topic (META_DEBUG_EDGE_RESISTANCE,
803                   "Screen edges for resistance  : %s\n", big_buffer);
804     }
805 #endif
806 
807   /*
808    * 1st: Get the total number of each kind of edge
809    */
810   num_left = num_right = num_top = num_bottom = 0;
811   for (i = 0; i < 3; i++)
812     {
813       tmp = NULL;
814       switch (i)
815         {
816         case 0:
817           tmp = window_edges;
818           break;
819         case 1:
820           tmp = monitor_edges;
821           break;
822         case 2:
823           tmp = screen_edges;
824           break;
825         default:
826           g_assert_not_reached ();
827         }
828 
829       while (tmp)
830         {
831           MetaEdge *edge = tmp->data;
832           switch (edge->side_type)
833             {
834             case META_SIDE_LEFT:
835               num_left++;
836               break;
837             case META_SIDE_RIGHT:
838               num_right++;
839               break;
840             case META_SIDE_TOP:
841               num_top++;
842               break;
843             case META_SIDE_BOTTOM:
844               num_bottom++;
845               break;
846             default:
847               g_assert_not_reached ();
848             }
849           tmp = tmp->next;
850         }
851     }
852 
853   /*
854    * 2nd: Allocate the edges
855    */
856   g_assert (display->grab_edge_resistance_data == NULL);
857   display->grab_edge_resistance_data = g_new0 (MetaEdgeResistanceData, 1);
858   edge_data = display->grab_edge_resistance_data;
859   edge_data->left_edges   = g_array_sized_new (FALSE,
860                                                FALSE,
861                                                sizeof(MetaEdge*),
862                                                num_left + num_right);
863   edge_data->right_edges  = g_array_sized_new (FALSE,
864                                                FALSE,
865                                                sizeof(MetaEdge*),
866                                                num_left + num_right);
867   edge_data->top_edges    = g_array_sized_new (FALSE,
868                                                FALSE,
869                                                sizeof(MetaEdge*),
870                                                num_top + num_bottom);
871   edge_data->bottom_edges = g_array_sized_new (FALSE,
872                                                FALSE,
873                                                sizeof(MetaEdge*),
874                                                num_top + num_bottom);
875 
876   /*
877    * 3rd: Add the edges to the arrays
878    */
879   for (i = 0; i < 3; i++)
880     {
881       tmp = NULL;
882       switch (i)
883         {
884         case 0:
885           tmp = window_edges;
886           break;
887         case 1:
888           tmp = monitor_edges;
889           break;
890         case 2:
891           tmp = screen_edges;
892           break;
893         default:
894           g_assert_not_reached ();
895         }
896 
897       while (tmp)
898         {
899           MetaEdge *edge = tmp->data;
900           switch (edge->side_type)
901             {
902             case META_SIDE_LEFT:
903             case META_SIDE_RIGHT:
904               g_array_append_val (edge_data->left_edges, edge);
905               g_array_append_val (edge_data->right_edges, edge);
906               break;
907             case META_SIDE_TOP:
908             case META_SIDE_BOTTOM:
909               g_array_append_val (edge_data->top_edges, edge);
910               g_array_append_val (edge_data->bottom_edges, edge);
911               break;
912             default:
913               g_assert_not_reached ();
914             }
915           tmp = tmp->next;
916         }
917     }
918 
919   /*
920    * 4th: Sort the arrays (FIXME: This is kinda dumb since the arrays were
921    * individually sorted earlier and we could have done this faster and
922    * avoided this sort by sticking them into the array with some simple
923    * merging of the lists).
924    */
925   g_array_sort (display->grab_edge_resistance_data->left_edges,
926                 stupid_sort_requiring_extra_pointer_dereference);
927   g_array_sort (display->grab_edge_resistance_data->right_edges,
928                 stupid_sort_requiring_extra_pointer_dereference);
929   g_array_sort (display->grab_edge_resistance_data->top_edges,
930                 stupid_sort_requiring_extra_pointer_dereference);
931   g_array_sort (display->grab_edge_resistance_data->bottom_edges,
932                 stupid_sort_requiring_extra_pointer_dereference);
933 }
934 
935 static void
initialize_grab_edge_resistance_data(MetaDisplay * display)936 initialize_grab_edge_resistance_data (MetaDisplay *display)
937 {
938   MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data;
939 
940   edge_data->left_data.timeout_setup   = FALSE;
941   edge_data->right_data.timeout_setup  = FALSE;
942   edge_data->top_data.timeout_setup    = FALSE;
943   edge_data->bottom_data.timeout_setup = FALSE;
944 
945   edge_data->left_data.keyboard_buildup   = 0;
946   edge_data->right_data.keyboard_buildup  = 0;
947   edge_data->top_data.keyboard_buildup    = 0;
948   edge_data->bottom_data.keyboard_buildup = 0;
949 }
950 
951 static void
compute_resistance_and_snapping_edges(MetaDisplay * display)952 compute_resistance_and_snapping_edges (MetaDisplay *display)
953 {
954   GList *stacked_windows;
955   GList *cur_window_iter;
956   GList *edges;
957   /* Lists of window positions (rects) and their relative stacking positions */
958   int stack_position;
959   GSList *obscuring_windows, *window_stacking;
960   /* The portions of the above lists that still remain at the stacking position
961    * in the layer that we are working on
962    */
963   GSList *rem_windows, *rem_win_stacking;
964 
965   g_assert (display->grab_window != NULL);
966   meta_topic (META_DEBUG_WINDOW_OPS,
967               "Computing edges to resist-movement or snap-to for %s.\n",
968               display->grab_window->desc);
969 
970   /*
971    * 1st: Get the list of relevant windows, from bottom to top
972    */
973   stacked_windows =
974     meta_stack_list_windows (display->grab_screen->stack,
975                              display->grab_screen->active_workspace);
976 
977   /*
978    * 2nd: we need to separate that stacked list into a list of windows that
979    * can obscure other edges.  To make sure we only have windows obscuring
980    * those below it instead of going both ways, we also need to keep a
981    * counter list.  Messy, I know.
982    */
983   obscuring_windows = window_stacking = NULL;
984   cur_window_iter = stacked_windows;
985   stack_position = 0;
986   while (cur_window_iter != NULL)
987     {
988       MetaWindow *cur_window = cur_window_iter->data;
989       if (WINDOW_EDGES_RELEVANT (cur_window, display))
990         {
991           MetaRectangle *new_rect;
992           new_rect = g_new (MetaRectangle, 1);
993           meta_window_get_outer_rect (cur_window, new_rect);
994           obscuring_windows = g_slist_prepend (obscuring_windows, new_rect);
995           window_stacking =
996             g_slist_prepend (window_stacking, GINT_TO_POINTER (stack_position));
997         }
998 
999       stack_position++;
1000       cur_window_iter = cur_window_iter->next;
1001     }
1002   /* Put 'em in bottom to top order */
1003   rem_windows = obscuring_windows = g_slist_reverse (obscuring_windows);
1004   rem_win_stacking = window_stacking = g_slist_reverse (window_stacking);
1005 
1006   /*
1007    * 3rd: loop over the windows again, this time getting the edges from
1008    * them and removing intersections with the relevant obscuring_windows &
1009    * obscuring_docks.
1010    */
1011   edges = NULL;
1012   stack_position = 0;
1013   cur_window_iter = stacked_windows;
1014   while (cur_window_iter != NULL)
1015     {
1016       MetaRectangle  cur_rect;
1017       MetaWindow    *cur_window = cur_window_iter->data;
1018       meta_window_get_outer_rect (cur_window, &cur_rect);
1019 
1020       /* Check if we want to use this window's edges for edge
1021        * resistance (note that dock edges are considered screen edges
1022        * which are handled separately
1023        */
1024       if (WINDOW_EDGES_RELEVANT (cur_window, display) &&
1025           cur_window->type != META_WINDOW_DOCK)
1026         {
1027           GList *new_edges;
1028           MetaEdge *new_edge;
1029           MetaRectangle reduced;
1030 
1031           /* We don't care about snapping to any portion of the window that
1032            * is offscreen (we also don't care about parts of edges covered
1033            * by other windows or DOCKS, but that's handled below).
1034            */
1035           meta_rectangle_intersect (&cur_rect,
1036                                     &display->grab_screen->rect,
1037                                     &reduced);
1038 
1039           new_edges = NULL;
1040 
1041           /* Left side of this window is resistance for the right edge of
1042            * the window being moved.
1043            */
1044           new_edge = g_new (MetaEdge, 1);
1045           new_edge->rect = reduced;
1046           new_edge->rect.width = 0;
1047           new_edge->side_type = META_SIDE_RIGHT;
1048           new_edge->edge_type = META_EDGE_WINDOW;
1049           new_edges = g_list_prepend (new_edges, new_edge);
1050 
1051           /* Right side of this window is resistance for the left edge of
1052            * the window being moved.
1053            */
1054           new_edge = g_new (MetaEdge, 1);
1055           new_edge->rect = reduced;
1056           new_edge->rect.x += new_edge->rect.width;
1057           new_edge->rect.width = 0;
1058           new_edge->side_type = META_SIDE_LEFT;
1059           new_edge->edge_type = META_EDGE_WINDOW;
1060           new_edges = g_list_prepend (new_edges, new_edge);
1061 
1062           /* Top side of this window is resistance for the bottom edge of
1063            * the window being moved.
1064            */
1065           new_edge = g_new (MetaEdge, 1);
1066           new_edge->rect = reduced;
1067           new_edge->rect.height = 0;
1068           new_edge->side_type = META_SIDE_BOTTOM;
1069           new_edge->edge_type = META_EDGE_WINDOW;
1070           new_edges = g_list_prepend (new_edges, new_edge);
1071 
1072           /* Top side of this window is resistance for the bottom edge of
1073            * the window being moved.
1074            */
1075           new_edge = g_new (MetaEdge, 1);
1076           new_edge->rect = reduced;
1077           new_edge->rect.y += new_edge->rect.height;
1078           new_edge->rect.height = 0;
1079           new_edge->side_type = META_SIDE_TOP;
1080           new_edge->edge_type = META_EDGE_WINDOW;
1081           new_edges = g_list_prepend (new_edges, new_edge);
1082 
1083           /* Update the remaining windows to only those at a higher
1084            * stacking position than this one.
1085            */
1086           while (rem_win_stacking &&
1087                  stack_position >= GPOINTER_TO_INT (rem_win_stacking->data))
1088             {
1089               rem_windows      = rem_windows->next;
1090               rem_win_stacking = rem_win_stacking->next;
1091             }
1092 
1093           /* Remove edge portions overlapped by rem_windows and rem_docks */
1094           new_edges =
1095             meta_rectangle_remove_intersections_with_boxes_from_edges (
1096               new_edges,
1097               rem_windows);
1098 
1099           /* Save the new edges */
1100           edges = g_list_concat (new_edges, edges);
1101         }
1102 
1103       stack_position++;
1104       cur_window_iter = cur_window_iter->next;
1105     }
1106 
1107   /*
1108    * 4th: Free the extra memory not needed and sort the list
1109    */
1110   g_list_free (stacked_windows);
1111   /* Free the memory used by the obscuring windows/docks lists */
1112   g_slist_free (window_stacking);
1113   /* FIXME: Shouldn't there be a helper function to make this one line of code
1114    * to free a list instead of four ugly ones?
1115    */
1116   g_slist_foreach (obscuring_windows,
1117                    (void (*)(gpointer,gpointer))&free, /* ew, for ugly */
1118                    NULL);
1119   g_slist_free (obscuring_windows);
1120 
1121   /* Sort the list.  FIXME: Should I bother with this sorting?  I just
1122    * sort again later in cache_edges() anyway...
1123    */
1124   edges = g_list_sort (edges, meta_rectangle_edge_cmp);
1125 
1126   /*
1127    * 5th: Cache the combination of these edges with the onscreen and
1128    * monitor edges in an array for quick access.  Free the edges since
1129    * they've been cached elsewhere.
1130    */
1131   cache_edges (display,
1132                edges,
1133                display->grab_screen->active_workspace->monitor_edges,
1134                display->grab_screen->active_workspace->screen_edges);
1135   g_list_free (edges);
1136 
1137   /*
1138    * 6th: Initialize the resistance timeouts and buildups
1139    */
1140   initialize_grab_edge_resistance_data (display);
1141 }
1142 
1143 /* Note that old_[xy] and new_[xy] are with respect to inner positions of
1144  * the window.
1145  */
1146 LOCAL_SYMBOL void
meta_window_edge_resistance_for_move(MetaWindow * window,int old_x,int old_y,int * new_x,int * new_y,GSourceFunc timeout_func,gboolean snap,gboolean is_keyboard_op)1147 meta_window_edge_resistance_for_move (MetaWindow  *window,
1148                                       int          old_x,
1149                                       int          old_y,
1150                                       int         *new_x,
1151                                       int         *new_y,
1152                                       GSourceFunc  timeout_func,
1153                                       gboolean     snap,
1154                                       gboolean     is_keyboard_op)
1155 {
1156   MetaRectangle old_outer, proposed_outer, new_outer;
1157   gboolean is_resize;
1158 
1159   meta_window_get_outer_rect (window, &old_outer);
1160 
1161   proposed_outer = old_outer;
1162   proposed_outer.x += (*new_x - old_x);
1163   proposed_outer.y += (*new_y - old_y);
1164   new_outer = proposed_outer;
1165 
1166   window->display->grab_last_user_action_was_snap = snap;
1167   is_resize = FALSE;
1168   if (apply_edge_resistance_to_each_side (window->display,
1169                                           window,
1170                                           &old_outer,
1171                                           &new_outer,
1172                                           timeout_func,
1173                                           snap,
1174                                           is_keyboard_op,
1175                                           is_resize))
1176     {
1177       /* apply_edge_resistance_to_each_side independently applies
1178        * resistance to both the right and left edges of new_outer as both
1179        * could meet areas of resistance.  But we don't want a resize, so we
1180        * just have both edges move according to the stricter of the
1181        * resistances.  Same thing goes for top & bottom edges.
1182        */
1183       MetaRectangle *reference;
1184       int left_change, right_change, smaller_x_change;
1185       int top_change, bottom_change, smaller_y_change;
1186 
1187       if (snap && !is_keyboard_op)
1188         reference = &proposed_outer;
1189       else
1190         reference = &old_outer;
1191 
1192       left_change  = BOX_LEFT (new_outer)  - BOX_LEFT (*reference);
1193       right_change = BOX_RIGHT (new_outer) - BOX_RIGHT (*reference);
1194       if (     snap && is_keyboard_op && left_change == 0)
1195         smaller_x_change = right_change;
1196       else if (snap && is_keyboard_op && right_change == 0)
1197         smaller_x_change = left_change;
1198       else if (ABS (left_change) < ABS (right_change))
1199         smaller_x_change = left_change;
1200       else
1201         smaller_x_change = right_change;
1202 
1203       top_change    = BOX_TOP (new_outer)    - BOX_TOP (*reference);
1204       bottom_change = BOX_BOTTOM (new_outer) - BOX_BOTTOM (*reference);
1205       if (     snap && is_keyboard_op && top_change == 0)
1206         smaller_y_change = bottom_change;
1207       else if (snap && is_keyboard_op && bottom_change == 0)
1208         smaller_y_change = top_change;
1209       else if (ABS (top_change) < ABS (bottom_change))
1210         smaller_y_change = top_change;
1211       else
1212         smaller_y_change = bottom_change;
1213 
1214       *new_x = old_x + smaller_x_change +
1215               (BOX_LEFT (*reference) - BOX_LEFT (old_outer));
1216       *new_y = old_y + smaller_y_change +
1217               (BOX_TOP (*reference) - BOX_TOP (old_outer));
1218 
1219       meta_topic (META_DEBUG_EDGE_RESISTANCE,
1220                   "outer x & y move-to coordinate changed from %d,%d to %d,%d\n",
1221                   proposed_outer.x, proposed_outer.y,
1222                   old_outer.x + (*new_x - old_x),
1223                   old_outer.y + (*new_y - old_y));
1224     }
1225 }
1226 
1227 /* Note that old_(width|height) and new_(width|height) are with respect to
1228  * sizes of the inner window.
1229  */
1230 LOCAL_SYMBOL void
meta_window_edge_resistance_for_resize(MetaWindow * window,int old_width,int old_height,int * new_width,int * new_height,int gravity,GSourceFunc timeout_func,gboolean snap,gboolean is_keyboard_op)1231 meta_window_edge_resistance_for_resize (MetaWindow  *window,
1232                                         int          old_width,
1233                                         int          old_height,
1234                                         int         *new_width,
1235                                         int         *new_height,
1236                                         int          gravity,
1237                                         GSourceFunc  timeout_func,
1238                                         gboolean     snap,
1239                                         gboolean     is_keyboard_op)
1240 {
1241   MetaRectangle old_outer, new_outer;
1242   int proposed_outer_width, proposed_outer_height;
1243   gboolean is_resize;
1244 
1245   meta_window_get_outer_rect (window, &old_outer);
1246   proposed_outer_width  = old_outer.width  + (*new_width  - old_width);
1247   proposed_outer_height = old_outer.height + (*new_height - old_height);
1248   meta_rectangle_resize_with_gravity (&old_outer,
1249                                       &new_outer,
1250                                       gravity,
1251                                       proposed_outer_width,
1252                                       proposed_outer_height);
1253 
1254   window->display->grab_last_user_action_was_snap = snap;
1255   is_resize = TRUE;
1256   if (apply_edge_resistance_to_each_side (window->display,
1257                                           window,
1258                                           &old_outer,
1259                                           &new_outer,
1260                                           timeout_func,
1261                                           snap,
1262                                           is_keyboard_op,
1263                                           is_resize))
1264     {
1265       *new_width  = old_width  + (new_outer.width  - old_outer.width);
1266       *new_height = old_height + (new_outer.height - old_outer.height);
1267 
1268       meta_topic (META_DEBUG_EDGE_RESISTANCE,
1269                   "outer width & height got changed from %d,%d to %d,%d\n",
1270                   proposed_outer_width, proposed_outer_height,
1271                   new_outer.width, new_outer.height);
1272     }
1273 }
1274