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