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