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