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