1 /*      $Id$
2 
3         This program is free software; you can redistribute it and/or modify
4         it under the terms of the GNU General Public License as published by
5         the Free Software Foundation; either version 2, or (at your option)
6         any later version.
7 
8         This program is distributed in the hope that it will be useful,
9         but WITHOUT ANY WARRANTY; without even the implied warranty of
10         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11         GNU General Public License for more details.
12 
13         You should have received a copy of the GNU General Public License
14         along with this program; if not, write to the Free Software
15         Foundation, Inc., Inc., 51 Franklin Street, Fifth Floor, Boston,
16         MA 02110-1301, USA.
17 
18 
19         oroborus - (c) 2001 Ken Lynch
20         xfwm4    - (c) 2002-2011 Olivier Fourdan
21 
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 
28 #include <X11/X.h>
29 #include <X11/Xlib.h>
30 #include <X11/Xutil.h>
31 #include <X11/Xmd.h>
32 
33 #include <glib.h>
34 #include <gdk/gdk.h>
35 #include <gtk/gtk.h>
36 #include <libxfce4util/libxfce4util.h>
37 
38 #include <common/xfwm-common.h>
39 
40 #include "display.h"
41 #include "screen.h"
42 #include "misc.h"
43 #include "transients.h"
44 #include "workspaces.h"
45 #include "settings.h"
46 #include "client.h"
47 #include "focus.h"
48 #include "stacking.h"
49 #include "hints.h"
50 
51 static void
workspaceGetPosition(ScreenInfo * screen_info,int n,int * row,int * col)52 workspaceGetPosition (ScreenInfo *screen_info, int n, int * row, int * col)
53 {
54     NetWmDesktopLayout l;
55     int major_length, minor_length, tmp;
56 
57     l = screen_info->desktop_layout;
58     if (l.orientation == NET_WM_ORIENTATION_HORZ)
59     {
60         major_length = l.cols;
61         minor_length = l.rows;
62     }
63     else
64     {
65         major_length = l.rows;
66         minor_length = l.cols;
67     }
68 
69     *row = n / major_length;
70     *col = n % major_length;
71 
72     switch (l.start)
73     {
74         case NET_WM_TOPRIGHT:
75             *col = major_length - *col - 1;
76             break;
77         case NET_WM_BOTTOMLEFT:
78             *row = minor_length - *row - 1;
79             break;
80         case NET_WM_BOTTOMRIGHT:
81             *col = major_length - *col - 1;
82             *row = minor_length - *row - 1;
83             break;
84         default:
85             break;
86     }
87 
88     if (l.orientation == NET_WM_ORIENTATION_VERT)
89     {
90         tmp = *row;
91         *row = *col;
92         *col = tmp;
93         if ((l.start == NET_WM_TOPRIGHT) || (l.start == NET_WM_BOTTOMLEFT))
94         {
95             *row = l.rows - *row - 1;
96             *col = l.cols - *col - 1;
97         }
98     }
99 }
100 
101 static gint
workspaceGetNumber(ScreenInfo * screen_info,gint row,gint col)102 workspaceGetNumber (ScreenInfo *screen_info, gint row, gint col)
103 {
104     NetWmDesktopLayout l;
105     gulong major_length, minor_length;
106     guint n, tmp;
107 
108     l = screen_info->desktop_layout;
109     if (l.orientation == NET_WM_ORIENTATION_HORZ)
110     {
111         major_length = l.cols;
112         minor_length = l.rows;
113     }
114     else
115     {
116         major_length = l.rows;
117         minor_length = l.cols;
118     }
119 
120     if (l.orientation == NET_WM_ORIENTATION_VERT)
121     {
122         tmp = row;
123         row = col;
124         col = tmp;
125         if ((l.start == NET_WM_TOPRIGHT) || (l.start == NET_WM_BOTTOMLEFT))
126         {
127             row = minor_length - row - 1;
128             col = major_length - col - 1;
129         }
130     }
131 
132     switch (l.start)
133     {
134         case NET_WM_TOPRIGHT:
135             col = major_length - col - 1;
136             break;
137         case NET_WM_BOTTOMLEFT:
138             row = minor_length - row - 1;
139             break;
140         case NET_WM_BOTTOMRIGHT:
141             col = major_length - col - 1;
142             row = minor_length - row - 1;
143             break;
144         default:
145             break;
146     }
147 
148     n = (row * major_length) + col;
149     return n;
150 }
151 
152 static int
modify_with_wrap(int value,int by,int limit,gboolean wrap)153 modify_with_wrap (int value, int by, int limit, gboolean wrap)
154 {
155     if (by >= limit) by = limit - 1;
156     value += by;
157     if (value >= limit)
158     {
159         if (!wrap)
160         {
161             value = limit - 1;
162         }
163         else
164         {
165             value = value % limit;
166         }
167     }
168     else if (value < 0)
169     {
170         if (!wrap)
171         {
172             value = 0;
173         }
174         else
175         {
176             value = (value + limit) % limit;
177         }
178     }
179     return value;
180 }
181 
182 /* returns TRUE if the workspace was changed, FALSE otherwise */
183 gboolean
workspaceMove(ScreenInfo * screen_info,gint rowmod,gint colmod,Client * c,guint32 timestamp)184 workspaceMove (ScreenInfo *screen_info, gint rowmod, gint colmod, Client * c, guint32 timestamp)
185 {
186     gint row, col, newrow, newcol, n;
187     guint previous_ws;
188 
189     g_return_val_if_fail (screen_info != NULL, FALSE);
190 
191     TRACE ("row %i, mod %i, timestamp %u", rowmod, colmod, timestamp);
192 
193     workspaceGetPosition (screen_info, screen_info->current_ws, &row, &col);
194     newrow = modify_with_wrap (row, rowmod, screen_info->desktop_layout.rows, screen_info->params->wrap_layout);
195     newcol = modify_with_wrap (col, colmod, screen_info->desktop_layout.cols, screen_info->params->wrap_layout);
196     n = workspaceGetNumber (screen_info, newrow, newcol);
197 
198     if (n == (gint) screen_info->current_ws)
199     {
200         return FALSE;
201     }
202 
203     previous_ws = screen_info->current_ws;
204     if ((n >= 0) && (n < (gint) screen_info->workspace_count))
205     {
206         workspaceSwitch (screen_info, n, c, TRUE, timestamp);
207     }
208     else if (screen_info->params->wrap_layout)
209     {
210         if (colmod < 0)
211         {
212            n = screen_info->workspace_count - 1;
213         }
214         else
215         {
216             if (colmod > 0)
217             {
218                 newcol = 0;
219             }
220             else if (rowmod > 0)
221             {
222                 newrow = 0;
223             }
224             else if (rowmod < 0)
225             {
226                 newrow--;
227             }
228             else
229             {
230                 return FALSE;
231             }
232 
233             n = workspaceGetNumber (screen_info, newrow, newcol);
234         }
235         workspaceSwitch (screen_info, n, c, TRUE, timestamp);
236     }
237 
238     return (screen_info->current_ws != previous_ws);
239 }
240 
241 void
workspaceSwitch(ScreenInfo * screen_info,gint new_ws,Client * c2,gboolean update_focus,guint32 timestamp)242 workspaceSwitch (ScreenInfo *screen_info, gint new_ws, Client * c2, gboolean update_focus, guint32 timestamp)
243 {
244     DisplayInfo *display_info;
245     Client *c, *new_focus;
246     Client *previous;
247     GList *list;
248     Window dr, window;
249     gint rx, ry, wx, wy;
250     unsigned int mask;
251 
252     g_return_if_fail (screen_info != NULL);
253 
254     TRACE ("workspace %i, timestamp %u", new_ws, timestamp);
255 
256     display_info = screen_info->display_info;
257     if ((new_ws == (gint) screen_info->current_ws) && (screen_info->params->toggle_workspaces))
258     {
259         new_ws = (gint) screen_info->previous_ws;
260     }
261 
262     if (new_ws == (gint) screen_info->current_ws)
263     {
264         return;
265     }
266 
267     if (screen_info->params->wrap_cycle)
268     {
269         if (new_ws > (gint) screen_info->workspace_count - 1)
270         {
271             new_ws = 0;
272         }
273         if (new_ws < 0)
274         {
275             new_ws = (gint) screen_info->workspace_count - 1;
276         }
277     }
278     else if ((new_ws > (gint) screen_info->workspace_count - 1) || (new_ws < 0))
279     {
280         return;
281     }
282 
283     screen_info->previous_ws = screen_info->current_ws;
284     screen_info->current_ws = new_ws;
285 
286     new_focus = NULL;
287     previous  = NULL;
288     c = clientGetFocus ();
289 
290     if (c2)
291     {
292         clientSetWorkspace (c2, new_ws, FALSE);
293     }
294 
295     if (c)
296     {
297         if (c->type & WINDOW_REGULAR_FOCUSABLE)
298         {
299             previous = c;
300         }
301         if (c2 == c)
302         {
303             new_focus = c2;
304         }
305     }
306 
307     /* First pass: Show, from top to bottom */
308     for (list = g_list_last(screen_info->windows_stack); list; list = g_list_previous (list))
309     {
310         c = (Client *) list->data;
311         if (FLAG_TEST (c->flags, CLIENT_FLAG_STICKY))
312         {
313             clientSetWorkspace (c, new_ws, TRUE);
314         }
315         else if (new_ws == (gint) c->win_workspace)
316         {
317             if (!FLAG_TEST (c->flags, CLIENT_FLAG_ICONIFIED) && !FLAG_TEST (c->xfwm_flags, XFWM_FLAG_VISIBLE))
318             {
319                 if (!clientIsTransientOrModal (c) || !clientTransientOrModalHasAncestor (c, new_ws))
320                 {
321                     clientShow (c, FALSE);
322                 }
323             }
324         }
325     }
326 
327     /* Second pass: Hide from bottom to top */
328     for (list = screen_info->windows_stack; list; list = g_list_next (list))
329     {
330         c = (Client *) list->data;
331 
332         if (new_ws != (gint) c->win_workspace)
333         {
334             if (c == previous)
335             {
336                 FLAG_SET (previous->xfwm_flags, XFWM_FLAG_FOCUS);
337                 clientSetFocus (screen_info, NULL, timestamp, FOCUS_IGNORE_MODAL);
338             }
339             if (FLAG_TEST (c->xfwm_flags, XFWM_FLAG_VISIBLE) && !FLAG_TEST (c->flags, CLIENT_FLAG_STICKY))
340             {
341                 if (!clientIsTransientOrModal (c) || !clientTransientOrModalHasAncestor (c, new_ws))
342                 {
343                     clientWithdraw (c, new_ws, FALSE);
344                 }
345             }
346         }
347     }
348 
349     /* Third pass: Check for focus, from top to bottom */
350     for (list = g_list_last(screen_info->windows_stack); list; list = g_list_previous (list))
351     {
352         c = (Client *) list->data;
353 
354         if (FLAG_TEST (c->flags, CLIENT_FLAG_STICKY))
355         {
356             if ((!new_focus) && (c == previous) && clientSelectMask (c, NULL, 0, WINDOW_REGULAR_FOCUSABLE))
357             {
358                 new_focus = c;
359             }
360             FLAG_UNSET (c->xfwm_flags, XFWM_FLAG_FOCUS);
361         }
362         else if (new_ws == (gint) c->win_workspace)
363         {
364             if ((!new_focus) && FLAG_TEST (c->xfwm_flags, XFWM_FLAG_FOCUS))
365             {
366                 new_focus = c;
367             }
368             FLAG_UNSET (c->xfwm_flags, XFWM_FLAG_FOCUS);
369         }
370     }
371 
372     setNetCurrentDesktop (display_info, screen_info->xroot, new_ws);
373     if (!(screen_info->params->click_to_focus))
374     {
375         if (!(c2) && (XQueryPointer (myScreenGetXDisplay (screen_info), screen_info->xroot, &dr, &window, &rx, &ry, &wx, &wy, &mask)))
376         {
377             c = clientAtPosition (screen_info, rx, ry, NULL);
378             if (c)
379             {
380                 new_focus = c;
381             }
382         }
383     }
384 
385     if (update_focus)
386     {
387         if (new_focus)
388         {
389             if ((screen_info->params->click_to_focus) && (screen_info->params->raise_on_click))
390             {
391                 if (!(screen_info->params->raise_on_focus) && !clientIsTopMost (new_focus))
392                 {
393                     clientRaise (new_focus, None);
394                 }
395             }
396             clientSetFocus (screen_info, new_focus, timestamp, FOCUS_SORT);
397         }
398         else
399         {
400             clientFocusTop (screen_info, WIN_LAYER_FULLSCREEN, timestamp);
401         }
402     }
403 }
404 
405 void
workspaceSetNames(ScreenInfo * screen_info,gchar ** names,int items)406 workspaceSetNames (ScreenInfo * screen_info, gchar **names, int items)
407 {
408     g_return_if_fail (screen_info != NULL);
409     g_return_if_fail (names != NULL);
410 
411     TRACE ("entering");
412 
413     if (screen_info->workspace_names)
414     {
415         g_strfreev (screen_info->workspace_names);
416     }
417 
418     screen_info->workspace_names = names;
419     screen_info->workspace_names_items = items;
420 }
421 
422 void
workspaceSetCount(ScreenInfo * screen_info,guint count)423 workspaceSetCount (ScreenInfo * screen_info, guint count)
424 {
425     DisplayInfo *display_info;
426     Client *c;
427     GList *list;
428 
429     g_return_if_fail (screen_info != NULL);
430 
431     TRACE ("count %u", count);
432 
433     if (count < 1)
434     {
435         count = 1;
436     }
437     if (count == screen_info->workspace_count)
438     {
439         return;
440     }
441 
442     display_info = screen_info->display_info;
443     setHint (display_info, screen_info->xroot, NET_NUMBER_OF_DESKTOPS, count);
444     screen_info->workspace_count = count;
445 
446     for (list = screen_info->windows_stack; list; list = g_list_next (list))
447     {
448         c = (Client *) list->data;
449         if (c->win_workspace > count - 1)
450         {
451             clientSetWorkspace (c, count - 1, TRUE);
452         }
453     }
454     if (screen_info->current_ws > count - 1)
455     {
456         workspaceSwitch (screen_info, count - 1, NULL, TRUE, myDisplayGetCurrentTime (display_info));
457     }
458     setNetWorkarea (display_info, screen_info->xroot, screen_info->workspace_count,
459                     screen_info->width, screen_info->height, screen_info->margins);
460     /* Recompute the layout based on the (changed) number of desktops */
461     getDesktopLayout (display_info, screen_info->xroot, screen_info->workspace_count,
462                      &screen_info->desktop_layout);
463 }
464 
465 void
workspaceInsert(ScreenInfo * screen_info,guint position)466 workspaceInsert (ScreenInfo * screen_info, guint position)
467 {
468     Client *c;
469     GList *list;
470     guint count;
471 
472     g_return_if_fail (screen_info != NULL);
473 
474     TRACE ("position %u", position);
475 
476     count = screen_info->workspace_count;
477     workspaceSetCount(screen_info, count + 1);
478 
479     if (position > count)
480     {
481         return;
482     }
483 
484     for (list = screen_info->windows_stack; list; list = g_list_next (list))
485     {
486         c = (Client *) list->data;
487         if (c->win_workspace >= position)
488         {
489             clientSetWorkspace (c, c->win_workspace + 1, TRUE);
490         }
491     }
492 }
493 
494 void
workspaceDelete(ScreenInfo * screen_info,guint position)495 workspaceDelete (ScreenInfo * screen_info, guint position)
496 {
497     Client *c;
498     guint i, count;
499 
500     g_return_if_fail (screen_info != NULL);
501 
502     TRACE ("position %u", position);
503 
504     count = screen_info->workspace_count;
505     if ((count < 1) || (position > count))
506     {
507         return;
508     }
509 
510     for (c = screen_info->clients, i = 0; i < screen_info->client_count; c = c->next, i++)
511     {
512         if (c->win_workspace > position)
513         {
514             clientSetWorkspace (c, c->win_workspace - 1, TRUE);
515         }
516     }
517 
518     workspaceSetCount(screen_info, count - 1);
519 }
520 
521 void
workspaceUpdateArea(ScreenInfo * screen_info)522 workspaceUpdateArea (ScreenInfo *screen_info)
523 {
524     DisplayInfo *display_info;
525     Client *c;
526     GdkRectangle top, left, right, bottom, workarea;
527     int prev_top;
528     int prev_left;
529     int prev_right;
530     int prev_bottom;
531     guint i;
532 
533 
534     g_return_if_fail (screen_info != NULL);
535     g_return_if_fail (screen_info->margins != NULL);
536     g_return_if_fail (screen_info->gnome_margins != NULL);
537 
538     TRACE ("entering");
539 
540     display_info = screen_info->display_info;
541     prev_top = screen_info->margins[STRUTS_TOP];
542     prev_left = screen_info->margins[STRUTS_LEFT];
543     prev_right = screen_info->margins[STRUTS_RIGHT];
544     prev_bottom = screen_info->margins[STRUTS_BOTTOM];
545 
546     if (!xfwm_get_primary_monitor_geometry (screen_info->gscr, &workarea, TRUE))
547     {
548         TRACE ("No monitor attached");
549         return;
550     }
551 
552     for (i = 0; i < 4; i++)
553     {
554         screen_info->margins[i] = screen_info->gnome_margins[i];
555     }
556 
557     for (c = screen_info->clients, i = 0; i < screen_info->client_count; c = c->next, i++)
558     {
559         if (strutsToRectangles (c, &left, &right, &top, &bottom))
560         {
561             /*
562              * NET_WORKAREA doesn't support L shaped displays at all.
563              * gdk works around this by ignoring it unless dealing with
564              * the primary monitor.
565              * Mimic this behaviour by ignoring struts not on the primary
566              * display when calculating NET_WORKAREA
567              */
568             if (checkValidStruts (&left, &workarea, STRUTS_LEFT) &&
569                 gdk_rectangle_intersect (&left, &workarea, NULL))
570             {
571                 screen_info->margins[STRUTS_LEFT] = MAX(screen_info->margins[STRUTS_LEFT],
572                                                          c->struts[STRUTS_LEFT]);
573             }
574 
575             if (checkValidStruts (&right, &workarea, STRUTS_RIGHT) &&
576                 gdk_rectangle_intersect (&right, &workarea, NULL))
577             {
578                 screen_info->margins[STRUTS_RIGHT] = MAX(screen_info->margins[STRUTS_RIGHT],
579                                                          c->struts[STRUTS_RIGHT]);
580             }
581 
582             if (checkValidStruts (&top, &workarea, STRUTS_TOP) &&
583                 gdk_rectangle_intersect (&top, &workarea, NULL))
584             {
585                 screen_info->margins[STRUTS_TOP] = MAX(screen_info->margins[STRUTS_TOP],
586                                                        c->struts[STRUTS_TOP]);
587             }
588 
589             if (checkValidStruts (&bottom, &workarea, STRUTS_BOTTOM) &&
590                 gdk_rectangle_intersect (&bottom, &workarea, NULL))
591             {
592                 screen_info->margins[STRUTS_BOTTOM] = MAX(screen_info->margins[STRUTS_BOTTOM],
593                                                           c->struts[STRUTS_BOTTOM]);
594             }
595         }
596     }
597 
598     if ((prev_top != screen_info->margins[STRUTS_TOP]) ||
599         (prev_left != screen_info->margins[STRUTS_LEFT]) ||
600         (prev_right != screen_info->margins[STRUTS_RIGHT]) ||
601         (prev_bottom != screen_info->margins[STRUTS_BOTTOM]))
602     {
603         TRACE ("margins have changed, updating net_workarea");
604         setNetWorkarea (display_info, screen_info->xroot, screen_info->workspace_count,
605                         screen_info->width, screen_info->height, screen_info->margins);
606         /* Also prevent windows from being off screen, just like when screen is resized */
607         clientScreenResize(screen_info, FALSE);
608     }
609 }
610