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