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