1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /* Marco Workspaces */
4 
5 /*
6  * Copyright (C) 2001 Havoc Pennington
7  * Copyright (C) 2003 Rob Adams
8  * Copyright (C) 2004, 2005 Elijah Newren
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation; either version 2 of the
13  * License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23  * 02110-1301, USA.
24  */
25 
26 #include <config.h>
27 #include "workspace.h"
28 #include "errors.h"
29 #include "prefs.h"
30 #include <X11/Xatom.h>
31 #include <string.h>
32 #include <canberra-gtk.h>
33 
34 void meta_workspace_queue_calc_showing   (MetaWorkspace *workspace);
35 static void set_active_space_hint        (MetaScreen *screen);
36 static void focus_ancestor_or_top_window (MetaWorkspace *workspace,
37                                           MetaWindow    *not_this_one,
38                                           guint32        timestamp);
39 static void free_this                    (gpointer candidate,
40                                           gpointer dummy);
41 static void workspace_free_struts        (MetaWorkspace *workspace);
42 
43 static void
maybe_add_to_list(MetaScreen * screen,MetaWindow * window,gpointer data)44 maybe_add_to_list (MetaScreen *screen, MetaWindow *window, gpointer data)
45 {
46   GList **mru_list = data;
47 
48   if (window->on_all_workspaces)
49     *mru_list = g_list_prepend (*mru_list, window);
50 }
51 
52 MetaWorkspace*
meta_workspace_new(MetaScreen * screen)53 meta_workspace_new (MetaScreen *screen)
54 {
55   MetaWorkspace *workspace;
56 
57   workspace = g_new (MetaWorkspace, 1);
58 
59   workspace->screen = screen;
60   workspace->screen->workspaces =
61     g_list_append (workspace->screen->workspaces, workspace);
62   workspace->windows = NULL;
63   workspace->mru_list = NULL;
64   meta_screen_foreach_window (screen, maybe_add_to_list, &workspace->mru_list);
65 
66   workspace->work_areas_invalid = TRUE;
67   workspace->work_area_xinerama = NULL;
68   workspace->work_area_screen.x = 0;
69   workspace->work_area_screen.y = 0;
70   workspace->work_area_screen.width = 0;
71   workspace->work_area_screen.height = 0;
72 
73   workspace->screen_region = NULL;
74   workspace->xinerama_region = NULL;
75   workspace->screen_edges = NULL;
76   workspace->xinerama_edges = NULL;
77   workspace->list_containing_self = g_list_prepend (NULL, workspace);
78 
79   workspace->all_struts = NULL;
80 
81   workspace->showing_desktop = FALSE;
82 
83   return workspace;
84 }
85 
86 /** Foreach function for workspace_free_struts() */
87 static void
free_this(gpointer candidate,gpointer dummy)88 free_this (gpointer candidate, gpointer dummy)
89 {
90   g_free (candidate);
91 }
92 
93 /**
94  * Frees the struts list of a workspace.
95  *
96  * \param workspace  The workspace.
97  */
98 static void
workspace_free_struts(MetaWorkspace * workspace)99 workspace_free_struts (MetaWorkspace *workspace)
100 {
101   if (workspace->all_struts == NULL)
102     return;
103 
104   g_slist_foreach (workspace->all_struts, free_this, NULL);
105   g_slist_free (workspace->all_struts);
106   workspace->all_struts = NULL;
107 }
108 
109 void
meta_workspace_free(MetaWorkspace * workspace)110 meta_workspace_free (MetaWorkspace *workspace)
111 {
112   GList *tmp;
113   MetaScreen *screen;
114   int i;
115 
116   g_return_if_fail (workspace != workspace->screen->active_workspace);
117 
118   /* Here we assume all the windows are already on another workspace
119    * as well, so they won't be "orphaned"
120    */
121 
122   tmp = workspace->windows;
123   while (tmp != NULL)
124     {
125       GList *next;
126       MetaWindow *window = tmp->data;
127       next = tmp->next;
128 
129       /* pop front of list we're iterating over */
130       meta_workspace_remove_window (workspace, window);
131       g_assert (window->workspace != NULL);
132 
133       tmp = next;
134     }
135 
136   g_assert (workspace->windows == NULL);
137 
138   screen = workspace->screen;
139 
140   workspace->screen->workspaces =
141     g_list_remove (workspace->screen->workspaces, workspace);
142 
143   g_free (workspace->work_area_xinerama);
144 
145   g_list_free (workspace->mru_list);
146   g_list_free (workspace->list_containing_self);
147 
148   /* screen.c:update_num_workspaces(), which calls us, removes windows from
149    * workspaces first, which can cause the workareas on the workspace to be
150    * invalidated (and hence for struts/regions/edges to be freed).
151    * So, no point trying to double free it; that causes a crash
152    * anyway.  #361804.
153    */
154 
155   if (!workspace->work_areas_invalid)
156     {
157       workspace_free_struts (workspace);
158       for (i = 0; i < screen->n_xinerama_infos; i++)
159         g_list_free_full (workspace->xinerama_region[i], g_free);
160       g_free (workspace->xinerama_region);
161       g_list_free_full (workspace->screen_region, g_free);
162       g_list_free_full (workspace->screen_edges, g_free);
163       g_list_free_full (workspace->xinerama_edges, g_free);
164     }
165 
166   g_free (workspace);
167 
168   /* don't bother to reset names, pagers can just ignore
169    * extra ones
170    */
171 }
172 
173 void
meta_workspace_add_window(MetaWorkspace * workspace,MetaWindow * window)174 meta_workspace_add_window (MetaWorkspace *workspace,
175                            MetaWindow    *window)
176 {
177   g_return_if_fail (window->workspace == NULL);
178 
179   /* If the window is on all workspaces, we want to add it to all mru
180    * lists, otherwise just add it to this workspaces mru list
181    */
182   if (window->on_all_workspaces)
183     {
184       if (window->workspace == NULL)
185         {
186           GList* tmp = window->screen->workspaces;
187           while (tmp)
188             {
189               MetaWorkspace* work = (MetaWorkspace*) tmp->data;
190               if (!g_list_find (work->mru_list, window))
191                 work->mru_list = g_list_prepend (work->mru_list, window);
192 
193               tmp = tmp->next;
194             }
195         }
196     }
197   else
198     {
199       g_assert (g_list_find (workspace->mru_list, window) == NULL);
200       workspace->mru_list = g_list_prepend (workspace->mru_list, window);
201     }
202 
203   workspace->windows = g_list_prepend (workspace->windows, window);
204   window->workspace = workspace;
205 
206   meta_window_set_current_workspace_hint (window);
207 
208   if (window->struts)
209     {
210       meta_topic (META_DEBUG_WORKAREA,
211                   "Invalidating work area of workspace %d since we're adding window %s to it\n",
212                   meta_workspace_index (workspace), window->desc);
213       meta_workspace_invalidate_work_area (workspace);
214     }
215 
216   /* queue a move_resize since changing workspaces may change
217    * the relevant struts
218    */
219   meta_window_queue (window, META_QUEUE_CALC_SHOWING|META_QUEUE_MOVE_RESIZE);
220 }
221 
222 void
meta_workspace_remove_window(MetaWorkspace * workspace,MetaWindow * window)223 meta_workspace_remove_window (MetaWorkspace *workspace,
224                               MetaWindow    *window)
225 {
226   g_return_if_fail (window->workspace == workspace);
227 
228   workspace->windows = g_list_remove (workspace->windows, window);
229   window->workspace = NULL;
230 
231   /* If the window is on all workspaces, we don't want to remove it
232    * from the MRU list unless this causes it to be removed from all
233    * workspaces
234    */
235   if (window->on_all_workspaces)
236     {
237       GList* tmp = window->screen->workspaces;
238       while (tmp)
239         {
240           MetaWorkspace* work = (MetaWorkspace*) tmp->data;
241           work->mru_list = g_list_remove (work->mru_list, window);
242 
243           tmp = tmp->next;
244         }
245     }
246   else
247     {
248       workspace->mru_list = g_list_remove (workspace->mru_list, window);
249       g_assert (g_list_find (workspace->mru_list, window) == NULL);
250     }
251 
252   meta_window_set_current_workspace_hint (window);
253 
254   if (window->struts)
255     {
256       meta_topic (META_DEBUG_WORKAREA,
257                   "Invalidating work area of workspace %d since we're removing window %s from it\n",
258                   meta_workspace_index (workspace), window->desc);
259       meta_workspace_invalidate_work_area (workspace);
260     }
261 
262   /* queue a move_resize since changing workspaces may change
263    * the relevant struts
264    */
265   meta_window_queue (window, META_QUEUE_CALC_SHOWING|META_QUEUE_MOVE_RESIZE);
266 }
267 
268 void
meta_workspace_relocate_windows(MetaWorkspace * workspace,MetaWorkspace * new_home)269 meta_workspace_relocate_windows (MetaWorkspace *workspace,
270                                  MetaWorkspace *new_home)
271 {
272   GList *tmp;
273   GList *copy;
274 
275   g_return_if_fail (workspace != new_home);
276 
277   /* can't modify list we're iterating over */
278   copy = g_list_copy (workspace->windows);
279 
280   tmp = copy;
281   while (tmp != NULL)
282     {
283       MetaWindow *window = tmp->data;
284 
285       meta_workspace_remove_window (workspace, window);
286       meta_workspace_add_window (new_home, window);
287 
288       tmp = tmp->next;
289     }
290 
291   g_list_free (copy);
292 
293   g_assert (workspace->windows == NULL);
294 }
295 
296 void
meta_workspace_queue_calc_showing(MetaWorkspace * workspace)297 meta_workspace_queue_calc_showing  (MetaWorkspace *workspace)
298 {
299   GList *tmp;
300 
301   tmp = workspace->windows;
302   while (tmp != NULL)
303     {
304       meta_window_queue (tmp->data, META_QUEUE_CALC_SHOWING);
305 
306       tmp = tmp->next;
307     }
308 }
309 
workspace_switch_sound(MetaWorkspace * from,MetaWorkspace * to)310 static void workspace_switch_sound(MetaWorkspace *from,
311                                    MetaWorkspace *to) {
312 
313   MetaWorkspaceLayout layout;
314   int i, nw, x, y, fi, ti;
315   const char *e;
316 
317   nw = meta_screen_get_n_workspaces(from->screen);
318   fi = meta_workspace_index(from);
319   ti = meta_workspace_index(to);
320 
321   meta_screen_calc_workspace_layout(from->screen,
322                                     nw,
323                                     fi,
324                                     &layout);
325 
326   for (i = 0; i < nw; i++)
327     if (layout.grid[i] == ti)
328       break;
329 
330   if (i >= nw) {
331     meta_bug("Failed to find destination workspace in layout\n");
332     goto finish;
333   }
334 
335   y = i / layout.cols;
336   x = i % layout.cols;
337 
338   /* We priorize horizontal over vertical movements here. The
339      rationale for this is that horizontal movements are probably more
340      interesting for sound effects because speakers are usually
341      positioned on a horizontal and not a vertical axis. i.e. your
342      spatial "Woosh!" effects will easily be able to encode horizontal
343      movement but not such much vertical movement. */
344 
345   if (x < layout.current_col)
346     e = "desktop-switch-left";
347   else if (x > layout.current_col)
348     e = "desktop-switch-right";
349   else if (y < layout.current_row)
350     e = "desktop-switch-up";
351   else if (y > layout.current_row)
352     e = "desktop-switch-down";
353   else {
354     meta_bug("Uh, origin and destination workspace at same logic position!\n");
355     goto finish;
356   }
357 
358   ca_context_play(ca_gtk_context_get(), 1,
359                   CA_PROP_EVENT_ID, e,
360                   CA_PROP_EVENT_DESCRIPTION, "Desktop switched",
361                   CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
362                   NULL);
363 
364  finish:
365   meta_screen_free_workspace_layout (&layout);
366 }
367 
368 void
meta_workspace_activate_with_focus(MetaWorkspace * workspace,MetaWindow * focus_this,guint32 timestamp)369 meta_workspace_activate_with_focus (MetaWorkspace *workspace,
370                                     MetaWindow    *focus_this,
371                                     guint32        timestamp)
372 {
373   MetaWorkspace *old;
374   MetaWindow *move_window;
375 
376   meta_verbose ("Activating workspace %d\n",
377                 meta_workspace_index (workspace));
378 
379   if (workspace->screen->active_workspace == workspace)
380     return;
381 
382   if (workspace->screen->active_workspace)
383     workspace_switch_sound(workspace->screen->active_workspace, workspace);
384 
385   /* Free any cached pointers to the workspaces's edges from
386    * a current resize or move operation */
387   meta_display_cleanup_edges (workspace->screen->display);
388 
389   /* Note that old can be NULL; e.g. when starting up */
390   old = workspace->screen->active_workspace;
391 
392   /* Save old workspace, to be able to switch back. */
393   workspace->screen->prev_workspace = old;
394 
395   workspace->screen->active_workspace = workspace;
396 
397   set_active_space_hint (workspace->screen);
398 
399   /* If the "show desktop" mode is active for either the old workspace
400    * or the new one *but not both*, then update the
401    * _net_showing_desktop hint
402    */
403   if (old && (old->showing_desktop ^ workspace->showing_desktop))
404     meta_screen_update_showing_desktop_hint (workspace->screen);
405 
406   if (old == NULL)
407     return;
408 
409   move_window = NULL;
410   if (workspace->screen->display->grab_op == META_GRAB_OP_MOVING ||
411       workspace->screen->display->grab_op == META_GRAB_OP_KEYBOARD_MOVING)
412     move_window = workspace->screen->display->grab_window;
413 
414   if (move_window != NULL)
415     {
416       if (move_window->on_all_workspaces)
417         move_window = NULL; /* don't move it after all */
418 
419       /* We put the window on the new workspace, flip spaces,
420        * then remove from old workspace, so the window
421        * never gets unmapped and we maintain the button grab
422        * on it.
423        *
424        * \bug  This comment appears to be the reverse of what happens
425        */
426       if (move_window && (move_window->workspace != workspace))
427         {
428           meta_workspace_remove_window (old, move_window);
429           meta_workspace_add_window (workspace, move_window);
430         }
431     }
432 
433   meta_workspace_queue_calc_showing (old);
434   meta_workspace_queue_calc_showing (workspace);
435 
436   /* FIXME: Why do we need this?!?  Isn't it handled in the lines above? */
437   if (move_window)
438       /* Removes window from other spaces */
439       meta_window_change_workspace (move_window, workspace);
440 
441   if (focus_this)
442     {
443       meta_window_focus (focus_this, timestamp);
444       meta_window_raise (focus_this);
445     }
446   else if (move_window)
447     {
448       meta_window_raise (move_window);
449     }
450   else
451     {
452       meta_topic (META_DEBUG_FOCUS, "Focusing default window on new workspace\n");
453       meta_workspace_focus_default_window (workspace, NULL, timestamp);
454     }
455 }
456 
457 void
meta_workspace_activate(MetaWorkspace * workspace,guint32 timestamp)458 meta_workspace_activate (MetaWorkspace *workspace,
459                          guint32        timestamp)
460 {
461   meta_workspace_activate_with_focus (workspace, NULL, timestamp);
462 }
463 
464 int
meta_workspace_index(MetaWorkspace * workspace)465 meta_workspace_index (MetaWorkspace *workspace)
466 {
467   int ret;
468 
469   ret = g_list_index (workspace->screen->workspaces, workspace);
470 
471   if (ret < 0)
472     meta_bug ("Workspace does not exist to index!\n");
473 
474   return ret;
475 }
476 
477 /* get windows contained on workspace, including workspace->windows
478  * and also sticky windows.
479  */
480 GList*
meta_workspace_list_windows(MetaWorkspace * workspace)481 meta_workspace_list_windows (MetaWorkspace *workspace)
482 {
483   GSList *display_windows;
484   GSList *tmp;
485   GList *workspace_windows;
486 
487   display_windows = meta_display_list_windows (workspace->screen->display);
488 
489   workspace_windows = NULL;
490   tmp = display_windows;
491   while (tmp != NULL)
492     {
493       MetaWindow *window = tmp->data;
494 
495       if (meta_window_located_on_workspace (window, workspace))
496         workspace_windows = g_list_prepend (workspace_windows,
497                                             window);
498 
499       tmp = tmp->next;
500     }
501 
502   g_slist_free (display_windows);
503 
504   return workspace_windows;
505 }
506 
507 static void
set_active_space_hint(MetaScreen * screen)508 set_active_space_hint (MetaScreen *screen)
509 {
510   unsigned long data[1];
511 
512   /* this is because we destroy the spaces in order,
513    * so we always end up setting a current desktop of
514    * 0 when closing a screen, so lose the current desktop
515    * on restart. By doing this we keep the current
516    * desktop on restart.
517    */
518   if (screen->closing > 0)
519     return;
520 
521   data[0] = meta_workspace_index (screen->active_workspace);
522 
523   meta_verbose ("Setting _NET_CURRENT_DESKTOP to %lu\n", data[0]);
524 
525   meta_error_trap_push (screen->display);
526   XChangeProperty (screen->display->xdisplay, screen->xroot,
527                    screen->display->atom__NET_CURRENT_DESKTOP,
528                    XA_CARDINAL,
529                    32, PropModeReplace, (guchar*) data, 1);
530   meta_error_trap_pop (screen->display, FALSE);
531 }
532 
533 void
meta_workspace_invalidate_work_area(MetaWorkspace * workspace)534 meta_workspace_invalidate_work_area (MetaWorkspace *workspace)
535 {
536   GList *tmp;
537   GList *windows;
538   int i;
539 
540   if (workspace->work_areas_invalid)
541     {
542       meta_topic (META_DEBUG_WORKAREA,
543                   "Work area for workspace %d is already invalid\n",
544                   meta_workspace_index (workspace));
545       return;
546     }
547 
548   meta_topic (META_DEBUG_WORKAREA,
549               "Invalidating work area for workspace %d\n",
550               meta_workspace_index (workspace));
551 
552   /* If we are in the middle of a resize or move operation, we
553    * might have cached pointers to the workspace's edges */
554   if (workspace == workspace->screen->active_workspace)
555     meta_display_cleanup_edges (workspace->screen->display);
556 
557   g_free (workspace->work_area_xinerama);
558   workspace->work_area_xinerama = NULL;
559 
560   workspace_free_struts (workspace);
561 
562   for (i = 0; i < workspace->screen->n_xinerama_infos; i++)
563     g_list_free_full (workspace->xinerama_region[i], g_free);
564   g_free (workspace->xinerama_region);
565   g_list_free_full (workspace->screen_region, g_free);
566   g_list_free_full (workspace->screen_edges, g_free);
567   g_list_free_full (workspace->xinerama_edges, g_free);
568   workspace->xinerama_region = NULL;
569   workspace->screen_region = NULL;
570   workspace->screen_edges = NULL;
571   workspace->xinerama_edges = NULL;
572 
573   workspace->work_areas_invalid = TRUE;
574 
575   /* redo the size/position constraints on all windows */
576   windows = meta_workspace_list_windows (workspace);
577   tmp = windows;
578   while (tmp != NULL)
579     {
580       MetaWindow *w = tmp->data;
581 
582       meta_window_queue (w, META_QUEUE_MOVE_RESIZE);
583 
584       tmp = tmp->next;
585     }
586 
587   g_list_free (windows);
588 
589   meta_screen_queue_workarea_recalc (workspace->screen);
590 }
591 
592 static void
ensure_work_areas_validated(MetaWorkspace * workspace)593 ensure_work_areas_validated (MetaWorkspace *workspace)
594 {
595   GList         *windows;
596   GList         *tmp;
597   MetaRectangle  work_area;
598   int            i;  /* C89 absolutely sucks... */
599 
600   if (!workspace->work_areas_invalid)
601     return;
602 
603   g_assert (workspace->all_struts == NULL);
604   g_assert (workspace->xinerama_region == NULL);
605   g_assert (workspace->screen_region == NULL);
606   g_assert (workspace->screen_edges == NULL);
607   g_assert (workspace->xinerama_edges == NULL);
608 
609   /* STEP 1: Get the list of struts */
610   windows = meta_workspace_list_windows (workspace);
611   for (tmp = windows; tmp != NULL; tmp = tmp->next)
612     {
613       MetaWindow *win = tmp->data;
614       GSList *s_iter;
615 
616       for (s_iter = win->struts; s_iter != NULL; s_iter = s_iter->next) {
617         MetaStrut *cpy = g_new (MetaStrut, 1);
618         *cpy = *((MetaStrut *)s_iter->data);
619         workspace->all_struts = g_slist_prepend (workspace->all_struts,
620                                                  cpy);
621       }
622     }
623   g_list_free (windows);
624 
625   /* STEP 2: Get the maximal/spanning rects for the onscreen and
626    *         on-single-xinerama regions
627    */
628   g_assert (workspace->xinerama_region == NULL);
629   g_assert (workspace->screen_region   == NULL);
630 
631   workspace->xinerama_region = g_new (GList*,
632                                       workspace->screen->n_xinerama_infos);
633   for (i = 0; i < workspace->screen->n_xinerama_infos; i++)
634     {
635       workspace->xinerama_region[i] =
636         meta_rectangle_get_minimal_spanning_set_for_region (
637           &workspace->screen->xinerama_infos[i].rect,
638           workspace->all_struts,
639           FALSE);
640     }
641   workspace->screen_region =
642     meta_rectangle_get_minimal_spanning_set_for_region (
643       &workspace->screen->rect,
644       workspace->all_struts,
645       TRUE);
646 
647   /* STEP 3: Get the work areas (region-to-maximize-to) for the screen and
648    *         xineramas.
649    */
650   work_area = workspace->screen->rect;  /* start with the screen */
651   if (workspace->screen_region == NULL)
652     work_area = meta_rect (0, 0, -1, -1);
653   else
654     meta_rectangle_clip_to_region (workspace->screen_region,
655                                    FIXED_DIRECTION_NONE,
656                                    &work_area);
657 
658   /* Lots of paranoia checks, forcing work_area_screen to be sane */
659 #define MIN_SANE_AREA 100
660   if (work_area.width < MIN_SANE_AREA)
661     {
662       meta_warning ("struts occupy an unusually large percentage of the screen; "
663                     "available remaining width = %d < %d",
664                     work_area.width, MIN_SANE_AREA);
665       if (work_area.width < 1)
666         {
667           work_area.x = (workspace->screen->rect.width - MIN_SANE_AREA)/2;
668           work_area.width = MIN_SANE_AREA;
669         }
670       else
671         {
672           int amount = (MIN_SANE_AREA - work_area.width)/2;
673           work_area.x     -=   amount;
674           work_area.width += 2*amount;
675         }
676     }
677   if (work_area.height < MIN_SANE_AREA)
678     {
679       meta_warning ("struts occupy an unusually large percentage of the screen; "
680                     "available remaining height = %d < %d",
681                     work_area.height, MIN_SANE_AREA);
682       if (work_area.height < 1)
683         {
684           work_area.y = (workspace->screen->rect.height - MIN_SANE_AREA)/2;
685           work_area.height = MIN_SANE_AREA;
686         }
687       else
688         {
689           int amount = (MIN_SANE_AREA - work_area.height)/2;
690           work_area.y      -=   amount;
691           work_area.height += 2*amount;
692         }
693     }
694   workspace->work_area_screen = work_area;
695   meta_topic (META_DEBUG_WORKAREA,
696               "Computed work area for workspace %d: %d,%d %d x %d\n",
697               meta_workspace_index (workspace),
698               workspace->work_area_screen.x,
699               workspace->work_area_screen.y,
700               workspace->work_area_screen.width,
701               workspace->work_area_screen.height);
702 
703   /* Now find the work areas for each xinerama */
704   g_free (workspace->work_area_xinerama);
705   workspace->work_area_xinerama = g_new (MetaRectangle,
706                                          workspace->screen->n_xinerama_infos);
707 
708   for (i = 0; i < workspace->screen->n_xinerama_infos; i++)
709     {
710       work_area = workspace->screen->xinerama_infos[i].rect;
711 
712       if (workspace->xinerama_region[i] == NULL)
713         /* FIXME: constraints.c untested with this, but it might be nice for
714          * a screen reader or magnifier.
715          */
716         work_area = meta_rect (work_area.x, work_area.y, -1, -1);
717       else
718         meta_rectangle_clip_to_region (workspace->xinerama_region[i],
719                                        FIXED_DIRECTION_NONE,
720                                        &work_area);
721 
722       workspace->work_area_xinerama[i] = work_area;
723       meta_topic (META_DEBUG_WORKAREA,
724                   "Computed work area for workspace %d "
725                   "xinerama %d: %d,%d %d x %d\n",
726                   meta_workspace_index (workspace),
727                   i,
728                   workspace->work_area_xinerama[i].x,
729                   workspace->work_area_xinerama[i].y,
730                   workspace->work_area_xinerama[i].width,
731                   workspace->work_area_xinerama[i].height);
732     }
733 
734   /* STEP 4: Make sure the screen_region is nonempty (separate from step 2
735    *         since it relies on step 3).
736    */
737   if (workspace->screen_region == NULL)
738     {
739       MetaRectangle *nonempty_region;
740       nonempty_region = g_new (MetaRectangle, 1);
741       *nonempty_region = workspace->work_area_screen;
742       workspace->screen_region = g_list_prepend (NULL, nonempty_region);
743     }
744 
745   /* STEP 5: Cache screen and xinerama edges for edge resistance and snapping */
746   g_assert (workspace->screen_edges    == NULL);
747   g_assert (workspace->xinerama_edges  == NULL);
748   workspace->screen_edges =
749     meta_rectangle_find_onscreen_edges (&workspace->screen->rect,
750                                         workspace->all_struts);
751   tmp = NULL;
752   for (i = 0; i < workspace->screen->n_xinerama_infos; i++)
753     tmp = g_list_prepend (tmp, &workspace->screen->xinerama_infos[i].rect);
754   workspace->xinerama_edges =
755     meta_rectangle_find_nonintersected_xinerama_edges (&workspace->screen->rect, tmp, workspace->all_struts);
756   g_list_free (tmp);
757 
758   /* We're all done, YAAY!  Record that everything has been validated. */
759   workspace->work_areas_invalid = FALSE;
760 }
761 
762 void
meta_workspace_get_work_area_for_xinerama(MetaWorkspace * workspace,int which_xinerama,MetaRectangle * area)763 meta_workspace_get_work_area_for_xinerama (MetaWorkspace *workspace,
764                                            int            which_xinerama,
765                                            MetaRectangle *area)
766 {
767   g_assert (which_xinerama >= 0);
768 
769   ensure_work_areas_validated (workspace);
770   g_assert (which_xinerama < workspace->screen->n_xinerama_infos);
771 
772   *area = workspace->work_area_xinerama[which_xinerama];
773 }
774 
775 void
meta_workspace_get_work_area_all_xineramas(MetaWorkspace * workspace,MetaRectangle * area)776 meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace,
777                                             MetaRectangle *area)
778 {
779   ensure_work_areas_validated (workspace);
780 
781   *area = workspace->work_area_screen;
782 }
783 
784 GList*
meta_workspace_get_onscreen_region(MetaWorkspace * workspace)785 meta_workspace_get_onscreen_region (MetaWorkspace *workspace)
786 {
787   ensure_work_areas_validated (workspace);
788 
789   return workspace->screen_region;
790 }
791 
792 GList*
meta_workspace_get_onxinerama_region(MetaWorkspace * workspace,int which_xinerama)793 meta_workspace_get_onxinerama_region (MetaWorkspace *workspace,
794                                       int            which_xinerama)
795 {
796   ensure_work_areas_validated (workspace);
797 
798   return workspace->xinerama_region[which_xinerama];
799 }
800 
801 #ifdef WITH_VERBOSE_MODE
802 static char *
meta_motion_direction_to_string(MetaMotionDirection direction)803 meta_motion_direction_to_string (MetaMotionDirection direction)
804 {
805   switch (direction)
806     {
807     case META_MOTION_UP:
808       return "Up";
809     case META_MOTION_DOWN:
810       return "Down";
811     case META_MOTION_LEFT:
812       return "Left";
813     case META_MOTION_RIGHT:
814       return "Right";
815     case META_MOTION_PREV:
816       return "Previous";
817     }
818 
819   return "Unknown";
820 }
821 #endif /* WITH_VERBOSE_MODE */
822 
823 MetaWorkspace*
meta_workspace_get_neighbor(MetaWorkspace * workspace,MetaMotionDirection direction)824 meta_workspace_get_neighbor (MetaWorkspace      *workspace,
825                              MetaMotionDirection direction)
826 {
827   MetaWorkspaceLayout layout;
828   int i, current_space, num_workspaces;
829   gboolean ltr;
830   MetaWrapStyle wrap;
831 
832   current_space = meta_workspace_index (workspace);
833   num_workspaces = meta_screen_get_n_workspaces (workspace->screen);
834   meta_screen_calc_workspace_layout (workspace->screen, num_workspaces,
835                                      current_space, &layout);
836   wrap = meta_prefs_get_wrap_style();
837 
838   meta_verbose ("Getting neighbor of %d in direction %s\n",
839                 current_space, meta_motion_direction_to_string (direction));
840 
841   ltr = meta_ui_get_direction() == META_UI_DIRECTION_LTR;
842 
843   switch (direction)
844     {
845     case META_MOTION_LEFT:
846       layout.current_col -= ltr ? 1 : -1;
847       break;
848     case META_MOTION_RIGHT:
849       layout.current_col += ltr ? 1 : -1;
850       break;
851     case META_MOTION_UP:
852       layout.current_row -= 1;
853       break;
854     case META_MOTION_DOWN:
855       layout.current_row += 1;
856       break;
857     case META_MOTION_PREV:
858       break;
859     }
860 
861   /* LEFT */
862   if (layout.current_col < 0)
863     switch (wrap)
864       {
865       case META_WRAP_NONE:
866         layout.current_col = 0;
867         break;
868       case META_WRAP_CLASSIC:
869         layout.current_row = layout.current_row > 0 ? layout.current_row - 1 : layout.rows - 1;
870         /* fall through */
871       case META_WRAP_TOROIDAL:
872         layout.current_col = layout.cols - 1;
873       }
874   /* RIGHT */
875   if (layout.current_col >= layout.cols)
876     switch (wrap)
877       {
878       case META_WRAP_NONE:
879         layout.current_col = layout.cols - 1;
880         break;
881       case META_WRAP_CLASSIC:
882         layout.current_row = layout.current_row < layout.rows - 1 ? layout.current_row + 1 : 0;
883         /* fall through */
884       case META_WRAP_TOROIDAL:
885         layout.current_col = 0;
886       }
887   /* UP */
888   if (layout.current_row < 0)
889     switch (wrap)
890       {
891       case META_WRAP_NONE:
892         layout.current_row = 0;
893         break;
894       case META_WRAP_CLASSIC:
895         layout.current_col = layout.current_col > 0 ? layout.current_col - 1 : layout.cols - 1;
896         /* fall through */
897       case META_WRAP_TOROIDAL:
898         layout.current_row = layout.rows - 1;
899       }
900   /* DOWN */
901   if (layout.current_row >= layout.rows)
902     switch (wrap)
903       {
904       case META_WRAP_NONE:
905         layout.current_row = layout.rows - 1;
906         break;
907       case META_WRAP_CLASSIC:
908         layout.current_col = layout.current_col < layout.cols - 1 ? layout.current_col + 1 : 0;
909         /* fall through */
910       case META_WRAP_TOROIDAL:
911         layout.current_row = 0;
912       }
913 
914   /* If we have an uneven arrangement of workspaces, (layout.cols - n, layout.rows - 1) may be an invalid workspace
915      e.g. we have 7 workspaces on a 3x3 pane */
916   if (wrap != META_WRAP_NONE && (layout.current_row * layout.cols + layout.current_col >= num_workspaces))
917     switch (direction)
918       {
919       case META_MOTION_LEFT:
920         layout.current_col = num_workspaces - (layout.current_row * layout.cols + 1);
921         break;
922       case META_MOTION_RIGHT:
923        layout.current_col = 0;
924        if (wrap == META_WRAP_CLASSIC)
925          layout.current_row = 0;
926        break;
927       case META_MOTION_UP:
928         layout.current_row -= 1;
929         break;
930       case META_MOTION_DOWN:
931         layout.current_row = 0;
932         if (wrap == META_WRAP_CLASSIC)
933 	  layout.current_col = layout.current_col < layout.cols - 1 ? layout.current_col + 1 : 0;
934         break;
935       case META_MOTION_PREV:
936         break;
937       }
938 
939   i = layout.grid[layout.current_row * layout.cols + layout.current_col];
940 
941   if (i < 0)
942     i = current_space;
943 
944   if (i >= num_workspaces)
945     meta_bug ("calc_workspace_layout left an invalid (too-high) workspace number %d in the grid\n",
946               i);
947 
948   meta_verbose ("Neighbor workspace is %d at row %d col %d\n",
949                 i, layout.current_row, layout.current_col);
950 
951   meta_screen_free_workspace_layout (&layout);
952 
953   return meta_screen_get_workspace_by_index (workspace->screen, i);
954 }
955 
956 const char*
meta_workspace_get_name(MetaWorkspace * workspace)957 meta_workspace_get_name (MetaWorkspace *workspace)
958 {
959   return meta_prefs_get_workspace_name (meta_workspace_index (workspace));
960 }
961 
962 void
meta_workspace_focus_default_window(MetaWorkspace * workspace,MetaWindow * not_this_one,guint32 timestamp)963 meta_workspace_focus_default_window (MetaWorkspace *workspace,
964                                      MetaWindow    *not_this_one,
965                                      guint32        timestamp)
966 {
967   if (timestamp == CurrentTime)
968     {
969       meta_warning ("CurrentTime used to choose focus window; "
970                     "focus window may not be correct.\n");
971     }
972 
973   if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK ||
974       !workspace->screen->display->mouse_mode)
975     focus_ancestor_or_top_window (workspace, not_this_one, timestamp);
976   else
977     {
978       MetaWindow * window;
979       window = meta_screen_get_mouse_window (workspace->screen, not_this_one);
980       if (window &&
981           window->type != META_WINDOW_DOCK &&
982           window->type != META_WINDOW_DESKTOP)
983         {
984           if (timestamp == CurrentTime)
985             {
986 
987               /* We would like for this to never happen.  However, if
988                * it does happen then we kludge since using CurrentTime
989                * can mean ugly race conditions--and we can avoid these
990                * by allowing EnterNotify events (which come with
991                * timestamps) to handle focus.
992                */
993 
994               meta_topic (META_DEBUG_FOCUS,
995                           "Not focusing mouse window %s because EnterNotify events should handle that\n", window->desc);
996             }
997           else
998             {
999               meta_topic (META_DEBUG_FOCUS,
1000                           "Focusing mouse window %s\n", window->desc);
1001               meta_window_focus (window, timestamp);
1002             }
1003 
1004           if (workspace->screen->display->autoraise_window != window &&
1005               meta_prefs_get_auto_raise ())
1006             {
1007               meta_display_queue_autoraise_callback (workspace->screen->display,
1008                                                      window);
1009             }
1010         }
1011       else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_SLOPPY)
1012         focus_ancestor_or_top_window (workspace, not_this_one, timestamp);
1013       else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_MOUSE)
1014         {
1015           meta_topic (META_DEBUG_FOCUS,
1016                       "Setting focus to no_focus_window, since no valid "
1017                       "window to focus found.\n");
1018           meta_display_focus_the_no_focus_window (workspace->screen->display,
1019                                                   workspace->screen,
1020                                                   timestamp);
1021         }
1022     }
1023 }
1024 
1025 static gboolean
record_ancestor(MetaWindow * window,void * data)1026 record_ancestor (MetaWindow *window,
1027                  void       *data)
1028 {
1029   MetaWindow **result = data;
1030 
1031   *result = window;
1032   return FALSE; /* quit with the first ancestor we find */
1033 }
1034 
1035 /* Focus ancestor of not_this_one if there is one, otherwise focus the MRU
1036  * window on active workspace
1037  */
1038 static void
focus_ancestor_or_top_window(MetaWorkspace * workspace,MetaWindow * not_this_one,guint32 timestamp)1039 focus_ancestor_or_top_window (MetaWorkspace *workspace,
1040                               MetaWindow    *not_this_one,
1041                               guint32        timestamp)
1042 {
1043   MetaWindow *window = NULL;
1044 
1045   if (not_this_one)
1046     meta_topic (META_DEBUG_FOCUS,
1047                 "Focusing MRU window excluding %s\n", not_this_one->desc);
1048   else
1049     meta_topic (META_DEBUG_FOCUS,
1050                 "Focusing MRU window\n");
1051 
1052   /* First, check to see if we need to focus an ancestor of a window */
1053   if (not_this_one)
1054     {
1055       MetaWindow *ancestor;
1056       ancestor = NULL;
1057       meta_window_foreach_ancestor (not_this_one, record_ancestor, &ancestor);
1058       if (ancestor != NULL)
1059         {
1060           meta_topic (META_DEBUG_FOCUS,
1061                       "Focusing %s, ancestor of %s\n",
1062                       ancestor->desc, not_this_one->desc);
1063 
1064           meta_window_focus (ancestor, timestamp);
1065 
1066           /* Also raise the window if in click-to-focus */
1067           if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK)
1068             meta_window_raise (ancestor);
1069 
1070           return;
1071         }
1072     }
1073 
1074   window = meta_stack_get_default_focus_window (workspace->screen->stack,
1075                                                 workspace, NULL);
1076 
1077   if (window)
1078     {
1079       meta_topic (META_DEBUG_FOCUS,
1080                   "Focusing workspace MRU window %s\n", window->desc);
1081 
1082       meta_window_focus (window, timestamp);
1083 
1084       /* Also raise the window if in click-to-focus */
1085       if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK)
1086         meta_window_raise (window);
1087     }
1088   else
1089     {
1090       meta_topic (META_DEBUG_FOCUS, "No MRU window to focus found; focusing no_focus_window.\n");
1091       meta_display_focus_the_no_focus_window (workspace->screen->display,
1092                                               workspace->screen,
1093                                               timestamp);
1094     }
1095 }
1096