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