nothing to see here, move along
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2004        Mikael Magnusson
5    Copyright (c) 2003        Ben Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "xerror.h"
25 #include "screen.h"
26 #include "moveresize.h"
27 #include "place.h"
28 #include "prop.h"
29 #include "extensions.h"
30 #include "frame.h"
31 #include "session.h"
32 #include "event.h"
33 #include "grab.h"
34 #include "focus.h"
35 #include "stacking.h"
36 #include "openbox.h"
37 #include "group.h"
38 #include "config.h"
39 #include "menuframe.h"
40 #include "keyboard.h"
41 #include "mouse.h"
42 #include "render/render.h"
43
44 #include <glib.h>
45 #include <X11/Xutil.h>
46
47 /*! The event mask to grab on client windows */
48 #define CLIENT_EVENTMASK (PropertyChangeMask | FocusChangeMask | \
49                           StructureNotifyMask)
50
51 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
52                                 ButtonMotionMask)
53
54 typedef struct
55 {
56     ObClientDestructor func;
57     gpointer data;
58 } Destructor;
59
60 GList      *client_list        = NULL;
61 GSList     *client_destructors = NULL;
62
63 static void client_get_all(ObClient *self);
64 static void client_toggle_border(ObClient *self, gboolean show);
65 static void client_get_startup_id(ObClient *self);
66 static void client_get_area(ObClient *self);
67 static void client_get_desktop(ObClient *self);
68 static void client_get_state(ObClient *self);
69 static void client_get_shaped(ObClient *self);
70 static void client_get_mwm_hints(ObClient *self);
71 static void client_get_gravity(ObClient *self);
72 static void client_showhide(ObClient *self);
73 static void client_change_allowed_actions(ObClient *self);
74 static void client_change_state(ObClient *self);
75 static void client_apply_startup_state(ObClient *self);
76 static void client_restore_session_state(ObClient *self);
77 static void client_restore_session_stacking(ObClient *self);
78 static void client_urgent_notify(ObClient *self);
79
80 void client_startup(gboolean reconfig)
81 {
82     if (reconfig) return;
83
84     client_set_list();
85 }
86
87 void client_shutdown(gboolean reconfig)
88 {
89 }
90
91 void client_add_destructor(ObClientDestructor func, gpointer data)
92 {
93     Destructor *d = g_new(Destructor, 1);
94     d->func = func;
95     d->data = data;
96     client_destructors = g_slist_prepend(client_destructors, d);
97 }
98
99 void client_remove_destructor(ObClientDestructor func)
100 {
101     GSList *it;
102
103     for (it = client_destructors; it; it = g_slist_next(it)) {
104         Destructor *d = it->data;
105         if (d->func == func) {
106             g_free(d);
107             client_destructors = g_slist_delete_link(client_destructors, it);
108             break;
109         }
110     }
111 }
112
113 void client_set_list()
114 {
115     Window *windows, *win_it;
116     GList *it;
117     guint size = g_list_length(client_list);
118
119     /* create an array of the window ids */
120     if (size > 0) {
121         windows = g_new(Window, size);
122         win_it = windows;
123         for (it = client_list; it; it = g_list_next(it), ++win_it)
124             *win_it = ((ObClient*)it->data)->window;
125     } else
126         windows = NULL;
127
128     PROP_SETA32(RootWindow(ob_display, ob_screen),
129                 net_client_list, window, (guint32*)windows, size);
130
131     if (windows)
132         g_free(windows);
133
134     stacking_set_list();
135 }
136
137 /*
138   void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
139   {
140   GSList *it;
141
142   for (it = self->transients; it; it = g_slist_next(it)) {
143   if (!func(it->data, data)) return;
144   client_foreach_transient(it->data, func, data);
145   }
146   }
147
148   void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
149   {
150   if (self->transient_for) {
151   if (self->transient_for != OB_TRAN_GROUP) {
152   if (!func(self->transient_for, data)) return;
153   client_foreach_ancestor(self->transient_for, func, data);
154   } else {
155   GSList *it;
156
157   for (it = self->group->members; it; it = g_slist_next(it))
158   if (it->data != self &&
159   !((ObClient*)it->data)->transient_for) {
160   if (!func(it->data, data)) return;
161   client_foreach_ancestor(it->data, func, data);
162   }
163   }
164   }
165   }
166 */
167
168 void client_manage_all()
169 {
170     guint i, j, nchild;
171     Window w, *children;
172     XWMHints *wmhints;
173     XWindowAttributes attrib;
174
175     XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
176                &w, &w, &children, &nchild);
177
178     /* remove all icon windows from the list */
179     for (i = 0; i < nchild; i++) {
180         if (children[i] == None) continue;
181         wmhints = XGetWMHints(ob_display, children[i]);
182         if (wmhints) {
183             if ((wmhints->flags & IconWindowHint) &&
184                 (wmhints->icon_window != children[i]))
185                 for (j = 0; j < nchild; j++)
186                     if (children[j] == wmhints->icon_window) {
187                         children[j] = None;
188                         break;
189                     }
190             XFree(wmhints);
191         }
192     }
193
194     for (i = 0; i < nchild; ++i) {
195         if (children[i] == None)
196             continue;
197         if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
198             if (attrib.override_redirect) continue;
199
200             if (attrib.map_state != IsUnmapped)
201                 client_manage(children[i]);
202         }
203     }
204     XFree(children);
205 }
206
207 void client_manage(Window window)
208 {
209     ObClient *self;
210     XEvent e;
211     XWindowAttributes attrib;
212     XSetWindowAttributes attrib_set;
213     XWMHints *wmhint;
214     gboolean activate = FALSE;
215
216     grab_server(TRUE);
217
218     /* check if it has already been unmapped by the time we started mapping
219        the grab does a sync so we don't have to here */
220     if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
221         XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) {
222         XPutBackEvent(ob_display, &e);
223
224         grab_server(FALSE);
225         return; /* don't manage it */
226     }
227
228     /* make sure it isn't an override-redirect window */
229     if (!XGetWindowAttributes(ob_display, window, &attrib) ||
230         attrib.override_redirect) {
231         grab_server(FALSE);
232         return; /* don't manage it */
233     }
234   
235     /* is the window a docking app */
236     if ((wmhint = XGetWMHints(ob_display, window))) {
237         if ((wmhint->flags & StateHint) &&
238             wmhint->initial_state == WithdrawnState) {
239             dock_add(window, wmhint);
240             grab_server(FALSE);
241             XFree(wmhint);
242             return;
243         }
244         XFree(wmhint);
245     }
246
247     ob_debug("Managing window: %lx\n", window);
248
249     /* choose the events we want to receive on the CLIENT window */
250     attrib_set.event_mask = CLIENT_EVENTMASK;
251     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
252     XChangeWindowAttributes(ob_display, window,
253                             CWEventMask|CWDontPropagate, &attrib_set);
254
255
256     /* create the ObClient struct, and populate it from the hints on the
257        window */
258     self = g_new0(ObClient, 1);
259     self->obwin.type = Window_Client;
260     self->window = window;
261
262     /* non-zero defaults */
263     self->title_count = 1;
264     self->wmstate = NormalState;
265     self->layer = -1;
266     self->desktop = screen_num_desktops; /* always an invalid value */
267
268     client_get_all(self);
269     client_restore_session_state(self);
270
271     sn_app_started(self->class);
272
273     /* update the focus lists, do this before the call to change_state or
274        it can end up in the list twice! */
275     focus_order_add_new(self);
276
277     client_change_state(self);
278
279     /* remove the client's border (and adjust re gravity) */
280     client_toggle_border(self, FALSE);
281      
282     /* specify that if we exit, the window should not be destroyed and should
283        be reparented back to root automatically */
284     XChangeSaveSet(ob_display, window, SetModeInsert);
285
286     /* create the decoration frame for the client window */
287     self->frame = frame_new();
288
289     frame_grab_client(self->frame, self);
290
291     grab_server(FALSE);
292
293     client_apply_startup_state(self);
294
295     stacking_add(CLIENT_AS_WINDOW(self));
296     client_restore_session_stacking(self);
297
298     /* focus the new window? */
299     if (ob_state() != OB_STATE_STARTING &&
300         (config_focus_new || client_search_focus_parent(self)) &&
301         /* note the check against Type_Normal/Dialog, not client_normal(self),
302            which would also include other types. in this case we want more
303            strict rules for focus */
304         (self->type == OB_CLIENT_TYPE_NORMAL ||
305          self->type == OB_CLIENT_TYPE_DIALOG))
306     {        
307         activate = TRUE;
308 #if 0
309         if (self->desktop != screen_desktop) {
310             /* activate the window */
311             activate = TRUE;
312         } else {
313             gboolean group_foc = FALSE;
314
315             if (self->group) {
316                 GSList *it;
317
318                 for (it = self->group->members; it; it = g_slist_next(it))
319                 {
320                     if (client_focused(it->data))
321                     {
322                         group_foc = TRUE;
323                         break;
324                     }
325                 }
326             }
327             if ((group_foc ||
328                  (!self->transient_for && (!self->group ||
329                                            !self->group->members->next))) ||
330                 client_search_focus_tree_full(self) ||
331                 !focus_client ||
332                 !client_normal(focus_client))
333             {
334                 /* activate the window */
335                 activate = TRUE;
336             }
337         }
338 #endif
339     }
340
341     if (ob_state() == OB_STATE_RUNNING) {
342         gint x = self->area.x, ox = x;
343         gint y = self->area.y, oy = y;
344
345         place_client(self, &x, &y);
346
347         /* make sure the window is visible */
348         client_find_onscreen(self, &x, &y,
349                              self->frame->area.width,
350                              self->frame->area.height,
351                              /* non-normal clients has less rules, and
352                                 windows that are being restored from a session
353                                 do also. we can assume you want it back where
354                                 you saved it */
355                              client_normal(self) && !self->session);
356
357         if (x != ox || y != oy)
358             client_move(self, x, y);
359     }
360
361     client_showhide(self);
362
363     /* use client_focus instead of client_activate cuz client_activate does
364        stuff like switch desktops etc and I'm not interested in all that when
365        a window maps since its not based on an action from the user like
366        clicking a window to activate is. so keep the new window out of the way
367        but do focus it. */
368     if (activate) {
369         /* if using focus_delay, stop the timer now so that focus doesn't go
370            moving on us */
371         event_halt_focus_delay();
372
373         client_focus(self);
374         /* since focus can change the stacking orders, if we focus the window
375            then the standard raise it gets is not enough, we need to queue one
376            for after the focus change takes place */
377         client_raise(self);
378     }
379
380     /* client_activate does this but we aret using it so we have to do it
381        here as well */
382     if (screen_showing_desktop)
383         screen_show_desktop(FALSE);
384
385     /* add to client list/map */
386     client_list = g_list_append(client_list, self);
387     g_hash_table_insert(window_map, &self->window, self);
388
389     /* this has to happen after we're in the client_list */
390     screen_update_areas();
391
392     /* update the list hints */
393     client_set_list();
394
395     keyboard_grab_for_client(self, TRUE);
396     mouse_grab_for_client(self, TRUE);
397
398     ob_debug("Managed window 0x%lx (%s)\n", window, self->class);
399 }
400
401 void client_unmanage_all()
402 {
403     while (client_list != NULL)
404         client_unmanage(client_list->data);
405 }
406
407 void client_unmanage(ObClient *self)
408 {
409     guint j;
410     GSList *it;
411
412     ob_debug("Unmanaging window: %lx (%s)\n", self->window, self->class);
413
414     g_assert(self != NULL);
415
416     keyboard_grab_for_client(self, FALSE);
417     mouse_grab_for_client(self, FALSE);
418
419     /* potentially fix focusLast */
420     if (config_focus_last)
421         grab_pointer(TRUE, OB_CURSOR_NONE);
422
423     /* remove the window from our save set */
424     XChangeSaveSet(ob_display, self->window, SetModeDelete);
425
426     /* we dont want events no more */
427     XSelectInput(ob_display, self->window, NoEventMask);
428
429     frame_hide(self->frame);
430
431     client_list = g_list_remove(client_list, self);
432     stacking_remove(self);
433     g_hash_table_remove(window_map, &self->window);
434
435     /* update the focus lists */
436     focus_order_remove(self);
437
438     /* once the client is out of the list, update the struts to remove it's
439        influence */
440     screen_update_areas();
441
442     for (it = client_destructors; it; it = g_slist_next(it)) {
443         Destructor *d = it->data;
444         d->func(self, d->data);
445     }
446         
447     if (focus_client == self) {
448         XEvent e;
449
450         /* focus the last focused window on the desktop, and ignore enter
451            events from the unmap so it doesnt mess with the focus */
452         while (XCheckTypedEvent(ob_display, EnterNotify, &e));
453         /* remove these flags so we don't end up getting focused in the
454            fallback! */
455         self->can_focus = FALSE;
456         self->focus_notify = FALSE;
457         self->modal = FALSE;
458         client_unfocus(self);
459     }
460
461     /* tell our parent(s) that we're gone */
462     if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
463         GSList *it;
464
465         for (it = self->group->members; it; it = g_slist_next(it))
466             if (it->data != self)
467                 ((ObClient*)it->data)->transients =
468                     g_slist_remove(((ObClient*)it->data)->transients, self);
469     } else if (self->transient_for) {        /* transient of window */
470         self->transient_for->transients =
471             g_slist_remove(self->transient_for->transients, self);
472     }
473
474     /* tell our transients that we're gone */
475     for (it = self->transients; it; it = g_slist_next(it)) {
476         if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
477             ((ObClient*)it->data)->transient_for = NULL;
478             client_calc_layer(it->data);
479         }
480     }
481
482     /* remove from its group */
483     if (self->group) {
484         group_remove(self->group, self);
485         self->group = NULL;
486     }
487
488     /* give the client its border back */
489     client_toggle_border(self, TRUE);
490
491     /* reparent the window out of the frame, and free the frame */
492     frame_release_client(self->frame, self);
493     self->frame = NULL;
494      
495     if (ob_state() != OB_STATE_EXITING) {
496         /* these values should not be persisted across a window
497            unmapping/mapping */
498         PROP_ERASE(self->window, net_wm_desktop);
499         PROP_ERASE(self->window, net_wm_state);
500         PROP_ERASE(self->window, wm_state);
501     } else {
502         /* if we're left in an unmapped state, the client wont be mapped. this
503            is bad, since we will no longer be managing the window on restart */
504         XMapWindow(ob_display, self->window);
505     }
506
507
508     ob_debug("Unmanaged window 0x%lx\n", self->window);
509
510     /* free all data allocated in the client struct */
511     g_slist_free(self->transients);
512     for (j = 0; j < self->nicons; ++j)
513         g_free(self->icons[j].data);
514     if (self->nicons > 0)
515         g_free(self->icons);
516     g_free(self->title);
517     g_free(self->icon_title);
518     g_free(self->name);
519     g_free(self->class);
520     g_free(self->role);
521     g_free(self->sm_client_id);
522     g_free(self);
523      
524     /* update the list hints */
525     client_set_list();
526
527     if (config_focus_last)
528         grab_pointer(FALSE, OB_CURSOR_NONE);
529 }
530
531 static void client_urgent_notify(ObClient *self)
532 {
533     if (self->urgent)
534         frame_flash_start(self->frame);
535     else
536         frame_flash_stop(self->frame);
537 }
538
539 static void client_restore_session_state(ObClient *self)
540 {
541     GList *it;
542
543     if (!(it = session_state_find(self)))
544         return;
545
546     self->session = it->data;
547
548     RECT_SET_POINT(self->area, self->session->x, self->session->y);
549     self->positioned = TRUE;
550     if (self->session->w > 0)
551         self->area.width = self->session->w;
552     if (self->session->h > 0)
553         self->area.height = self->session->h;
554     XResizeWindow(ob_display, self->window,
555                   self->area.width, self->area.height);
556
557     self->desktop = (self->session->desktop == DESKTOP_ALL ?
558                      self->session->desktop :
559                      MIN(screen_num_desktops - 1, self->session->desktop));
560     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
561
562     self->shaded = self->session->shaded;
563     self->iconic = self->session->iconic;
564     self->skip_pager = self->session->skip_pager;
565     self->skip_taskbar = self->session->skip_taskbar;
566     self->fullscreen = self->session->fullscreen;
567     self->above = self->session->above;
568     self->below = self->session->below;
569     self->max_horz = self->session->max_horz;
570     self->max_vert = self->session->max_vert;
571 }
572
573 static void client_restore_session_stacking(ObClient *self)
574 {
575     GList *it;
576
577     if (!self->session) return;
578
579     it = g_list_find(session_saved_state, self->session);
580     for (it = g_list_previous(it); it; it = g_list_previous(it)) {
581         GList *cit;
582
583         for (cit = client_list; cit; cit = g_list_next(cit))
584             if (session_state_cmp(it->data, cit->data))
585                 break;
586         if (cit) {
587             client_calc_layer(self);
588             stacking_below(CLIENT_AS_WINDOW(self),
589                            CLIENT_AS_WINDOW(cit->data));
590             break;
591         }
592     }
593 }
594
595 void client_move_onscreen(ObClient *self, gboolean rude)
596 {
597     gint x = self->area.x;
598     gint y = self->area.y;
599     if (client_find_onscreen(self, &x, &y,
600                              self->frame->area.width,
601                              self->frame->area.height, rude)) {
602         client_move(self, x, y);
603     }
604 }
605
606 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
607                               gboolean rude)
608 {
609     Rect *a;
610     gint ox = *x, oy = *y;
611
612     frame_client_gravity(self->frame, x, y); /* get where the frame
613                                                 would be */
614
615     /* XXX watch for xinerama dead areas */
616
617     a = screen_area(self->desktop);
618     if (client_normal(self)) {
619         if (!self->strut.right && *x >= a->x + a->width - 1)
620             *x = a->x + a->width - self->frame->area.width;
621         if (!self->strut.bottom && *y >= a->y + a->height - 1)
622             *y = a->y + a->height - self->frame->area.height;
623         if (!self->strut.left && *x + self->frame->area.width - 1 < a->x)
624             *x = a->x;
625         if (!self->strut.top && *y + self->frame->area.height - 1 < a->y)
626             *y = a->y;
627     }
628
629     if (rude) {
630         /* this is my MOZILLA BITCHSLAP. oh ya it fucking feels good.
631            Java can suck it too. */
632
633         /* dont let windows map/move into the strut unless they
634            are bigger than the available area */
635         if (w <= a->width) {
636             if (!self->strut.left && *x < a->x) *x = a->x;
637             if (!self->strut.right && *x + w > a->x + a->width)
638                 *x = a->x + a->width - w;
639         }
640         if (h <= a->height) {
641             if (!self->strut.top && *y < a->y) *y = a->y;
642             if (!self->strut.bottom && *y + h > a->y + a->height)
643                 *y = a->y + a->height - h;
644         }
645     }
646
647     frame_frame_gravity(self->frame, x, y); /* get where the client
648                                                should be */
649
650     return ox != *x || oy != *y;
651 }
652
653 static void client_toggle_border(ObClient *self, gboolean show)
654 {
655     /* adjust our idea of where the client is, based on its border. When the
656        border is removed, the client should now be considered to be in a
657        different position.
658        when re-adding the border to the client, the same operation needs to be
659        reversed. */
660     gint oldx = self->area.x, oldy = self->area.y;
661     gint x = oldx, y = oldy;
662     switch(self->gravity) {
663     default:
664     case NorthWestGravity:
665     case WestGravity:
666     case SouthWestGravity:
667         break;
668     case NorthEastGravity:
669     case EastGravity:
670     case SouthEastGravity:
671         if (show) x -= self->border_width * 2;
672         else      x += self->border_width * 2;
673         break;
674     case NorthGravity:
675     case SouthGravity:
676     case CenterGravity:
677     case ForgetGravity:
678     case StaticGravity:
679         if (show) x -= self->border_width;
680         else      x += self->border_width;
681         break;
682     }
683     switch(self->gravity) {
684     default:
685     case NorthWestGravity:
686     case NorthGravity:
687     case NorthEastGravity:
688         break;
689     case SouthWestGravity:
690     case SouthGravity:
691     case SouthEastGravity:
692         if (show) y -= self->border_width * 2;
693         else      y += self->border_width * 2;
694         break;
695     case WestGravity:
696     case EastGravity:
697     case CenterGravity:
698     case ForgetGravity:
699     case StaticGravity:
700         if (show) y -= self->border_width;
701         else      y += self->border_width;
702         break;
703     }
704     self->area.x = x;
705     self->area.y = y;
706
707     if (show) {
708         XSetWindowBorderWidth(ob_display, self->window, self->border_width);
709
710         /* move the client so it is back it the right spot _with_ its
711            border! */
712         if (x != oldx || y != oldy)
713             XMoveWindow(ob_display, self->window, x, y);
714     } else
715         XSetWindowBorderWidth(ob_display, self->window, 0);
716 }
717
718
719 static void client_get_all(ObClient *self)
720 {
721     client_get_area(self);
722     client_update_transient_for(self);
723     client_update_wmhints(self);
724     client_get_startup_id(self);
725     client_get_desktop(self);
726     client_get_shaped(self);
727
728     client_get_mwm_hints(self);
729     client_get_type(self);/* this can change the mwmhints for special cases */
730
731     /* The transient hint is used to pick a type, but the type can also affect
732        transiency (dialogs are always made transients). This is Havoc's idea,
733        but it is needed to make some apps work right (eg tsclient). */
734     client_update_transient_for(self);
735
736     client_get_state(self);
737
738     {
739         /* a couple type-based defaults for new windows */
740
741         /* this makes sure that these windows appear on all desktops */
742         if (self->type == OB_CLIENT_TYPE_DESKTOP)
743             self->desktop = DESKTOP_ALL;
744     }
745
746     client_update_protocols(self);
747
748     client_get_gravity(self); /* get the attribute gravity */
749     client_update_normal_hints(self); /* this may override the attribute
750                                          gravity */
751
752     /* got the type, the mwmhints, the protocols, and the normal hints
753        (min/max sizes), so we're ready to set up the decorations/functions */
754     client_setup_decor_and_functions(self);
755   
756     client_update_title(self);
757     client_update_class(self);
758     client_update_sm_client_id(self);
759     client_update_strut(self);
760     client_update_icons(self);
761 }
762
763 static void client_get_startup_id(ObClient *self)
764 {
765     if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
766         if (self->group)
767             PROP_GETS(self->group->leader,
768                       net_startup_id, utf8, &self->startup_id);
769 }
770
771 static void client_get_area(ObClient *self)
772 {
773     XWindowAttributes wattrib;
774     Status ret;
775   
776     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
777     g_assert(ret != BadWindow);
778
779     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
780     self->border_width = wattrib.border_width;
781 }
782
783 static void client_get_desktop(ObClient *self)
784 {
785     guint32 d = screen_num_desktops; /* an always-invalid value */
786
787     if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
788         if (d >= screen_num_desktops && d != DESKTOP_ALL)
789             self->desktop = screen_num_desktops - 1;
790         else
791             self->desktop = d;
792     } else {
793         gboolean trdesk = FALSE;
794
795         if (self->transient_for) {
796             if (self->transient_for != OB_TRAN_GROUP) {
797                 self->desktop = self->transient_for->desktop;
798                 trdesk = TRUE;
799             } else {
800                 GSList *it;
801
802                 for (it = self->group->members; it; it = g_slist_next(it))
803                     if (it->data != self &&
804                         !((ObClient*)it->data)->transient_for) {
805                         self->desktop = ((ObClient*)it->data)->desktop;
806                         trdesk = TRUE;
807                         break;
808                     }
809             }
810         }
811         if (!trdesk) {
812             /* try get from the startup-notification protocol */
813             if (sn_get_desktop(self->startup_id, &self->desktop)) {
814                 if (self->desktop >= screen_num_desktops &&
815                     self->desktop != DESKTOP_ALL)
816                     self->desktop = screen_num_desktops - 1;
817             } else
818                 /* defaults to the current desktop */
819                 self->desktop = screen_desktop;
820         }
821     }
822     if (self->desktop != d) {
823         /* set the desktop hint, to make sure that it always exists */
824         PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
825     }
826 }
827
828 static void client_get_state(ObClient *self)
829 {
830     guint32 *state;
831     guint num;
832   
833     if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
834         gulong i;
835         for (i = 0; i < num; ++i) {
836             if (state[i] == prop_atoms.net_wm_state_modal)
837                 self->modal = TRUE;
838             else if (state[i] == prop_atoms.net_wm_state_shaded)
839                 self->shaded = TRUE;
840             else if (state[i] == prop_atoms.net_wm_state_hidden)
841                 self->iconic = TRUE;
842             else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
843                 self->skip_taskbar = TRUE;
844             else if (state[i] == prop_atoms.net_wm_state_skip_pager)
845                 self->skip_pager = TRUE;
846             else if (state[i] == prop_atoms.net_wm_state_fullscreen)
847                 self->fullscreen = TRUE;
848             else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
849                 self->max_vert = TRUE;
850             else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
851                 self->max_horz = TRUE;
852             else if (state[i] == prop_atoms.net_wm_state_above)
853                 self->above = TRUE;
854             else if (state[i] == prop_atoms.net_wm_state_below)
855                 self->below = TRUE;
856             else if (state[i] == prop_atoms.ob_wm_state_undecorated)
857                 self->undecorated = TRUE;
858         }
859
860         g_free(state);
861     }
862
863     if (!(self->above || self->below)) {
864         if (self->group) {
865             /* apply stuff from the group */
866             GSList *it;
867             gint layer = -2;
868
869             for (it = self->group->members; it; it = g_slist_next(it)) {
870                 ObClient *c = it->data;
871                 if (c != self && !client_search_transient(self, c) &&
872                     client_normal(self) && client_normal(c))
873                 {
874                     layer = MAX(layer,
875                                 (c->above ? 1 : (c->below ? -1 : 0)));
876                 }
877             }
878             switch (layer) {
879             case -1:
880                 self->below = TRUE;
881                 break;
882             case -2:
883             case 0:
884                 break;
885             case 1:
886                 self->above = TRUE;
887                 break;
888             default:
889                 g_assert_not_reached();
890                 break;
891             }
892         }
893     }
894 }
895
896 static void client_get_shaped(ObClient *self)
897 {
898     self->shaped = FALSE;
899 #ifdef   SHAPE
900     if (extensions_shape) {
901         gint foo;
902         guint ufoo;
903         gint s;
904
905         XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
906
907         XShapeQueryExtents(ob_display, self->window, &s, &foo,
908                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
909                            &ufoo);
910         self->shaped = (s != 0);
911     }
912 #endif
913 }
914
915 void client_update_transient_for(ObClient *self)
916 {
917     Window t = None;
918     ObClient *target = NULL;
919
920     if (XGetTransientForHint(ob_display, self->window, &t)) {
921         self->transient = TRUE;
922         if (t != self->window) { /* cant be transient to itself! */
923             target = g_hash_table_lookup(window_map, &t);
924             /* if this happens then we need to check for it*/
925             g_assert(target != self);
926             if (target && !WINDOW_IS_CLIENT(target)) {
927                 /* this can happen when a dialog is a child of
928                    a dockapp, for example */
929                 target = NULL;
930             }
931             
932             if (!target && self->group) {
933                 /* not transient to a client, see if it is transient for a
934                    group */
935                 if (t == self->group->leader ||
936                     t == None ||
937                     t == RootWindow(ob_display, ob_screen))
938                 {
939                     /* window is a transient for its group! */
940                     target = OB_TRAN_GROUP;
941                 }
942             }
943         }
944     } else if (self->type == OB_CLIENT_TYPE_DIALOG && self->group) {
945         self->transient = TRUE;
946         target = OB_TRAN_GROUP;
947     } else
948         self->transient = FALSE;
949
950     /* if anything has changed... */
951     if (target != self->transient_for) {
952         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
953             GSList *it;
954
955             /* remove from old parents */
956             for (it = self->group->members; it; it = g_slist_next(it)) {
957                 ObClient *c = it->data;
958                 if (c != self && !c->transient_for)
959                     c->transients = g_slist_remove(c->transients, self);
960             }
961         } else if (self->transient_for != NULL) { /* transient of window */
962             /* remove from old parent */
963             self->transient_for->transients =
964                 g_slist_remove(self->transient_for->transients, self);
965         }
966         self->transient_for = target;
967         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
968             GSList *it;
969
970             /* add to new parents */
971             for (it = self->group->members; it; it = g_slist_next(it)) {
972                 ObClient *c = it->data;
973                 if (c != self && !c->transient_for)
974                     c->transients = g_slist_append(c->transients, self);
975             }
976
977             /* remove all transients which are in the group, that causes
978                circlular pointer hell of doom */
979             for (it = self->group->members; it; it = g_slist_next(it)) {
980                 GSList *sit, *next;
981                 for (sit = self->transients; sit; sit = next) {
982                     next = g_slist_next(sit);
983                     if (sit->data == it->data)
984                         self->transients =
985                             g_slist_delete_link(self->transients, sit);
986                 }
987             }
988         } else if (self->transient_for != NULL) { /* transient of window */
989             /* add to new parent */
990             self->transient_for->transients =
991                 g_slist_append(self->transient_for->transients, self);
992         }
993     }
994 }
995
996 static void client_get_mwm_hints(ObClient *self)
997 {
998     guint num;
999     guint32 *hints;
1000
1001     self->mwmhints.flags = 0; /* default to none */
1002
1003     if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
1004                     &hints, &num)) {
1005         if (num >= OB_MWM_ELEMENTS) {
1006             self->mwmhints.flags = hints[0];
1007             self->mwmhints.functions = hints[1];
1008             self->mwmhints.decorations = hints[2];
1009         }
1010         g_free(hints);
1011     }
1012 }
1013
1014 void client_get_type(ObClient *self)
1015 {
1016     guint num, i;
1017     guint32 *val;
1018
1019     self->type = -1;
1020   
1021     if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
1022         /* use the first value that we know about in the array */
1023         for (i = 0; i < num; ++i) {
1024             if (val[i] == prop_atoms.net_wm_window_type_desktop)
1025                 self->type = OB_CLIENT_TYPE_DESKTOP;
1026             else if (val[i] == prop_atoms.net_wm_window_type_dock)
1027                 self->type = OB_CLIENT_TYPE_DOCK;
1028             else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
1029                 self->type = OB_CLIENT_TYPE_TOOLBAR;
1030             else if (val[i] == prop_atoms.net_wm_window_type_menu)
1031                 self->type = OB_CLIENT_TYPE_MENU;
1032             else if (val[i] == prop_atoms.net_wm_window_type_utility)
1033                 self->type = OB_CLIENT_TYPE_UTILITY;
1034             else if (val[i] == prop_atoms.net_wm_window_type_splash)
1035                 self->type = OB_CLIENT_TYPE_SPLASH;
1036             else if (val[i] == prop_atoms.net_wm_window_type_dialog)
1037                 self->type = OB_CLIENT_TYPE_DIALOG;
1038             else if (val[i] == prop_atoms.net_wm_window_type_normal)
1039                 self->type = OB_CLIENT_TYPE_NORMAL;
1040             else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
1041                 /* prevent this window from getting any decor or
1042                    functionality */
1043                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1044                                          OB_MWM_FLAG_DECORATIONS);
1045                 self->mwmhints.decorations = 0;
1046                 self->mwmhints.functions = 0;
1047             }
1048             if (self->type != (ObClientType) -1)
1049                 break; /* grab the first legit type */
1050         }
1051         g_free(val);
1052     }
1053     
1054     if (self->type == (ObClientType) -1) {
1055         /*the window type hint was not set, which means we either classify
1056           ourself as a normal window or a dialog, depending on if we are a
1057           transient. */
1058         if (self->transient)
1059             self->type = OB_CLIENT_TYPE_DIALOG;
1060         else
1061             self->type = OB_CLIENT_TYPE_NORMAL;
1062     }
1063 }
1064
1065 void client_update_protocols(ObClient *self)
1066 {
1067     guint32 *proto;
1068     guint num_return, i;
1069
1070     self->focus_notify = FALSE;
1071     self->delete_window = FALSE;
1072
1073     if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1074         for (i = 0; i < num_return; ++i) {
1075             if (proto[i] == prop_atoms.wm_delete_window) {
1076                 /* this means we can request the window to close */
1077                 self->delete_window = TRUE;
1078             } else if (proto[i] == prop_atoms.wm_take_focus)
1079                 /* if this protocol is requested, then the window will be
1080                    notified whenever we want it to receive focus */
1081                 self->focus_notify = TRUE;
1082         }
1083         g_free(proto);
1084     }
1085 }
1086
1087 static void client_get_gravity(ObClient *self)
1088 {
1089     XWindowAttributes wattrib;
1090     Status ret;
1091
1092     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1093     g_assert(ret != BadWindow);
1094     self->gravity = wattrib.win_gravity;
1095 }
1096
1097 void client_update_normal_hints(ObClient *self)
1098 {
1099     XSizeHints size;
1100     glong ret;
1101     gint oldgravity = self->gravity;
1102
1103     /* defaults */
1104     self->min_ratio = 0.0f;
1105     self->max_ratio = 0.0f;
1106     SIZE_SET(self->size_inc, 1, 1);
1107     SIZE_SET(self->base_size, 0, 0);
1108     SIZE_SET(self->min_size, 0, 0);
1109     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1110
1111     /* get the hints from the window */
1112     if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1113         /* normal windows can't request placement! har har
1114         if (!client_normal(self))
1115         */
1116         self->positioned = !!(size.flags & (PPosition|USPosition));
1117
1118         if (size.flags & PWinGravity) {
1119             self->gravity = size.win_gravity;
1120       
1121             /* if the client has a frame, i.e. has already been mapped and
1122                is changing its gravity */
1123             if (self->frame && self->gravity != oldgravity) {
1124                 /* move our idea of the client's position based on its new
1125                    gravity */
1126                 self->area.x = self->frame->area.x;
1127                 self->area.y = self->frame->area.y;
1128                 frame_frame_gravity(self->frame, &self->area.x, &self->area.y);
1129             }
1130         }
1131
1132         if (size.flags & PAspect) {
1133             if (size.min_aspect.y)
1134                 self->min_ratio =
1135                     (gfloat) size.min_aspect.x / size.min_aspect.y;
1136             if (size.max_aspect.y)
1137                 self->max_ratio =
1138                     (gfloat) size.max_aspect.x / size.max_aspect.y;
1139         }
1140
1141         if (size.flags & PMinSize)
1142             SIZE_SET(self->min_size, size.min_width, size.min_height);
1143     
1144         if (size.flags & PMaxSize)
1145             SIZE_SET(self->max_size, size.max_width, size.max_height);
1146     
1147         if (size.flags & PBaseSize)
1148             SIZE_SET(self->base_size, size.base_width, size.base_height);
1149     
1150         if (size.flags & PResizeInc)
1151             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1152     }
1153 }
1154
1155 void client_setup_decor_and_functions(ObClient *self)
1156 {
1157     /* start with everything (cept fullscreen) */
1158     self->decorations =
1159         (OB_FRAME_DECOR_TITLEBAR |
1160          (ob_rr_theme->show_handle ? OB_FRAME_DECOR_HANDLE : 0) |
1161          OB_FRAME_DECOR_GRIPS |
1162          OB_FRAME_DECOR_BORDER |
1163          OB_FRAME_DECOR_ICON |
1164          OB_FRAME_DECOR_ALLDESKTOPS |
1165          OB_FRAME_DECOR_ICONIFY |
1166          OB_FRAME_DECOR_MAXIMIZE |
1167          OB_FRAME_DECOR_SHADE |
1168          OB_FRAME_DECOR_CLOSE);
1169     self->functions =
1170         (OB_CLIENT_FUNC_RESIZE |
1171          OB_CLIENT_FUNC_MOVE |
1172          OB_CLIENT_FUNC_ICONIFY |
1173          OB_CLIENT_FUNC_MAXIMIZE |
1174          OB_CLIENT_FUNC_SHADE |
1175          OB_CLIENT_FUNC_CLOSE);
1176
1177     if (!(self->min_size.width < self->max_size.width ||
1178           self->min_size.height < self->max_size.height))
1179         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1180
1181     switch (self->type) {
1182     case OB_CLIENT_TYPE_NORMAL:
1183         /* normal windows retain all of the possible decorations and
1184            functionality, and are the only windows that you can fullscreen */
1185         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1186         break;
1187
1188     case OB_CLIENT_TYPE_DIALOG:
1189     case OB_CLIENT_TYPE_UTILITY:
1190         /* these windows cannot be maximized */
1191         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1192         break;
1193
1194     case OB_CLIENT_TYPE_MENU:
1195     case OB_CLIENT_TYPE_TOOLBAR:
1196         /* these windows get less functionality */
1197         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY | OB_CLIENT_FUNC_RESIZE);
1198         break;
1199
1200     case OB_CLIENT_TYPE_DESKTOP:
1201     case OB_CLIENT_TYPE_DOCK:
1202     case OB_CLIENT_TYPE_SPLASH:
1203         /* none of these windows are manipulated by the window manager */
1204         self->decorations = 0;
1205         self->functions = 0;
1206         break;
1207     }
1208
1209     /* Mwm Hints are applied subtractively to what has already been chosen for
1210        decor and functionality */
1211     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1212         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1213             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1214                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1215                 /* if the mwm hints request no handle or title, then all
1216                    decorations are disabled */
1217                 self->decorations = 0;
1218         }
1219     }
1220
1221     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1222         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1223             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1224                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1225             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1226                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1227             /* dont let mwm hints kill any buttons
1228                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1229                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1230                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1231                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1232             */
1233             /* dont let mwm hints kill the close button
1234                if (! (self->mwmhints.functions & MwmFunc_Close))
1235                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1236         }
1237     }
1238
1239     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1240         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1241     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1242         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1243     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1244         self->decorations &= ~OB_FRAME_DECOR_GRIPS;
1245
1246     /* can't maximize without moving/resizing */
1247     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1248           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1249           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1250         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1251         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1252     }
1253
1254     /* kill the handle on fully maxed windows */
1255     if (self->max_vert && self->max_horz)
1256         self->decorations &= ~OB_FRAME_DECOR_HANDLE;
1257
1258     /* finally, the user can have requested no decorations, which overrides
1259        everything (but doesnt give it a border if it doesnt have one) */
1260     if (self->undecorated) {
1261         if (config_theme_keepborder)
1262             self->decorations &= OB_FRAME_DECOR_BORDER;
1263         else
1264             self->decorations = 0;
1265     }
1266
1267     /* if we don't have a titlebar, then we cannot shade! */
1268     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1269         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1270
1271     /* now we need to check against rules for the client's current state */
1272     if (self->fullscreen) {
1273         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1274                             OB_CLIENT_FUNC_FULLSCREEN |
1275                             OB_CLIENT_FUNC_ICONIFY);
1276         self->decorations = 0;
1277     }
1278
1279     client_change_allowed_actions(self);
1280
1281     if (self->frame) {
1282         /* adjust the client's decorations, etc. */
1283         client_reconfigure(self);
1284     }
1285 }
1286
1287 static void client_change_allowed_actions(ObClient *self)
1288 {
1289     guint32 actions[9];
1290     gint num = 0;
1291
1292     /* desktop windows are kept on all desktops */
1293     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1294         actions[num++] = prop_atoms.net_wm_action_change_desktop;
1295
1296     if (self->functions & OB_CLIENT_FUNC_SHADE)
1297         actions[num++] = prop_atoms.net_wm_action_shade;
1298     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1299         actions[num++] = prop_atoms.net_wm_action_close;
1300     if (self->functions & OB_CLIENT_FUNC_MOVE)
1301         actions[num++] = prop_atoms.net_wm_action_move;
1302     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1303         actions[num++] = prop_atoms.net_wm_action_minimize;
1304     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1305         actions[num++] = prop_atoms.net_wm_action_resize;
1306     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1307         actions[num++] = prop_atoms.net_wm_action_fullscreen;
1308     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1309         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1310         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1311     }
1312
1313     PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1314
1315     /* make sure the window isn't breaking any rules now */
1316
1317     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1318         if (self->frame) client_shade(self, FALSE);
1319         else self->shaded = FALSE;
1320     }
1321     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1322         if (self->frame) client_iconify(self, FALSE, TRUE);
1323         else self->iconic = FALSE;
1324     }
1325     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1326         if (self->frame) client_fullscreen(self, FALSE, TRUE);
1327         else self->fullscreen = FALSE;
1328     }
1329     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1330                                                          self->max_vert)) {
1331         if (self->frame) client_maximize(self, FALSE, 0, TRUE);
1332         else self->max_vert = self->max_horz = FALSE;
1333     }
1334 }
1335
1336 void client_reconfigure(ObClient *self)
1337 {
1338     /* by making this pass FALSE for user, we avoid the emacs event storm where
1339        every configurenotify causes an update in its normal hints, i think this
1340        is generally what we want anyways... */
1341     client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
1342                      self->area.width, self->area.height, FALSE, TRUE);
1343 }
1344
1345 void client_update_wmhints(ObClient *self)
1346 {
1347     XWMHints *hints;
1348     gboolean ur = FALSE;
1349     GSList *it;
1350
1351     /* assume a window takes input if it doesnt specify */
1352     self->can_focus = TRUE;
1353   
1354     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1355         if (hints->flags & InputHint)
1356             self->can_focus = hints->input;
1357
1358         /* only do this when first managing the window *AND* when we aren't
1359            starting up! */
1360         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1361             if (hints->flags & StateHint)
1362                 self->iconic = hints->initial_state == IconicState;
1363
1364         if (hints->flags & XUrgencyHint)
1365             ur = TRUE;
1366
1367         if (!(hints->flags & WindowGroupHint))
1368             hints->window_group = None;
1369
1370         /* did the group state change? */
1371         if (hints->window_group !=
1372             (self->group ? self->group->leader : None)) {
1373             /* remove from the old group if there was one */
1374             if (self->group != NULL) {
1375                 /* remove transients of the group */
1376                 for (it = self->group->members; it; it = g_slist_next(it))
1377                     self->transients = g_slist_remove(self->transients,
1378                                                       it->data);
1379
1380                 /* remove myself from parents in the group */
1381                 if (self->transient_for == OB_TRAN_GROUP) {
1382                     for (it = self->group->members; it;
1383                          it = g_slist_next(it))
1384                     {
1385                         ObClient *c = it->data;
1386
1387                         if (c != self && !c->transient_for)
1388                             c->transients = g_slist_remove(c->transients,
1389                                                            self);
1390                     }
1391                 }
1392
1393                 group_remove(self->group, self);
1394                 self->group = NULL;
1395             }
1396             if (hints->window_group != None) {
1397                 self->group = group_add(hints->window_group, self);
1398
1399                 /* i can only have transients from the group if i am not
1400                    transient myself */
1401                 if (!self->transient_for) {
1402                     /* add other transients of the group that are already
1403                        set up */
1404                     for (it = self->group->members; it;
1405                          it = g_slist_next(it))
1406                     {
1407                         ObClient *c = it->data;
1408                         if (c != self && c->transient_for == OB_TRAN_GROUP)
1409                             self->transients =
1410                                 g_slist_append(self->transients, c);
1411                     }
1412                 }
1413             }
1414
1415             /* because the self->transient flag wont change from this call,
1416                we don't need to update the window's type and such, only its
1417                transient_for, and the transients lists of other windows in
1418                the group may be affected */
1419             client_update_transient_for(self);
1420         }
1421
1422         /* the WM_HINTS can contain an icon */
1423         client_update_icons(self);
1424
1425         XFree(hints);
1426     }
1427
1428     if (ur != self->urgent) {
1429         self->urgent = ur;
1430         /* fire the urgent callback if we're mapped, otherwise, wait until
1431            after we're mapped */
1432         if (self->frame)
1433             client_urgent_notify(self);
1434     }
1435 }
1436
1437 void client_update_title(ObClient *self)
1438 {
1439     GList *it;
1440     guint32 nums;
1441     guint i;
1442     gchar *data = NULL;
1443     gboolean read_title;
1444     gchar *old_title;
1445
1446     old_title = self->title;
1447      
1448     /* try netwm */
1449     if (!PROP_GETS(self->window, net_wm_name, utf8, &data))
1450         /* try old x stuff */
1451         if (!PROP_GETS(self->window, wm_name, locale, &data))
1452             // http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1453             if (self->transient)
1454                 data = g_strdup("");
1455             else
1456                 data = g_strdup("Unnamed Window");
1457
1458     /* did the title change? then reset the title_count */
1459     if (old_title && 0 != strncmp(old_title, data, strlen(data)))
1460         self->title_count = 1;
1461
1462     /* look for duplicates and append a number */
1463     nums = 0;
1464     for (it = client_list; it; it = g_list_next(it))
1465         if (it->data != self) {
1466             ObClient *c = it->data;
1467             if (0 == strncmp(c->title, data, strlen(data)))
1468                 nums |= 1 << c->title_count;
1469         }
1470     /* find first free number */
1471     for (i = 1; i <= 32; ++i)
1472         if (!(nums & (1 << i))) {
1473             if (self->title_count == 1 || i == 1)
1474                 self->title_count = i;
1475             break;
1476         }
1477     /* dont display the number for the first window */
1478     if (self->title_count > 1) {
1479         gchar *ndata;
1480         ndata = g_strdup_printf("%s - [%u]", data, self->title_count);
1481         g_free(data);
1482         data = ndata;
1483     }
1484
1485     PROP_SETS(self->window, net_wm_visible_name, data);
1486
1487     self->title = data;
1488
1489     if (self->frame)
1490         frame_adjust_title(self->frame);
1491
1492     g_free(old_title);
1493
1494     /* update the icon title */
1495     data = NULL;
1496     g_free(self->icon_title);
1497
1498     read_title = TRUE;
1499     /* try netwm */
1500     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1501         /* try old x stuff */
1502         if (!PROP_GETS(self->window, wm_icon_name, locale, &data)) {
1503             data = g_strdup(self->title);
1504             read_title = FALSE;
1505         }
1506
1507     /* append the title count, dont display the number for the first window */
1508     if (read_title && self->title_count > 1) {
1509         gchar *vdata, *ndata;
1510         ndata = g_strdup_printf(" - [%u]", self->title_count);
1511         vdata = g_strconcat(data, ndata, NULL);
1512         g_free(ndata);
1513         g_free(data);
1514         data = vdata;
1515     }
1516
1517     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1518
1519     self->icon_title = data;
1520 }
1521
1522 void client_update_class(ObClient *self)
1523 {
1524     gchar **data;
1525     gchar *s;
1526
1527     if (self->name) g_free(self->name);
1528     if (self->class) g_free(self->class);
1529     if (self->role) g_free(self->role);
1530
1531     self->name = self->class = self->role = NULL;
1532
1533     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1534         if (data[0]) {
1535             self->name = g_strdup(data[0]);
1536             if (data[1])
1537                 self->class = g_strdup(data[1]);
1538         }
1539         g_strfreev(data);     
1540     }
1541
1542     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1543         self->role = s;
1544
1545     if (self->name == NULL) self->name = g_strdup("");
1546     if (self->class == NULL) self->class = g_strdup("");
1547     if (self->role == NULL) self->role = g_strdup("");
1548 }
1549
1550 void client_update_strut(ObClient *self)
1551 {
1552     guint num;
1553     guint32 *data;
1554     gboolean got = FALSE;
1555     StrutPartial strut;
1556
1557     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1558                     &data, &num)) {
1559         if (num == 12) {
1560             got = TRUE;
1561             STRUT_PARTIAL_SET(strut,
1562                               data[0], data[2], data[1], data[3],
1563                               data[4], data[5], data[8], data[9],
1564                               data[6], data[7], data[10], data[11]);
1565         }
1566         g_free(data);
1567     }
1568
1569     if (!got &&
1570         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1571         if (num == 4) {
1572             const Rect *a;
1573
1574             got = TRUE;
1575
1576             /* use the screen's width/height */
1577             a = screen_physical_area();
1578
1579             STRUT_PARTIAL_SET(strut,
1580                               data[0], data[2], data[1], data[3],
1581                               a->y, a->y + a->height - 1,
1582                               a->x, a->x + a->width - 1,
1583                               a->y, a->y + a->height - 1,
1584                               a->x, a->x + a->width - 1);
1585         }
1586         g_free(data);
1587     }
1588
1589     if (!got)
1590         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1591                           0, 0, 0, 0, 0, 0, 0, 0);
1592
1593     if (!STRUT_EQUAL(strut, self->strut)) {
1594         self->strut = strut;
1595
1596         /* updating here is pointless while we're being mapped cuz we're not in
1597            the client list yet */
1598         if (self->frame)
1599             screen_update_areas();
1600     }
1601 }
1602
1603 void client_update_icons(ObClient *self)
1604 {
1605     guint num;
1606     guint32 *data;
1607     guint w, h, i, j;
1608
1609     for (i = 0; i < self->nicons; ++i)
1610         g_free(self->icons[i].data);
1611     if (self->nicons > 0)
1612         g_free(self->icons);
1613     self->nicons = 0;
1614
1615     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1616         /* figure out how many valid icons are in here */
1617         i = 0;
1618         while (num - i > 2) {
1619             w = data[i++];
1620             h = data[i++];
1621             i += w * h;
1622             if (i > num || w*h == 0) break;
1623             ++self->nicons;
1624         }
1625
1626         self->icons = g_new(ObClientIcon, self->nicons);
1627     
1628         /* store the icons */
1629         i = 0;
1630         for (j = 0; j < self->nicons; ++j) {
1631             guint x, y, t;
1632
1633             w = self->icons[j].width = data[i++];
1634             h = self->icons[j].height = data[i++];
1635
1636             if (w*h == 0) continue;
1637
1638             self->icons[j].data = g_new(RrPixel32, w * h);
1639             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1640                 if (x >= w) {
1641                     x = 0;
1642                     ++y;
1643                 }
1644                 self->icons[j].data[t] =
1645                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1646                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1647                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1648                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1649             }
1650             g_assert(i <= num);
1651         }
1652
1653         g_free(data);
1654     } else if (PROP_GETA32(self->window, kwm_win_icon,
1655                            kwm_win_icon, &data, &num)) {
1656         if (num == 2) {
1657             self->nicons++;
1658             self->icons = g_new(ObClientIcon, self->nicons);
1659             xerror_set_ignore(TRUE);
1660             if (!RrPixmapToRGBA(ob_rr_inst,
1661                                 data[0], data[1],
1662                                 &self->icons[self->nicons-1].width,
1663                                 &self->icons[self->nicons-1].height,
1664                                 &self->icons[self->nicons-1].data)) {
1665                 g_free(&self->icons[self->nicons-1]);
1666                 self->nicons--;
1667             }
1668             xerror_set_ignore(FALSE);
1669         }
1670         g_free(data);
1671     } else {
1672         XWMHints *hints;
1673
1674         if ((hints = XGetWMHints(ob_display, self->window))) {
1675             if (hints->flags & IconPixmapHint) {
1676                 self->nicons++;
1677                 self->icons = g_new(ObClientIcon, self->nicons);
1678                 xerror_set_ignore(TRUE);
1679                 if (!RrPixmapToRGBA(ob_rr_inst,
1680                                     hints->icon_pixmap,
1681                                     (hints->flags & IconMaskHint ?
1682                                      hints->icon_mask : None),
1683                                     &self->icons[self->nicons-1].width,
1684                                     &self->icons[self->nicons-1].height,
1685                                     &self->icons[self->nicons-1].data)){
1686                     g_free(&self->icons[self->nicons-1]);
1687                     self->nicons--;
1688                 }
1689                 xerror_set_ignore(FALSE);
1690             }
1691             XFree(hints);
1692         }
1693     }
1694
1695     if (self->frame)
1696         frame_adjust_icon(self->frame);
1697 }
1698
1699 static void client_change_state(ObClient *self)
1700 {
1701     guint32 state[2];
1702     guint32 netstate[11];
1703     guint num;
1704
1705     state[0] = self->wmstate;
1706     state[1] = None;
1707     PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1708
1709     num = 0;
1710     if (self->modal)
1711         netstate[num++] = prop_atoms.net_wm_state_modal;
1712     if (self->shaded)
1713         netstate[num++] = prop_atoms.net_wm_state_shaded;
1714     if (self->iconic)
1715         netstate[num++] = prop_atoms.net_wm_state_hidden;
1716     if (self->skip_taskbar)
1717         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1718     if (self->skip_pager)
1719         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1720     if (self->fullscreen)
1721         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1722     if (self->max_vert)
1723         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1724     if (self->max_horz)
1725         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1726     if (self->above)
1727         netstate[num++] = prop_atoms.net_wm_state_above;
1728     if (self->below)
1729         netstate[num++] = prop_atoms.net_wm_state_below;
1730     if (self->undecorated)
1731         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1732     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1733
1734     client_calc_layer(self);
1735
1736     if (self->frame)
1737         frame_adjust_state(self->frame);
1738 }
1739
1740 ObClient *client_search_focus_tree(ObClient *self)
1741 {
1742     GSList *it;
1743     ObClient *ret;
1744
1745     for (it = self->transients; it; it = g_slist_next(it)) {
1746         if (client_focused(it->data)) return it->data;
1747         if ((ret = client_search_focus_tree(it->data))) return ret;
1748     }
1749     return NULL;
1750 }
1751
1752 ObClient *client_search_focus_tree_full(ObClient *self)
1753 {
1754     if (self->transient_for) {
1755         if (self->transient_for != OB_TRAN_GROUP) {
1756             return client_search_focus_tree_full(self->transient_for);
1757         } else {
1758             GSList *it;
1759             gboolean recursed = FALSE;
1760         
1761             for (it = self->group->members; it; it = g_slist_next(it))
1762                 if (!((ObClient*)it->data)->transient_for) {
1763                     ObClient *c;
1764                     if ((c = client_search_focus_tree_full(it->data)))
1765                         return c;
1766                     recursed = TRUE;
1767                 }
1768             if (recursed)
1769                 return NULL;
1770         }
1771     }
1772
1773     /* this function checks the whole tree, the client_search_focus_tree~
1774        does not, so we need to check this window */
1775     if (client_focused(self))
1776         return self;
1777     return client_search_focus_tree(self);
1778 }
1779
1780 static ObStackingLayer calc_layer(ObClient *self)
1781 {
1782     ObStackingLayer l;
1783
1784     if (self->fullscreen &&
1785         (client_focused(self) || client_search_focus_tree(self)))
1786         l = OB_STACKING_LAYER_FULLSCREEN;
1787     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1788         l = OB_STACKING_LAYER_DESKTOP;
1789     else if (self->type == OB_CLIENT_TYPE_DOCK) {
1790         if (self->above) l = OB_STACKING_LAYER_DOCK_ABOVE;
1791         else if (self->below) l = OB_STACKING_LAYER_DOCK_BELOW;
1792         else l = OB_STACKING_LAYER_DOCK_NORMAL;
1793     }
1794     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1795     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1796     else l = OB_STACKING_LAYER_NORMAL;
1797
1798     return l;
1799 }
1800
1801 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1802                                         ObStackingLayer l, gboolean raised)
1803 {
1804     ObStackingLayer old, own;
1805     GSList *it;
1806
1807     old = self->layer;
1808     own = calc_layer(self);
1809     self->layer = l > own ? l : own;
1810
1811     for (it = self->transients; it; it = g_slist_next(it))
1812         client_calc_layer_recursive(it->data, orig,
1813                                     l, raised ? raised : l != old);
1814
1815     if (!raised && l != old)
1816         if (orig->frame) { /* only restack if the original window is managed */
1817             stacking_remove(CLIENT_AS_WINDOW(self));
1818             stacking_add(CLIENT_AS_WINDOW(self));
1819         }
1820 }
1821
1822 void client_calc_layer(ObClient *self)
1823 {
1824     ObStackingLayer l;
1825     ObClient *orig;
1826
1827     orig = self;
1828
1829     /* transients take on the layer of their parents */
1830     self = client_search_top_transient(self);
1831
1832     l = calc_layer(self);
1833
1834     client_calc_layer_recursive(self, orig, l, FALSE);
1835 }
1836
1837 gboolean client_should_show(ObClient *self)
1838 {
1839     if (self->iconic)
1840         return FALSE;
1841     if (client_normal(self) && screen_showing_desktop)
1842         return FALSE;
1843     /*
1844     if (self->transient_for) {
1845         if (self->transient_for != OB_TRAN_GROUP)
1846             return client_should_show(self->transient_for);
1847         else {
1848             GSList *it;
1849
1850             for (it = self->group->members; it; it = g_slist_next(it)) {
1851                 ObClient *c = it->data;
1852                 if (c != self && !c->transient_for) {
1853                     if (client_should_show(c))
1854                         return TRUE;
1855                 }
1856             }
1857         }
1858     }
1859     */
1860     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
1861         return TRUE;
1862     
1863     return FALSE;
1864 }
1865
1866 static void client_showhide(ObClient *self)
1867 {
1868
1869     if (client_should_show(self))
1870         frame_show(self->frame);
1871     else
1872         frame_hide(self->frame);
1873 }
1874
1875 gboolean client_normal(ObClient *self) {
1876     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
1877               self->type == OB_CLIENT_TYPE_DOCK ||
1878               self->type == OB_CLIENT_TYPE_SPLASH);
1879 }
1880
1881 static void client_apply_startup_state(ObClient *self)
1882 {
1883     /* these are in a carefully crafted order.. */
1884
1885     if (self->iconic) {
1886         self->iconic = FALSE;
1887         client_iconify(self, TRUE, FALSE);
1888     }
1889     if (self->fullscreen) {
1890         self->fullscreen = FALSE;
1891         client_fullscreen(self, TRUE, FALSE);
1892     }
1893     if (self->undecorated) {
1894         self->undecorated = FALSE;
1895         client_set_undecorated(self, TRUE);
1896     }
1897     if (self->shaded) {
1898         self->shaded = FALSE;
1899         client_shade(self, TRUE);
1900     }
1901     if (self->urgent)
1902         client_urgent_notify(self);
1903   
1904     if (self->max_vert && self->max_horz) {
1905         self->max_vert = self->max_horz = FALSE;
1906         client_maximize(self, TRUE, 0, FALSE);
1907     } else if (self->max_vert) {
1908         self->max_vert = FALSE;
1909         client_maximize(self, TRUE, 2, FALSE);
1910     } else if (self->max_horz) {
1911         self->max_horz = FALSE;
1912         client_maximize(self, TRUE, 1, FALSE);
1913     }
1914
1915     /* nothing to do for the other states:
1916        skip_taskbar
1917        skip_pager
1918        modal
1919        above
1920        below
1921     */
1922 }
1923
1924 void client_configure_full(ObClient *self, ObCorner anchor,
1925                            gint x, gint y, gint w, gint h,
1926                            gboolean user, gboolean final,
1927                            gboolean force_reply)
1928 {
1929     gint oldw, oldh;
1930     gboolean send_resize_client;
1931     gboolean moved = FALSE, resized = FALSE;
1932     guint fdecor = self->frame->decorations;
1933     gboolean fhorz = self->frame->max_horz;
1934
1935     /* make the frame recalculate its dimentions n shit without changing
1936        anything visible for real, this way the constraints below can work with
1937        the updated frame dimensions. */
1938     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
1939
1940     /* gets the frame's position */
1941     frame_client_gravity(self->frame, &x, &y);
1942
1943     /* these positions are frame positions, not client positions */
1944
1945     /* set the size and position if fullscreen */
1946     if (self->fullscreen) {
1947 #ifdef VIDMODE
1948         gint dot;
1949         XF86VidModeModeLine mode;
1950 #endif
1951         Rect *a;
1952         guint i;
1953
1954         i = client_monitor(self);
1955         a = screen_physical_area_monitor(i);
1956
1957 #ifdef VIDMODE
1958         if (i == 0 && /* primary head */
1959             extensions_vidmode &&
1960             XF86VidModeGetViewPort(ob_display, ob_screen, &x, &y) &&
1961             /* get the mode last so the mode.privsize isnt freed incorrectly */
1962             XF86VidModeGetModeLine(ob_display, ob_screen, &dot, &mode)) {
1963             x += a->x;
1964             y += a->y;
1965             w = mode.hdisplay;
1966             h = mode.vdisplay;
1967             if (mode.privsize) XFree(mode.private);
1968         } else
1969 #endif
1970         {
1971             x = a->x;
1972             y = a->y;
1973             w = a->width;
1974             h = a->height;
1975         }
1976
1977         user = FALSE; /* ignore that increment etc shit when in fullscreen */
1978     } else {
1979         Rect *a;
1980
1981         a = screen_area_monitor(self->desktop, client_monitor(self));
1982
1983         /* set the size and position if maximized */
1984         if (self->max_horz) {
1985             x = a->x;
1986             w = a->width - self->frame->size.left - self->frame->size.right;
1987         }
1988         if (self->max_vert) {
1989             y = a->y;
1990             h = a->height - self->frame->size.top - self->frame->size.bottom;
1991         }
1992     }
1993
1994     /* gets the client's position */
1995     frame_frame_gravity(self->frame, &x, &y);
1996
1997     /* these override the above states! if you cant move you can't move! */
1998     if (user) {
1999         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2000             x = self->area.x;
2001             y = self->area.y;
2002         }
2003         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2004             w = self->area.width;
2005             h = self->area.height;
2006         }
2007     }
2008
2009     if (!(w == self->area.width && h == self->area.height)) {
2010         gint basew, baseh, minw, minh;
2011
2012         /* base size is substituted with min size if not specified */
2013         if (self->base_size.width || self->base_size.height) {
2014             basew = self->base_size.width;
2015             baseh = self->base_size.height;
2016         } else {
2017             basew = self->min_size.width;
2018             baseh = self->min_size.height;
2019         }
2020         /* min size is substituted with base size if not specified */
2021         if (self->min_size.width || self->min_size.height) {
2022             minw = self->min_size.width;
2023             minh = self->min_size.height;
2024         } else {
2025             minw = self->base_size.width;
2026             minh = self->base_size.height;
2027         }
2028
2029         /* if this is a user-requested resize, then check against min/max
2030            sizes */
2031
2032         /* smaller than min size or bigger than max size? */
2033         if (w > self->max_size.width) w = self->max_size.width;
2034         if (w < minw) w = minw;
2035         if (h > self->max_size.height) h = self->max_size.height;
2036         if (h < minh) h = minh;
2037
2038         w -= basew;
2039         h -= baseh;
2040
2041         /* keep to the increments */
2042         w /= self->size_inc.width;
2043         h /= self->size_inc.height;
2044
2045         /* you cannot resize to nothing */
2046         if (basew + w < 1) w = 1 - basew;
2047         if (baseh + h < 1) h = 1 - baseh;
2048   
2049         /* store the logical size */
2050         SIZE_SET(self->logical_size,
2051                  self->size_inc.width > 1 ? w : w + basew,
2052                  self->size_inc.height > 1 ? h : h + baseh);
2053
2054         w *= self->size_inc.width;
2055         h *= self->size_inc.height;
2056
2057         w += basew;
2058         h += baseh;
2059
2060         /* adjust the height to match the width for the aspect ratios.
2061            for this, min size is not substituted for base size ever. */
2062         w -= self->base_size.width;
2063         h -= self->base_size.height;
2064
2065         if (!self->fullscreen) {
2066             if (self->min_ratio)
2067                 if (h * self->min_ratio > w) {
2068                     h = (gint)(w / self->min_ratio);
2069
2070                     /* you cannot resize to nothing */
2071                     if (h < 1) {
2072                         h = 1;
2073                         w = (gint)(h * self->min_ratio);
2074                     }
2075                 }
2076             if (self->max_ratio)
2077                 if (h * self->max_ratio < w) {
2078                     h = (gint)(w / self->max_ratio);
2079
2080                     /* you cannot resize to nothing */
2081                     if (h < 1) {
2082                         h = 1;
2083                         w = (gint)(h * self->min_ratio);
2084                     }
2085                 }
2086         }
2087
2088         w += self->base_size.width;
2089         h += self->base_size.height;
2090     }
2091
2092     g_assert(w > 0);
2093     g_assert(h > 0);
2094
2095     switch (anchor) {
2096     case OB_CORNER_TOPLEFT:
2097         break;
2098     case OB_CORNER_TOPRIGHT:
2099         x -= w - self->area.width;
2100         break;
2101     case OB_CORNER_BOTTOMLEFT:
2102         y -= h - self->area.height;
2103         break;
2104     case OB_CORNER_BOTTOMRIGHT:
2105         x -= w - self->area.width;
2106         y -= h - self->area.height;
2107         break;
2108     }
2109
2110     moved = x != self->area.x || y != self->area.y;
2111     resized = w != self->area.width || h != self->area.height;
2112
2113     oldw = self->area.width;
2114     oldh = self->area.height;
2115     RECT_SET(self->area, x, y, w, h);
2116
2117     /* for app-requested resizes, always resize if 'resized' is true.
2118        for user-requested ones, only resize if final is true, or when
2119        resizing in redraw mode */
2120     send_resize_client = ((!user && resized) ||
2121                           (user && (final ||
2122                                     (resized && config_resize_redraw))));
2123
2124     /* if the client is enlarging, then resize the client before the frame */
2125     if (send_resize_client && user && (w > oldw || h > oldh))
2126         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2127
2128     /* move/resize the frame to match the request */
2129     if (self->frame) {
2130         if (self->decorations != fdecor || self->max_horz != fhorz)
2131             moved = resized = TRUE;
2132
2133         if (moved || resized)
2134             frame_adjust_area(self->frame, moved, resized, FALSE);
2135
2136         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2137         {
2138             XEvent event;
2139             event.type = ConfigureNotify;
2140             event.xconfigure.display = ob_display;
2141             event.xconfigure.event = self->window;
2142             event.xconfigure.window = self->window;
2143
2144             /* root window real coords */
2145             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2146                 self->border_width;
2147             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2148                 self->border_width;
2149             event.xconfigure.width = w;
2150             event.xconfigure.height = h;
2151             event.xconfigure.border_width = 0;
2152             event.xconfigure.above = self->frame->plate;
2153             event.xconfigure.override_redirect = FALSE;
2154             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2155                        FALSE, StructureNotifyMask, &event);
2156         }
2157     }
2158
2159     /* if the client is shrinking, then resize the frame before the client */
2160     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2161         XResizeWindow(ob_display, self->window, w, h);
2162
2163     XFlush(ob_display);
2164 }
2165
2166 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2167 {
2168     gint x, y, w, h;
2169
2170     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2171         self->fullscreen == fs) return;                   /* already done */
2172
2173     self->fullscreen = fs;
2174     client_change_state(self); /* change the state hints on the client,
2175                                   and adjust out layer/stacking */
2176
2177     if (fs) {
2178         if (savearea)
2179             self->pre_fullscreen_area = self->area;
2180
2181         /* these are not actually used cuz client_configure will set them
2182            as appropriate when the window is fullscreened */
2183         x = y = w = h = 0;
2184     } else {
2185         Rect *a;
2186
2187         if (self->pre_fullscreen_area.width > 0 &&
2188             self->pre_fullscreen_area.height > 0)
2189         {
2190             x = self->pre_fullscreen_area.x;
2191             y = self->pre_fullscreen_area.y;
2192             w = self->pre_fullscreen_area.width;
2193             h = self->pre_fullscreen_area.height;
2194             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2195         } else {
2196             /* pick some fallbacks... */
2197             a = screen_area_monitor(self->desktop, 0);
2198             x = a->x + a->width / 4;
2199             y = a->y + a->height / 4;
2200             w = a->width / 2;
2201             h = a->height / 2;
2202         }
2203     }
2204
2205     client_setup_decor_and_functions(self);
2206
2207     client_move_resize(self, x, y, w, h);
2208
2209     /* try focus us when we go into fullscreen mode */
2210     client_focus(self);
2211 }
2212
2213 static void client_iconify_recursive(ObClient *self,
2214                                      gboolean iconic, gboolean curdesk)
2215 {
2216     GSList *it;
2217     gboolean changed = FALSE;
2218
2219
2220     if (self->iconic != iconic) {
2221         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2222                  self->window);
2223
2224         self->iconic = iconic;
2225
2226         if (iconic) {
2227             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2228                 glong old;
2229
2230                 old = self->wmstate;
2231                 self->wmstate = IconicState;
2232                 if (old != self->wmstate)
2233                     PROP_MSG(self->window, kde_wm_change_state,
2234                              self->wmstate, 1, 0, 0);
2235
2236                 /* update the focus lists.. iconic windows go to the bottom of
2237                    the list, put the new iconic window at the 'top of the
2238                    bottom'. */
2239                 focus_order_to_top(self);
2240
2241                 changed = TRUE;
2242             }
2243         } else {
2244             glong old;
2245
2246             if (curdesk)
2247                 client_set_desktop(self, screen_desktop, FALSE);
2248
2249             old = self->wmstate;
2250             self->wmstate = self->shaded ? IconicState : NormalState;
2251             if (old != self->wmstate)
2252                 PROP_MSG(self->window, kde_wm_change_state,
2253                          self->wmstate, 1, 0, 0);
2254
2255             /* this puts it after the current focused window */
2256             focus_order_remove(self);
2257             focus_order_add_new(self);
2258
2259             /* this is here cuz with the VIDMODE extension, the viewport can
2260                change while a fullscreen window is iconic, and when it
2261                uniconifies, it would be nice if it did so to the new position
2262                of the viewport */
2263             client_reconfigure(self);
2264
2265             changed = TRUE;
2266         }
2267     }
2268
2269     if (changed) {
2270         client_change_state(self);
2271         client_showhide(self);
2272         screen_update_areas();
2273     }
2274
2275     /* iconify all transients */
2276     for (it = self->transients; it; it = g_slist_next(it))
2277         if (it->data != self) client_iconify_recursive(it->data,
2278                                                        iconic, curdesk);
2279 }
2280
2281 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2282 {
2283     /* move up the transient chain as far as possible first */
2284     self = client_search_top_transient(self);
2285
2286     client_iconify_recursive(client_search_top_transient(self),
2287                              iconic, curdesk);
2288 }
2289
2290 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2291 {
2292     gint x, y, w, h;
2293      
2294     g_assert(dir == 0 || dir == 1 || dir == 2);
2295     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2296
2297     /* check if already done */
2298     if (max) {
2299         if (dir == 0 && self->max_horz && self->max_vert) return;
2300         if (dir == 1 && self->max_horz) return;
2301         if (dir == 2 && self->max_vert) return;
2302     } else {
2303         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2304         if (dir == 1 && !self->max_horz) return;
2305         if (dir == 2 && !self->max_vert) return;
2306     }
2307
2308     /* we just tell it to configure in the same place and client_configure
2309        worries about filling the screen with the window */
2310     x = self->area.x;
2311     y = self->area.y;
2312     w = self->area.width;
2313     h = self->area.height;
2314
2315     if (max) {
2316         if (savearea) {
2317             if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2318                 RECT_SET(self->pre_max_area,
2319                          self->area.x, self->pre_max_area.y,
2320                          self->area.width, self->pre_max_area.height);
2321             }
2322             if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2323                 RECT_SET(self->pre_max_area,
2324                          self->pre_max_area.x, self->area.y,
2325                          self->pre_max_area.width, self->area.height);
2326             }
2327         }
2328     } else {
2329         Rect *a;
2330
2331         a = screen_area_monitor(self->desktop, 0);
2332         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2333             if (self->pre_max_area.width > 0) {
2334                 x = self->pre_max_area.x;
2335                 w = self->pre_max_area.width;
2336
2337                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2338                          0, self->pre_max_area.height);
2339             } else {
2340                 /* pick some fallbacks... */
2341                 x = a->x + a->width / 4;
2342                 w = a->width / 2;
2343             }
2344         }
2345         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2346             if (self->pre_max_area.height > 0) {
2347                 y = self->pre_max_area.y;
2348                 h = self->pre_max_area.height;
2349
2350                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2351                          self->pre_max_area.width, 0);
2352             } else {
2353                 /* pick some fallbacks... */
2354                 y = a->y + a->height / 4;
2355                 h = a->height / 2;
2356             }
2357         }
2358     }
2359
2360     if (dir == 0 || dir == 1) /* horz */
2361         self->max_horz = max;
2362     if (dir == 0 || dir == 2) /* vert */
2363         self->max_vert = max;
2364
2365     client_change_state(self); /* change the state hints on the client */
2366
2367     client_setup_decor_and_functions(self);
2368
2369     client_move_resize(self, x, y, w, h);
2370 }
2371
2372 void client_shade(ObClient *self, gboolean shade)
2373 {
2374     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2375          shade) ||                         /* can't shade */
2376         self->shaded == shade) return;     /* already done */
2377
2378     /* when we're iconic, don't change the wmstate */
2379     if (!self->iconic) {
2380         glong old;
2381
2382         old = self->wmstate;
2383         self->wmstate = shade ? IconicState : NormalState;
2384         if (old != self->wmstate)
2385             PROP_MSG(self->window, kde_wm_change_state,
2386                      self->wmstate, 1, 0, 0);
2387     }
2388
2389     self->shaded = shade;
2390     client_change_state(self);
2391     /* resize the frame to just the titlebar */
2392     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2393 }
2394
2395 void client_close(ObClient *self)
2396 {
2397     XEvent ce;
2398
2399     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2400
2401     /* in the case that the client provides no means to requesting that it
2402        close, we just kill it */
2403     if (!self->delete_window)
2404         client_kill(self);
2405     
2406     /*
2407       XXX: itd be cool to do timeouts and shit here for killing the client's
2408       process off
2409       like... if the window is around after 5 seconds, then the close button
2410       turns a nice red, and if this function is called again, the client is
2411       explicitly killed.
2412     */
2413
2414     ce.xclient.type = ClientMessage;
2415     ce.xclient.message_type =  prop_atoms.wm_protocols;
2416     ce.xclient.display = ob_display;
2417     ce.xclient.window = self->window;
2418     ce.xclient.format = 32;
2419     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2420     ce.xclient.data.l[1] = event_lasttime;
2421     ce.xclient.data.l[2] = 0l;
2422     ce.xclient.data.l[3] = 0l;
2423     ce.xclient.data.l[4] = 0l;
2424     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2425 }
2426
2427 void client_kill(ObClient *self)
2428 {
2429     XKillClient(ob_display, self->window);
2430 }
2431
2432 void client_set_desktop_recursive(ObClient *self,
2433                                   guint target, gboolean donthide)
2434 {
2435     guint old;
2436     GSList *it;
2437
2438     if (target != self->desktop) {
2439
2440         ob_debug("Setting desktop %u\n", target+1);
2441
2442         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2443
2444         /* remove from the old desktop(s) */
2445         focus_order_remove(self);
2446
2447         old = self->desktop;
2448         self->desktop = target;
2449         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2450         /* the frame can display the current desktop state */
2451         frame_adjust_state(self->frame);
2452         /* 'move' the window to the new desktop */
2453         if (!donthide)
2454             client_showhide(self);
2455         /* raise if it was not already on the desktop */
2456         if (old != DESKTOP_ALL)
2457             client_raise(self);
2458         screen_update_areas();
2459
2460         /* add to the new desktop(s) */
2461         if (config_focus_new)
2462             focus_order_to_top(self);
2463         else
2464             focus_order_to_bottom(self);
2465     }
2466
2467     /* move all transients */
2468     for (it = self->transients; it; it = g_slist_next(it))
2469         if (it->data != self) client_set_desktop_recursive(it->data,
2470                                                            target, donthide);
2471 }
2472
2473 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2474 {
2475     client_set_desktop_recursive(client_search_top_transient(self),
2476                                  target, donthide);
2477 }
2478
2479 ObClient *client_search_modal_child(ObClient *self)
2480 {
2481     GSList *it;
2482     ObClient *ret;
2483   
2484     for (it = self->transients; it; it = g_slist_next(it)) {
2485         ObClient *c = it->data;
2486         if ((ret = client_search_modal_child(c))) return ret;
2487         if (c->modal) return c;
2488     }
2489     return NULL;
2490 }
2491
2492 gboolean client_validate(ObClient *self)
2493 {
2494     XEvent e; 
2495
2496     XSync(ob_display, FALSE); /* get all events on the server */
2497
2498     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2499         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2500         XPutBackEvent(ob_display, &e);
2501         return FALSE;
2502     }
2503
2504     return TRUE;
2505 }
2506
2507 void client_set_wm_state(ObClient *self, glong state)
2508 {
2509     if (state == self->wmstate) return; /* no change */
2510   
2511     switch (state) {
2512     case IconicState:
2513         client_iconify(self, TRUE, TRUE);
2514         break;
2515     case NormalState:
2516         client_iconify(self, FALSE, TRUE);
2517         break;
2518     }
2519 }
2520
2521 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2522 {
2523     gboolean shaded = self->shaded;
2524     gboolean fullscreen = self->fullscreen;
2525     gboolean undecorated = self->undecorated;
2526     gboolean max_horz = self->max_horz;
2527     gboolean max_vert = self->max_vert;
2528     gboolean modal = self->modal;
2529     gint i;
2530
2531     if (!(action == prop_atoms.net_wm_state_add ||
2532           action == prop_atoms.net_wm_state_remove ||
2533           action == prop_atoms.net_wm_state_toggle))
2534         /* an invalid action was passed to the client message, ignore it */
2535         return; 
2536
2537     for (i = 0; i < 2; ++i) {
2538         Atom state = i == 0 ? data1 : data2;
2539     
2540         if (!state) continue;
2541
2542         /* if toggling, then pick whether we're adding or removing */
2543         if (action == prop_atoms.net_wm_state_toggle) {
2544             if (state == prop_atoms.net_wm_state_modal)
2545                 action = modal ? prop_atoms.net_wm_state_remove :
2546                     prop_atoms.net_wm_state_add;
2547             else if (state == prop_atoms.net_wm_state_maximized_vert)
2548                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2549                     prop_atoms.net_wm_state_add;
2550             else if (state == prop_atoms.net_wm_state_maximized_horz)
2551                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2552                     prop_atoms.net_wm_state_add;
2553             else if (state == prop_atoms.net_wm_state_shaded)
2554                 action = shaded ? prop_atoms.net_wm_state_remove :
2555                     prop_atoms.net_wm_state_add;
2556             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2557                 action = self->skip_taskbar ?
2558                     prop_atoms.net_wm_state_remove :
2559                     prop_atoms.net_wm_state_add;
2560             else if (state == prop_atoms.net_wm_state_skip_pager)
2561                 action = self->skip_pager ?
2562                     prop_atoms.net_wm_state_remove :
2563                     prop_atoms.net_wm_state_add;
2564             else if (state == prop_atoms.net_wm_state_fullscreen)
2565                 action = fullscreen ?
2566                     prop_atoms.net_wm_state_remove :
2567                     prop_atoms.net_wm_state_add;
2568             else if (state == prop_atoms.net_wm_state_above)
2569                 action = self->above ? prop_atoms.net_wm_state_remove :
2570                     prop_atoms.net_wm_state_add;
2571             else if (state == prop_atoms.net_wm_state_below)
2572                 action = self->below ? prop_atoms.net_wm_state_remove :
2573                     prop_atoms.net_wm_state_add;
2574             else if (state == prop_atoms.ob_wm_state_undecorated)
2575                 action = undecorated ? prop_atoms.net_wm_state_remove :
2576                     prop_atoms.net_wm_state_add;
2577         }
2578     
2579         if (action == prop_atoms.net_wm_state_add) {
2580             if (state == prop_atoms.net_wm_state_modal) {
2581                 modal = TRUE;
2582             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2583                 max_vert = TRUE;
2584             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2585                 max_horz = TRUE;
2586             } else if (state == prop_atoms.net_wm_state_shaded) {
2587                 shaded = TRUE;
2588             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2589                 self->skip_taskbar = TRUE;
2590             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2591                 self->skip_pager = TRUE;
2592             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2593                 fullscreen = TRUE;
2594             } else if (state == prop_atoms.net_wm_state_above) {
2595                 self->above = TRUE;
2596                 self->below = FALSE;
2597             } else if (state == prop_atoms.net_wm_state_below) {
2598                 self->above = FALSE;
2599                 self->below = TRUE;
2600             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2601                 undecorated = TRUE;
2602             }
2603
2604         } else { /* action == prop_atoms.net_wm_state_remove */
2605             if (state == prop_atoms.net_wm_state_modal) {
2606                 modal = FALSE;
2607             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2608                 max_vert = FALSE;
2609             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2610                 max_horz = FALSE;
2611             } else if (state == prop_atoms.net_wm_state_shaded) {
2612                 shaded = FALSE;
2613             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2614                 self->skip_taskbar = FALSE;
2615             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2616                 self->skip_pager = FALSE;
2617             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2618                 fullscreen = FALSE;
2619             } else if (state == prop_atoms.net_wm_state_above) {
2620                 self->above = FALSE;
2621             } else if (state == prop_atoms.net_wm_state_below) {
2622                 self->below = FALSE;
2623             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2624                 undecorated = FALSE;
2625             }
2626         }
2627     }
2628     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2629         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2630             /* toggling both */
2631             if (max_horz == max_vert) { /* both going the same way */
2632                 client_maximize(self, max_horz, 0, TRUE);
2633             } else {
2634                 client_maximize(self, max_horz, 1, TRUE);
2635                 client_maximize(self, max_vert, 2, TRUE);
2636             }
2637         } else {
2638             /* toggling one */
2639             if (max_horz != self->max_horz)
2640                 client_maximize(self, max_horz, 1, TRUE);
2641             else
2642                 client_maximize(self, max_vert, 2, TRUE);
2643         }
2644     }
2645     /* change fullscreen state before shading, as it will affect if the window
2646        can shade or not */
2647     if (fullscreen != self->fullscreen)
2648         client_fullscreen(self, fullscreen, TRUE);
2649     if (shaded != self->shaded)
2650         client_shade(self, shaded);
2651     if (undecorated != self->undecorated)
2652         client_set_undecorated(self, undecorated);
2653     if (modal != self->modal) {
2654         self->modal = modal;
2655         /* when a window changes modality, then its stacking order with its
2656            transients needs to change */
2657         client_raise(self);
2658     }
2659     client_calc_layer(self);
2660     client_change_state(self); /* change the hint to reflect these changes */
2661 }
2662
2663 ObClient *client_focus_target(ObClient *self)
2664 {
2665     ObClient *child;
2666      
2667     /* if we have a modal child, then focus it, not us */
2668     child = client_search_modal_child(client_search_top_transient(self));
2669     if (child) return child;
2670     return self;
2671 }
2672
2673 gboolean client_can_focus(ObClient *self)
2674 {
2675     XEvent ev;
2676
2677     /* choose the correct target */
2678     self = client_focus_target(self);
2679
2680     if (!self->frame->visible)
2681         return FALSE;
2682
2683     if (!(self->can_focus || self->focus_notify))
2684         return FALSE;
2685
2686     /* do a check to see if the window has already been unmapped or destroyed
2687        do this intelligently while watching out for unmaps we've generated
2688        (ignore_unmaps > 0) */
2689     if (XCheckTypedWindowEvent(ob_display, self->window,
2690                                DestroyNotify, &ev)) {
2691         XPutBackEvent(ob_display, &ev);
2692         return FALSE;
2693     }
2694     while (XCheckTypedWindowEvent(ob_display, self->window,
2695                                   UnmapNotify, &ev)) {
2696         if (self->ignore_unmaps) {
2697             self->ignore_unmaps--;
2698         } else {
2699             XPutBackEvent(ob_display, &ev);
2700             return FALSE;
2701         }
2702     }
2703
2704     return TRUE;
2705 }
2706
2707 gboolean client_focus(ObClient *self)
2708 {
2709     /* choose the correct target */
2710     self = client_focus_target(self);
2711
2712     if (!client_can_focus(self)) {
2713         if (!self->frame->visible) {
2714             /* update the focus lists */
2715             focus_order_to_top(self);
2716         }
2717         return FALSE;
2718     }
2719
2720     if (self->can_focus) {
2721         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2722            I choose to use it always, hopefully to find errors quicker, if any
2723            are left. (I hate X. I hate focus events.)
2724            
2725            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2726            #799. So now it is RevertToNone again.
2727         */
2728         XSetInputFocus(ob_display, self->window, RevertToNone,
2729                        event_lasttime);
2730     }
2731
2732     if (self->focus_notify) {
2733         XEvent ce;
2734         ce.xclient.type = ClientMessage;
2735         ce.xclient.message_type = prop_atoms.wm_protocols;
2736         ce.xclient.display = ob_display;
2737         ce.xclient.window = self->window;
2738         ce.xclient.format = 32;
2739         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2740         ce.xclient.data.l[1] = event_lasttime;
2741         ce.xclient.data.l[2] = 0l;
2742         ce.xclient.data.l[3] = 0l;
2743         ce.xclient.data.l[4] = 0l;
2744         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2745     }
2746
2747 #ifdef DEBUG_FOCUS
2748     ob_debug("%sively focusing %lx at %d\n",
2749              (self->can_focus ? "act" : "pass"),
2750              self->window, (gint) event_lasttime);
2751 #endif
2752
2753     /* Cause the FocusIn to come back to us. Important for desktop switches,
2754        since otherwise we'll have no FocusIn on the queue and send it off to
2755        the focus_backup. */
2756     XSync(ob_display, FALSE);
2757     return TRUE;
2758 }
2759
2760 void client_unfocus(ObClient *self)
2761 {
2762     if (focus_client == self) {
2763 #ifdef DEBUG_FOCUS
2764         ob_debug("client_unfocus for %lx\n", self->window);
2765 #endif
2766         focus_fallback(OB_FOCUS_FALLBACK_UNFOCUSING);
2767     }
2768 }
2769
2770 void client_activate(ObClient *self, gboolean here)
2771 {
2772     if (client_normal(self) && screen_showing_desktop)
2773         screen_show_desktop(FALSE);
2774     if (self->iconic)
2775         client_iconify(self, FALSE, here);
2776     if (self->desktop != DESKTOP_ALL &&
2777         self->desktop != screen_desktop) {
2778         if (here)
2779             client_set_desktop(self, screen_desktop, FALSE);
2780         else
2781             screen_set_desktop(self->desktop);
2782     } else if (!self->frame->visible)
2783         /* if its not visible for other reasons, then don't mess
2784            with it */
2785         return;
2786     if (self->shaded)
2787         client_shade(self, FALSE);
2788
2789     client_focus(self);
2790
2791     /* we do this an action here. this is rather important. this is because
2792        we want the results from the focus change to take place BEFORE we go
2793        about raising the window. when a fullscreen window loses focus, we need
2794        this or else the raise wont be able to raise above the to-lose-focus
2795        fullscreen window. */
2796     client_raise(self);
2797 }
2798
2799 void client_raise(ObClient *self)
2800 {
2801     action_run_string("Raise", self);
2802 }
2803
2804 void client_lower(ObClient *self)
2805 {
2806     action_run_string("Lower", self);
2807 }
2808
2809 gboolean client_focused(ObClient *self)
2810 {
2811     return self == focus_client;
2812 }
2813
2814 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
2815 {
2816     guint i;
2817     /* si is the smallest image >= req */
2818     /* li is the largest image < req */
2819     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2820
2821     if (!self->nicons) {
2822         ObClientIcon *parent = NULL;
2823
2824         if (self->transient_for) {
2825             if (self->transient_for != OB_TRAN_GROUP)
2826                 parent = client_icon_recursive(self->transient_for, w, h);
2827             else {
2828                 GSList *it;
2829                 for (it = self->group->members; it; it = g_slist_next(it)) {
2830                     ObClient *c = it->data;
2831                     if (c != self && !c->transient_for) {
2832                         if ((parent = client_icon_recursive(c, w, h)))
2833                             break;
2834                     }
2835                 }
2836             }
2837         }
2838         
2839         return parent;
2840     }
2841
2842     for (i = 0; i < self->nicons; ++i) {
2843         size = self->icons[i].width * self->icons[i].height;
2844         if (size < smallest && size >= (unsigned)(w * h)) {
2845             smallest = size;
2846             si = i;
2847         }
2848         if (size > largest && size <= (unsigned)(w * h)) {
2849             largest = size;
2850             li = i;
2851         }
2852     }
2853     if (largest == 0) /* didnt find one smaller than the requested size */
2854         return &self->icons[si];
2855     return &self->icons[li];
2856 }
2857
2858 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
2859 {
2860     ObClientIcon *ret;
2861     static ObClientIcon deficon;
2862
2863     if (!(ret = client_icon_recursive(self, w, h))) {
2864         deficon.width = deficon.height = 48;
2865         deficon.data = ob_rr_theme->def_win_icon;
2866         ret = &deficon;
2867     }
2868     return ret;
2869 }
2870
2871 /* this be mostly ripped from fvwm */
2872 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2873 {
2874     gint my_cx, my_cy, his_cx, his_cy;
2875     gint offset = 0;
2876     gint distance = 0;
2877     gint score, best_score;
2878     ObClient *best_client, *cur;
2879     GList *it;
2880
2881     if(!client_list)
2882         return NULL;
2883
2884     /* first, find the centre coords of the currently focused window */
2885     my_cx = c->frame->area.x + c->frame->area.width / 2;
2886     my_cy = c->frame->area.y + c->frame->area.height / 2;
2887
2888     best_score = -1;
2889     best_client = NULL;
2890
2891     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
2892         cur = it->data;
2893
2894         /* the currently selected window isn't interesting */
2895         if(cur == c)
2896             continue;
2897         if (!client_normal(cur))
2898             continue;
2899         if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2900             continue;
2901         if(cur->iconic)
2902             continue;
2903         if(client_focus_target(cur) == cur &&
2904            !(cur->can_focus || cur->focus_notify))
2905             continue;
2906
2907         /* find the centre coords of this window, from the
2908          * currently focused window's point of view */
2909         his_cx = (cur->frame->area.x - my_cx)
2910             + cur->frame->area.width / 2;
2911         his_cy = (cur->frame->area.y - my_cy)
2912             + cur->frame->area.height / 2;
2913
2914         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
2915            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
2916             gint tx;
2917             /* Rotate the diagonals 45 degrees counterclockwise.
2918              * To do this, multiply the matrix /+h +h\ with the
2919              * vector (x y).                   \-h +h/
2920              * h = sqrt(0.5). We can set h := 1 since absolute
2921              * distance doesn't matter here. */
2922             tx = his_cx + his_cy;
2923             his_cy = -his_cx + his_cy;
2924             his_cx = tx;
2925         }
2926
2927         switch(dir) {
2928         case OB_DIRECTION_NORTH:
2929         case OB_DIRECTION_SOUTH:
2930         case OB_DIRECTION_NORTHEAST:
2931         case OB_DIRECTION_SOUTHWEST:
2932             offset = (his_cx < 0) ? -his_cx : his_cx;
2933             distance = ((dir == OB_DIRECTION_NORTH ||
2934                          dir == OB_DIRECTION_NORTHEAST) ?
2935                         -his_cy : his_cy);
2936             break;
2937         case OB_DIRECTION_EAST:
2938         case OB_DIRECTION_WEST:
2939         case OB_DIRECTION_SOUTHEAST:
2940         case OB_DIRECTION_NORTHWEST:
2941             offset = (his_cy < 0) ? -his_cy : his_cy;
2942             distance = ((dir == OB_DIRECTION_WEST ||
2943                          dir == OB_DIRECTION_NORTHWEST) ?
2944                         -his_cx : his_cx);
2945             break;
2946         }
2947
2948         /* the target must be in the requested direction */
2949         if(distance <= 0)
2950             continue;
2951
2952         /* Calculate score for this window.  The smaller the better. */
2953         score = distance + offset;
2954
2955         /* windows more than 45 degrees off the direction are
2956          * heavily penalized and will only be chosen if nothing
2957          * else within a million pixels */
2958         if(offset > distance)
2959             score += 1000000;
2960
2961         if(best_score == -1 || score < best_score)
2962             best_client = cur,
2963                 best_score = score;
2964     }
2965
2966     return best_client;
2967 }
2968
2969 void client_set_layer(ObClient *self, gint layer)
2970 {
2971     if (layer < 0) {
2972         self->below = TRUE;
2973         self->above = FALSE;
2974     } else if (layer == 0) {
2975         self->below = self->above = FALSE;
2976     } else {
2977         self->below = FALSE;
2978         self->above = TRUE;
2979     }
2980     client_calc_layer(self);
2981     client_change_state(self); /* reflect this in the state hints */
2982 }
2983
2984 void client_set_undecorated(ObClient *self, gboolean undecorated)
2985 {
2986     if (self->undecorated != undecorated) {
2987         self->undecorated = undecorated;
2988         client_setup_decor_and_functions(self);
2989         client_change_state(self); /* reflect this in the state hints */
2990     }
2991 }
2992
2993 guint client_monitor(ObClient *self)
2994 {
2995     guint i;
2996     guint most = 0;
2997     guint mostv = 0;
2998
2999     for (i = 0; i < screen_num_monitors; ++i) {
3000         Rect *area = screen_physical_area_monitor(i);
3001         if (RECT_INTERSECTS_RECT(*area, self->frame->area)) {
3002             Rect r;
3003             guint v;
3004
3005             RECT_SET_INTERSECTION(r, *area, self->frame->area);
3006             v = r.width * r.height;
3007
3008             if (v > mostv) {
3009                 mostv = v;
3010                 most = i;
3011             }
3012         }
3013     }
3014     return most;
3015 }
3016
3017 ObClient *client_search_top_transient(ObClient *self)
3018 {
3019     /* move up the transient chain as far as possible */
3020     if (self->transient_for) {
3021         if (self->transient_for != OB_TRAN_GROUP) {
3022             return client_search_top_transient(self->transient_for);
3023         } else {
3024             GSList *it;
3025
3026             g_assert(self->group);
3027
3028             for (it = self->group->members; it; it = g_slist_next(it)) {
3029                 ObClient *c = it->data;
3030
3031                 /* checking transient_for prevents infinate loops! */
3032                 if (c != self && !c->transient_for)
3033                     break;
3034             }
3035             if (it)
3036                 return it->data;
3037         }
3038     }
3039
3040     return self;
3041 }
3042
3043 ObClient *client_search_focus_parent(ObClient *self)
3044 {
3045     if (self->transient_for) {
3046         if (self->transient_for != OB_TRAN_GROUP) {
3047             if (client_focused(self->transient_for))
3048                 return self->transient_for;
3049         } else {
3050             GSList *it;
3051
3052             for (it = self->group->members; it; it = g_slist_next(it)) {
3053                 ObClient *c = it->data;
3054
3055                 /* checking transient_for prevents infinate loops! */
3056                 if (c != self && !c->transient_for)
3057                     if (client_focused(c))
3058                         return c;
3059             }
3060         }
3061     }
3062
3063     return NULL;
3064 }
3065
3066 ObClient *client_search_parent(ObClient *self, ObClient *search)
3067 {
3068     if (self->transient_for) {
3069         if (self->transient_for != OB_TRAN_GROUP) {
3070             if (self->transient_for == search)
3071                 return search;
3072         } else {
3073             GSList *it;
3074
3075             for (it = self->group->members; it; it = g_slist_next(it)) {
3076                 ObClient *c = it->data;
3077
3078                 /* checking transient_for prevents infinate loops! */
3079                 if (c != self && !c->transient_for)
3080                     if (c == search)
3081                         return search;
3082             }
3083         }
3084     }
3085
3086     return NULL;
3087 }
3088
3089 ObClient *client_search_transient(ObClient *self, ObClient *search)
3090 {
3091     GSList *sit;
3092
3093     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3094         if (sit->data == search)
3095             return search;
3096         if (client_search_transient(sit->data, search))
3097             return search;
3098     }
3099     return NULL;
3100 }
3101
3102 void client_update_sm_client_id(ObClient *self)
3103 {
3104     g_free(self->sm_client_id);
3105     self->sm_client_id = NULL;
3106
3107     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3108         self->group)
3109         PROP_GETS(self->group->leader, sm_client_id, locale,
3110                   &self->sm_client_id);
3111 }
3112
3113 /* finds the nearest edge in the given direction from the current client
3114  * note to self: the edge is the -frame- edge (the actual one), not the
3115  * client edge.
3116  */
3117 gint client_directional_edge_search(ObClient *c, ObDirection dir)
3118 {
3119     gint dest;
3120     gint my_edge_start, my_edge_end, my_offset;
3121     GList *it;
3122     Rect *a;
3123     
3124     if(!client_list)
3125         return -1;
3126
3127     a = screen_area(c->desktop);
3128
3129     switch(dir) {
3130     case OB_DIRECTION_NORTH:
3131         my_edge_start = c->frame->area.x;
3132         my_edge_end = c->frame->area.x + c->frame->area.width;
3133         my_offset = c->frame->area.y;
3134         
3135         /* default: top of screen */
3136         dest = a->y;
3137
3138         for(it = client_list; it; it = g_list_next(it)) {
3139             gint his_edge_start, his_edge_end, his_offset;
3140             ObClient *cur = it->data;
3141
3142             if(cur == c)
3143                 continue;
3144             if(!client_normal(cur))
3145                 continue;
3146             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3147                 continue;
3148             if(cur->iconic)
3149                 continue;
3150
3151             his_edge_start = cur->frame->area.x;
3152             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3153             his_offset = cur->frame->area.y + cur->frame->area.height;
3154
3155             if(his_offset + 1 > my_offset)
3156                 continue;
3157
3158             if(his_offset < dest)
3159                 continue;
3160             
3161             if(his_edge_start >= my_edge_start &&
3162                his_edge_start <= my_edge_end)
3163                 dest = his_offset;
3164
3165             if(my_edge_start >= his_edge_start &&
3166                my_edge_start <= his_edge_end)
3167                 dest = his_offset;
3168
3169         }
3170         break;
3171     case OB_DIRECTION_SOUTH:
3172         my_edge_start = c->frame->area.x;
3173         my_edge_end = c->frame->area.x + c->frame->area.width;
3174         my_offset = c->frame->area.y + c->frame->area.height;
3175
3176         /* default: bottom of screen */
3177         dest = a->y + a->height;
3178
3179         for(it = client_list; it; it = g_list_next(it)) {
3180             gint his_edge_start, his_edge_end, his_offset;
3181             ObClient *cur = it->data;
3182
3183             if(cur == c)
3184                 continue;
3185             if(!client_normal(cur))
3186                 continue;
3187             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3188                 continue;
3189             if(cur->iconic)
3190                 continue;
3191
3192             his_edge_start = cur->frame->area.x;
3193             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3194             his_offset = cur->frame->area.y;
3195
3196
3197             if(his_offset - 1 < my_offset)
3198                 continue;
3199             
3200             if(his_offset > dest)
3201                 continue;
3202             
3203             if(his_edge_start >= my_edge_start &&
3204                his_edge_start <= my_edge_end)
3205                 dest = his_offset;
3206
3207             if(my_edge_start >= his_edge_start &&
3208                my_edge_start <= his_edge_end)
3209                 dest = his_offset;
3210
3211         }
3212         break;
3213     case OB_DIRECTION_WEST:
3214         my_edge_start = c->frame->area.y;
3215         my_edge_end = c->frame->area.y + c->frame->area.height;
3216         my_offset = c->frame->area.x;
3217
3218         /* default: leftmost egde of screen */
3219         dest = a->x;
3220
3221         for(it = client_list; it; it = g_list_next(it)) {
3222             gint his_edge_start, his_edge_end, his_offset;
3223             ObClient *cur = it->data;
3224
3225             if(cur == c)
3226                 continue;
3227             if(!client_normal(cur))
3228                 continue;
3229             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3230                 continue;
3231             if(cur->iconic)
3232                 continue;
3233
3234             his_edge_start = cur->frame->area.y;
3235             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3236             his_offset = cur->frame->area.x + cur->frame->area.width;
3237
3238             if(his_offset + 1 > my_offset)
3239                 continue;
3240             
3241             if(his_offset < dest)
3242                 continue;
3243             
3244             if(his_edge_start >= my_edge_start &&
3245                his_edge_start <= my_edge_end)
3246                 dest = his_offset;
3247
3248             if(my_edge_start >= his_edge_start &&
3249                my_edge_start <= his_edge_end)
3250                 dest = his_offset;
3251                 
3252
3253         }
3254         break;
3255     case OB_DIRECTION_EAST:
3256         my_edge_start = c->frame->area.y;
3257         my_edge_end = c->frame->area.y + c->frame->area.height;
3258         my_offset = c->frame->area.x + c->frame->area.width;
3259         
3260         /* default: rightmost edge of screen */
3261         dest = a->x + a->width;
3262
3263         for(it = client_list; it; it = g_list_next(it)) {
3264             gint his_edge_start, his_edge_end, his_offset;
3265             ObClient *cur = it->data;
3266
3267             if(cur == c)
3268                 continue;
3269             if(!client_normal(cur))
3270                 continue;
3271             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3272                 continue;
3273             if(cur->iconic)
3274                 continue;
3275
3276             his_edge_start = cur->frame->area.y;
3277             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3278             his_offset = cur->frame->area.x;
3279
3280             if(his_offset - 1 < my_offset)
3281                 continue;
3282             
3283             if(his_offset > dest)
3284                 continue;
3285             
3286             if(his_edge_start >= my_edge_start &&
3287                his_edge_start <= my_edge_end)
3288                 dest = his_offset;
3289
3290             if(my_edge_start >= his_edge_start &&
3291                my_edge_start <= his_edge_end)
3292                 dest = his_offset;
3293
3294         }
3295         break;
3296     case OB_DIRECTION_NORTHEAST:
3297     case OB_DIRECTION_SOUTHEAST:
3298     case OB_DIRECTION_NORTHWEST:
3299     case OB_DIRECTION_SOUTHWEST:
3300         /* not implemented */
3301     default:
3302         g_assert_not_reached();
3303     }
3304     return dest;
3305 }
3306
3307 ObClient* client_under_pointer()
3308 {
3309     gint x, y;
3310     GList *it;
3311     ObClient *ret = NULL;
3312
3313     if (screen_pointer_pos(&x, &y)) {
3314         for (it = stacking_list; it; it = g_list_next(it)) {
3315             if (WINDOW_IS_CLIENT(it->data)) {
3316                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3317                 if (c->frame->visible &&
3318                     RECT_CONTAINS(c->frame->area, x, y)) {
3319                     ret = c;
3320                     break;
3321                 }
3322             }
3323         }
3324     }
3325     return ret;
3326 }
3327
3328 gboolean client_has_group_siblings(ObClient *self)
3329 {
3330     return self->group && self->group->members->next;
3331 }