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