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