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