]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
when setting up decor on a window, it needs to reconfigure twice if the decor changes...
[dana/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     /* gets the frame's position */
1716     frame_client_gravity(self->frame, &x, &y);
1717
1718     /* these positions are frame positions, not client positions */
1719
1720     /* set the size and position if fullscreen */
1721     if (self->fullscreen) {
1722 #ifdef VIDMODE
1723         int dot;
1724         XF86VidModeModeLine mode;
1725 #endif
1726         Rect *a;
1727         guint i;
1728
1729         i = client_monitor(self);
1730         a = screen_physical_area_monitor(i);
1731
1732 #ifdef VIDMODE
1733         if (i == 0 && /* primary head */
1734             extensions_vidmode &&
1735             XF86VidModeGetViewPort(ob_display, ob_screen, &x, &y) &&
1736             /* get the mode last so the mode.privsize isnt freed incorrectly */
1737             XF86VidModeGetModeLine(ob_display, ob_screen, &dot, &mode)) {
1738             x += a->x;
1739             y += a->y;
1740             w = mode.hdisplay;
1741             h = mode.vdisplay;
1742             if (mode.privsize) XFree(mode.private);
1743         } else
1744 #endif
1745         {
1746             x = a->x;
1747             y = a->y;
1748             w = a->width;
1749             h = a->height;
1750         }
1751
1752         user = FALSE; /* ignore that increment etc shit when in fullscreen */
1753     } else {
1754         Rect *a;
1755
1756         a = screen_area_monitor(self->desktop, client_monitor(self));
1757
1758         /* set the size and position if maximized */
1759         if (self->max_horz) {
1760             x = a->x - self->frame->size.left;
1761             w = a->width;
1762         }
1763         if (self->max_vert) {
1764             y = a->y;
1765             h = a->height - self->frame->size.top - self->frame->size.bottom;
1766         }
1767     }
1768
1769     /* gets the client's position */
1770     frame_frame_gravity(self->frame, &x, &y);
1771
1772     /* these override the above states! if you cant move you can't move! */
1773     if (user) {
1774         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
1775             x = self->area.x;
1776             y = self->area.y;
1777         }
1778         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
1779             w = self->area.width;
1780             h = self->area.height;
1781         }
1782     }
1783
1784     if (!(w == self->area.width && h == self->area.height)) {
1785         int basew, baseh, minw, minh;
1786
1787         /* base size is substituted with min size if not specified */
1788         if (self->base_size.width || self->base_size.height) {
1789             basew = self->base_size.width;
1790             baseh = self->base_size.height;
1791         } else {
1792             basew = self->min_size.width;
1793             baseh = self->min_size.height;
1794         }
1795         /* min size is substituted with base size if not specified */
1796         if (self->min_size.width || self->min_size.height) {
1797             minw = self->min_size.width;
1798             minh = self->min_size.height;
1799         } else {
1800             minw = self->base_size.width;
1801             minh = self->base_size.height;
1802         }
1803
1804         if (user) {
1805             /* for interactive resizing. have to move half an increment in each
1806                direction. */
1807
1808             /* how far we are towards the next size inc */
1809             int mw = (w - basew) % self->size_inc.width; 
1810             int mh = (h - baseh) % self->size_inc.height;
1811             /* amount to add */
1812             int aw = self->size_inc.width / 2;
1813             int ah = self->size_inc.height / 2;
1814             /* don't let us move into a new size increment */
1815             if (mw + aw >= self->size_inc.width)
1816                 aw = self->size_inc.width - mw - 1;
1817             if (mh + ah >= self->size_inc.height)
1818                 ah = self->size_inc.height - mh - 1;
1819             w += aw;
1820             h += ah;
1821     
1822             /* if this is a user-requested resize, then check against min/max
1823                sizes */
1824
1825             /* smaller than min size or bigger than max size? */
1826             if (w > self->max_size.width) w = self->max_size.width;
1827             if (w < minw) w = minw;
1828             if (h > self->max_size.height) h = self->max_size.height;
1829             if (h < minh) h = minh;
1830         }
1831
1832         w -= basew;
1833         h -= baseh;
1834
1835         /* keep to the increments */
1836         w /= self->size_inc.width;
1837         h /= self->size_inc.height;
1838
1839         /* you cannot resize to nothing */
1840         if (basew + w < 1) w = 1 - basew;
1841         if (baseh + h < 1) h = 1 - baseh;
1842   
1843         /* store the logical size */
1844         SIZE_SET(self->logical_size,
1845                  self->size_inc.width > 1 ? w : w + basew,
1846                  self->size_inc.height > 1 ? h : h + baseh);
1847
1848         w *= self->size_inc.width;
1849         h *= self->size_inc.height;
1850
1851         w += basew;
1852         h += baseh;
1853
1854         if (user) {
1855             /* adjust the height to match the width for the aspect ratios.
1856              for this, min size is not substituted for base size ever. */
1857             w -= self->base_size.width;
1858             h -= self->base_size.height;
1859
1860             if (self->min_ratio)
1861                 if (h * self->min_ratio > w) h = (int)(w / self->min_ratio);
1862             if (self->max_ratio)
1863                 if (h * self->max_ratio < w) h = (int)(w / self->max_ratio);
1864
1865             w += self->base_size.width;
1866             h += self->base_size.height;
1867         }
1868     }
1869
1870     switch (anchor) {
1871     case OB_CORNER_TOPLEFT:
1872         break;
1873     case OB_CORNER_TOPRIGHT:
1874         x -= w - self->area.width;
1875         break;
1876     case OB_CORNER_BOTTOMLEFT:
1877         y -= h - self->area.height;
1878         break;
1879     case OB_CORNER_BOTTOMRIGHT:
1880         x -= w - self->area.width;
1881         y -= h - self->area.height;
1882         break;
1883     }
1884
1885     moved = x != self->area.x || y != self->area.y;
1886     resized = w != self->area.width || h != self->area.height;
1887
1888     RECT_SET(self->area, x, y, w, h);
1889
1890     /* for app-requested resizes, always resize if 'resized' is true.
1891        for user-requested ones, only resize if final is true, or when
1892        resizing in redraw mode */
1893     if ((!user && resized) ||
1894         (user && (final || (resized && config_redraw_resize))))
1895         XResizeWindow(ob_display, self->window, w, h);
1896
1897     /* move/resize the frame to match the request */
1898     if (self->frame) {
1899         if (self->decorations != self->frame->decorations)
1900             moved = resized = TRUE;
1901
1902         if (moved || resized)
1903             frame_adjust_area(self->frame, moved, resized);
1904
1905         if (!resized && (force_reply || ((!user && moved) || (user && final))))
1906         {
1907             XEvent event;
1908             event.type = ConfigureNotify;
1909             event.xconfigure.display = ob_display;
1910             event.xconfigure.event = self->window;
1911             event.xconfigure.window = self->window;
1912
1913             /* root window real coords */
1914             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
1915                 self->border_width;
1916             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
1917                 self->border_width;
1918             event.xconfigure.width = w;
1919             event.xconfigure.height = h;
1920             event.xconfigure.border_width = 0;
1921             event.xconfigure.above = self->frame->plate;
1922             event.xconfigure.override_redirect = FALSE;
1923             XSendEvent(event.xconfigure.display, event.xconfigure.window,
1924                        FALSE, StructureNotifyMask, &event);
1925         }
1926     }
1927 }
1928
1929 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
1930 {
1931     int x, y, w, h;
1932
1933     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
1934         self->fullscreen == fs) return;                   /* already done */
1935
1936     self->fullscreen = fs;
1937     client_change_state(self); /* change the state hints on the client,
1938                                   and adjust out layer/stacking */
1939
1940     if (fs) {
1941         if (savearea) {
1942             guint32 dimensions[4];
1943             dimensions[0] = self->area.x;
1944             dimensions[1] = self->area.y;
1945             dimensions[2] = self->area.width;
1946             dimensions[3] = self->area.height;
1947   
1948             PROP_SETA32(self->window, openbox_premax, cardinal,
1949                         dimensions, 4);
1950         }
1951
1952         /* these are not actually used cuz client_configure will set them
1953            as appropriate when the window is fullscreened */
1954         x = y = w = h = 0;
1955     } else {
1956         guint num;
1957         gint32 *dimensions;
1958         Rect *a;
1959
1960         /* pick some fallbacks... */
1961         a = screen_area_monitor(self->desktop, 0);
1962         x = a->x + a->width / 4;
1963         y = a->y + a->height / 4;
1964         w = a->width / 2;
1965         h = a->height / 2;
1966
1967         if (PROP_GETA32(self->window, openbox_premax, cardinal,
1968                         (guint32**)&dimensions, &num)) {
1969             if (num == 4) {
1970                 x = dimensions[0];
1971                 y = dimensions[1];
1972                 w = dimensions[2];
1973                 h = dimensions[3];
1974             }
1975             g_free(dimensions);
1976         }
1977     }
1978
1979     client_setup_decor_and_functions(self);
1980
1981     client_configure(self, OB_CORNER_TOPLEFT, x, y, w, h, TRUE, TRUE);
1982
1983     /* try focus us when we go into fullscreen mode */
1984     client_focus(self);
1985 }
1986
1987 static void client_iconify_recursive(ObClient *self,
1988                                      gboolean iconic, gboolean curdesk)
1989 {
1990     GSList *it;
1991     gboolean changed = FALSE;
1992
1993
1994     if (self->iconic != iconic) {
1995         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
1996                  self->window);
1997
1998         self->iconic = iconic;
1999
2000         if (iconic) {
2001             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2002                 self->wmstate = IconicState;
2003                 self->ignore_unmaps++;
2004                 /* we unmap the client itself so that we can get MapRequest
2005                    events, and because the ICCCM tells us to! */
2006                 XUnmapWindow(ob_display, self->window);
2007
2008                 /* update the focus lists.. iconic windows go to the bottom of
2009                    the list, put the new iconic window at the 'top of the
2010                    bottom'. */
2011                 focus_order_to_top(self);
2012
2013                 changed = TRUE;
2014             }
2015         } else {
2016             if (curdesk)
2017                 client_set_desktop(self, screen_desktop, FALSE);
2018             self->wmstate = self->shaded ? IconicState : NormalState;
2019             XMapWindow(ob_display, self->window);
2020
2021             /* this puts it after the current focused window */
2022             focus_order_remove(self);
2023             focus_order_add_new(self);
2024
2025             /* this is here cuz with the VIDMODE extension, the viewport can
2026                change while a fullscreen window is iconic, and when it
2027                uniconifies, it would be nice if it did so to the new position
2028                of the viewport */
2029             client_reconfigure(self);
2030
2031             changed = TRUE;
2032         }
2033     }
2034
2035     if (changed) {
2036         client_change_state(self);
2037         client_showhide(self);
2038         screen_update_areas();
2039
2040         dispatch_client(iconic ? Event_Client_Unmapped : Event_Client_Mapped,
2041                         self, 0, 0);
2042     }
2043
2044     /* iconify all transients */
2045     for (it = self->transients; it != NULL; it = it->next)
2046         if (it->data != self) client_iconify_recursive(it->data,
2047                                                        iconic, curdesk);
2048 }
2049
2050 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2051 {
2052     /* move up the transient chain as far as possible first */
2053     self = client_search_top_transient(self);
2054
2055     client_iconify_recursive(client_search_top_transient(self),
2056                              iconic, curdesk);
2057 }
2058
2059 void client_maximize(ObClient *self, gboolean max, int dir, gboolean savearea)
2060 {
2061     int x, y, w, h;
2062      
2063     g_assert(dir == 0 || dir == 1 || dir == 2);
2064     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2065
2066     /* check if already done */
2067     if (max) {
2068         if (dir == 0 && self->max_horz && self->max_vert) return;
2069         if (dir == 1 && self->max_horz) return;
2070         if (dir == 2 && self->max_vert) return;
2071     } else {
2072         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2073         if (dir == 1 && !self->max_horz) return;
2074         if (dir == 2 && !self->max_vert) return;
2075     }
2076
2077     /* work with the frame's coords */
2078     x = self->frame->area.x;
2079     y = self->frame->area.y;
2080     w = self->area.width;
2081     h = self->area.height;
2082
2083     if (max) {
2084         if (savearea) {
2085             gint32 dimensions[4];
2086             gint32 *readdim;
2087             guint num;
2088
2089             dimensions[0] = x;
2090             dimensions[1] = y;
2091             dimensions[2] = w;
2092             dimensions[3] = h;
2093
2094             /* get the property off the window and use it for the dimensions
2095                we are already maxed on */
2096             if (PROP_GETA32(self->window, openbox_premax, cardinal,
2097                             (guint32**)&readdim, &num)) {
2098                 if (num == 4) {
2099                     if (self->max_horz) {
2100                         dimensions[0] = readdim[0];
2101                         dimensions[2] = readdim[2];
2102                     }
2103                     if (self->max_vert) {
2104                         dimensions[1] = readdim[1];
2105                         dimensions[3] = readdim[3];
2106                     }
2107                 }
2108                 g_free(readdim);
2109             }
2110
2111             PROP_SETA32(self->window, openbox_premax, cardinal,
2112                         (guint32*)dimensions, 4);
2113         }
2114     } else {
2115         guint num;
2116         gint32 *dimensions;
2117         Rect *a;
2118
2119         /* pick some fallbacks... */
2120         a = screen_area_monitor(self->desktop, 0);
2121         if (dir == 0 || dir == 1) { /* horz */
2122             x = a->x + a->width / 4;
2123             w = a->width / 2;
2124         }
2125         if (dir == 0 || dir == 2) { /* vert */
2126             y = a->y + a->height / 4;
2127             h = a->height / 2;
2128         }
2129
2130         if (PROP_GETA32(self->window, openbox_premax, cardinal,
2131                         (guint32**)&dimensions, &num)) {
2132             if (num == 4) {
2133                 if (dir == 0 || dir == 1) { /* horz */
2134                     x = dimensions[0];
2135                     w = dimensions[2];
2136                 }
2137                 if (dir == 0 || dir == 2) { /* vert */
2138                     y = dimensions[1];
2139                     h = dimensions[3];
2140                 }
2141             }
2142             g_free(dimensions);
2143         }
2144     }
2145
2146     if (dir == 0 || dir == 1) /* horz */
2147         self->max_horz = max;
2148     if (dir == 0 || dir == 2) /* vert */
2149         self->max_vert = max;
2150
2151     if (!self->max_horz && !self->max_vert)
2152         PROP_ERASE(self->window, openbox_premax);
2153
2154     client_change_state(self); /* change the state hints on the client */
2155
2156     /* figure out where the client should be going */
2157     frame_frame_gravity(self->frame, &x, &y);
2158     client_configure(self, OB_CORNER_TOPLEFT, x, y, w, h, TRUE, TRUE);
2159 }
2160
2161 void client_shade(ObClient *self, gboolean shade)
2162 {
2163     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2164          shade) ||                         /* can't shade */
2165         self->shaded == shade) return;     /* already done */
2166
2167     /* when we're iconic, don't change the wmstate */
2168     if (!self->iconic)
2169         self->wmstate = shade ? IconicState : NormalState;
2170     self->shaded = shade;
2171     client_change_state(self);
2172     /* resize the frame to just the titlebar */
2173     frame_adjust_area(self->frame, FALSE, FALSE);
2174 }
2175
2176 void client_close(ObClient *self)
2177 {
2178     XEvent ce;
2179
2180     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2181
2182     /*
2183       XXX: itd be cool to do timeouts and shit here for killing the client's
2184       process off
2185       like... if the window is around after 5 seconds, then the close button
2186       turns a nice red, and if this function is called again, the client is
2187       explicitly killed.
2188     */
2189
2190     ce.xclient.type = ClientMessage;
2191     ce.xclient.message_type =  prop_atoms.wm_protocols;
2192     ce.xclient.display = ob_display;
2193     ce.xclient.window = self->window;
2194     ce.xclient.format = 32;
2195     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2196     ce.xclient.data.l[1] = event_lasttime;
2197     ce.xclient.data.l[2] = 0l;
2198     ce.xclient.data.l[3] = 0l;
2199     ce.xclient.data.l[4] = 0l;
2200     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2201 }
2202
2203 void client_kill(ObClient *self)
2204 {
2205     XKillClient(ob_display, self->window);
2206 }
2207
2208 void client_set_desktop_recursive(ObClient *self,
2209                                   guint target, gboolean donthide)
2210 {
2211     guint old;
2212     GSList *it;
2213
2214     if (target != self->desktop) {
2215
2216         ob_debug("Setting desktop %u\n", target+1);
2217
2218         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2219
2220         /* remove from the old desktop(s) */
2221         focus_order_remove(self);
2222
2223         old = self->desktop;
2224         self->desktop = target;
2225         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2226         /* the frame can display the current desktop state */
2227         frame_adjust_state(self->frame);
2228         /* 'move' the window to the new desktop */
2229         if (!donthide)
2230             client_showhide(self);
2231         /* raise if it was not already on the desktop */
2232         if (old != DESKTOP_ALL)
2233             stacking_raise(CLIENT_AS_WINDOW(self));
2234         screen_update_areas();
2235
2236         /* add to the new desktop(s) */
2237         if (config_focus_new)
2238             focus_order_to_top(self);
2239         else
2240             focus_order_to_bottom(self);
2241
2242         dispatch_client(Event_Client_Desktop, self, target, old);
2243     }
2244
2245     /* move all transients */
2246     for (it = self->transients; it != NULL; it = it->next)
2247         if (it->data != self) client_set_desktop_recursive(it->data,
2248                                                            target, donthide);
2249 }
2250
2251 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2252 {
2253     client_set_desktop_recursive(client_search_top_transient(self),
2254                                  target, donthide);
2255 }
2256
2257 ObClient *client_search_modal_child(ObClient *self)
2258 {
2259     GSList *it;
2260     ObClient *ret;
2261   
2262     for (it = self->transients; it != NULL; it = it->next) {
2263         ObClient *c = it->data;
2264         if ((ret = client_search_modal_child(c))) return ret;
2265         if (c->modal) return c;
2266     }
2267     return NULL;
2268 }
2269
2270 gboolean client_validate(ObClient *self)
2271 {
2272     XEvent e; 
2273
2274     XSync(ob_display, FALSE); /* get all events on the server */
2275
2276     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2277         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2278         XPutBackEvent(ob_display, &e);
2279         return FALSE;
2280     }
2281
2282     return TRUE;
2283 }
2284
2285 void client_set_wm_state(ObClient *self, long state)
2286 {
2287     if (state == self->wmstate) return; /* no change */
2288   
2289     switch (state) {
2290     case IconicState:
2291         client_iconify(self, TRUE, TRUE);
2292         break;
2293     case NormalState:
2294         client_iconify(self, FALSE, TRUE);
2295         break;
2296     }
2297 }
2298
2299 void client_set_state(ObClient *self, Atom action, long data1, long data2)
2300 {
2301     gboolean shaded = self->shaded;
2302     gboolean fullscreen = self->fullscreen;
2303     gboolean max_horz = self->max_horz;
2304     gboolean max_vert = self->max_vert;
2305     int i;
2306
2307     if (!(action == prop_atoms.net_wm_state_add ||
2308           action == prop_atoms.net_wm_state_remove ||
2309           action == prop_atoms.net_wm_state_toggle))
2310         /* an invalid action was passed to the client message, ignore it */
2311         return; 
2312
2313     for (i = 0; i < 2; ++i) {
2314         Atom state = i == 0 ? data1 : data2;
2315     
2316         if (!state) continue;
2317
2318         /* if toggling, then pick whether we're adding or removing */
2319         if (action == prop_atoms.net_wm_state_toggle) {
2320             if (state == prop_atoms.net_wm_state_modal)
2321                 action = self->modal ? prop_atoms.net_wm_state_remove :
2322                     prop_atoms.net_wm_state_add;
2323             else if (state == prop_atoms.net_wm_state_maximized_vert)
2324                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2325                     prop_atoms.net_wm_state_add;
2326             else if (state == prop_atoms.net_wm_state_maximized_horz)
2327                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2328                     prop_atoms.net_wm_state_add;
2329             else if (state == prop_atoms.net_wm_state_shaded)
2330                 action = self->shaded ? prop_atoms.net_wm_state_remove :
2331                     prop_atoms.net_wm_state_add;
2332             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2333                 action = self->skip_taskbar ?
2334                     prop_atoms.net_wm_state_remove :
2335                     prop_atoms.net_wm_state_add;
2336             else if (state == prop_atoms.net_wm_state_skip_pager)
2337                 action = self->skip_pager ?
2338                     prop_atoms.net_wm_state_remove :
2339                     prop_atoms.net_wm_state_add;
2340             else if (state == prop_atoms.net_wm_state_fullscreen)
2341                 action = self->fullscreen ?
2342                     prop_atoms.net_wm_state_remove :
2343                     prop_atoms.net_wm_state_add;
2344             else if (state == prop_atoms.net_wm_state_above)
2345                 action = self->above ? prop_atoms.net_wm_state_remove :
2346                     prop_atoms.net_wm_state_add;
2347             else if (state == prop_atoms.net_wm_state_below)
2348                 action = self->below ? prop_atoms.net_wm_state_remove :
2349                     prop_atoms.net_wm_state_add;
2350         }
2351     
2352         if (action == prop_atoms.net_wm_state_add) {
2353             if (state == prop_atoms.net_wm_state_modal) {
2354                 /* XXX raise here or something? */
2355                 self->modal = TRUE;
2356             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2357                 max_vert = TRUE;
2358             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2359                 max_horz = TRUE;
2360             } else if (state == prop_atoms.net_wm_state_shaded) {
2361                 shaded = TRUE;
2362             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2363                 self->skip_taskbar = TRUE;
2364             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2365                 self->skip_pager = TRUE;
2366             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2367                 fullscreen = TRUE;
2368             } else if (state == prop_atoms.net_wm_state_above) {
2369                 self->above = TRUE;
2370             } else if (state == prop_atoms.net_wm_state_below) {
2371                 self->below = TRUE;
2372             }
2373
2374         } else { /* action == prop_atoms.net_wm_state_remove */
2375             if (state == prop_atoms.net_wm_state_modal) {
2376                 self->modal = FALSE;
2377             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2378                 max_vert = FALSE;
2379             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2380                 max_horz = FALSE;
2381             } else if (state == prop_atoms.net_wm_state_shaded) {
2382                 shaded = FALSE;
2383             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2384                 self->skip_taskbar = FALSE;
2385             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2386                 self->skip_pager = FALSE;
2387             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2388                 fullscreen = FALSE;
2389             } else if (state == prop_atoms.net_wm_state_above) {
2390                 self->above = FALSE;
2391             } else if (state == prop_atoms.net_wm_state_below) {
2392                 self->below = FALSE;
2393             }
2394         }
2395     }
2396     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2397         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2398             /* toggling both */
2399             if (max_horz == max_vert) { /* both going the same way */
2400                 client_maximize(self, max_horz, 0, TRUE);
2401             } else {
2402                 client_maximize(self, max_horz, 1, TRUE);
2403                 client_maximize(self, max_vert, 2, TRUE);
2404             }
2405         } else {
2406             /* toggling one */
2407             if (max_horz != self->max_horz)
2408                 client_maximize(self, max_horz, 1, TRUE);
2409             else
2410                 client_maximize(self, max_vert, 2, TRUE);
2411         }
2412     }
2413     /* change fullscreen state before shading, as it will affect if the window
2414        can shade or not */
2415     if (fullscreen != self->fullscreen)
2416         client_fullscreen(self, fullscreen, TRUE);
2417     if (shaded != self->shaded)
2418         client_shade(self, shaded);
2419     client_calc_layer(self);
2420     client_change_state(self); /* change the hint to reflect these changes */
2421 }
2422
2423 ObClient *client_focus_target(ObClient *self)
2424 {
2425     ObClient *child;
2426      
2427     /* if we have a modal child, then focus it, not us */
2428     child = client_search_modal_child(self);
2429     if (child) return child;
2430     return self;
2431 }
2432
2433 gboolean client_can_focus(ObClient *self)
2434 {
2435     XEvent ev;
2436
2437     /* choose the correct target */
2438     self = client_focus_target(self);
2439
2440     if (!self->frame->visible)
2441         return FALSE;
2442
2443     if (!((self->can_focus || self->focus_notify) &&
2444           (self->desktop == screen_desktop ||
2445            self->desktop == DESKTOP_ALL) &&
2446           !self->iconic))
2447         return FALSE;
2448
2449     /* do a check to see if the window has already been unmapped or destroyed
2450        do this intelligently while watching out for unmaps we've generated
2451        (ignore_unmaps > 0) */
2452     if (XCheckTypedWindowEvent(ob_display, self->window,
2453                                DestroyNotify, &ev)) {
2454         XPutBackEvent(ob_display, &ev);
2455         return FALSE;
2456     }
2457     while (XCheckTypedWindowEvent(ob_display, self->window,
2458                                   UnmapNotify, &ev)) {
2459         if (self->ignore_unmaps) {
2460             self->ignore_unmaps--;
2461         } else {
2462             XPutBackEvent(ob_display, &ev);
2463             return FALSE;
2464         }
2465     }
2466
2467     return TRUE;
2468 }
2469
2470 gboolean client_focus(ObClient *self)
2471 {
2472     /* choose the correct target */
2473     self = client_focus_target(self);
2474
2475     if (!client_can_focus(self)) {
2476         if (!self->frame->visible) {
2477             /* update the focus lists */
2478             focus_order_to_top(self);
2479         }
2480         return FALSE;
2481     }
2482
2483     if (self->can_focus)
2484         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2485            I choose to use it always, hopefully to find errors quicker, if any
2486            are left. (I hate X. I hate focus events.) */
2487         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
2488                        event_lasttime);
2489
2490     if (self->focus_notify) {
2491         XEvent ce;
2492         ce.xclient.type = ClientMessage;
2493         ce.xclient.message_type = prop_atoms.wm_protocols;
2494         ce.xclient.display = ob_display;
2495         ce.xclient.window = self->window;
2496         ce.xclient.format = 32;
2497         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2498         ce.xclient.data.l[1] = event_lasttime;
2499         ce.xclient.data.l[2] = 0l;
2500         ce.xclient.data.l[3] = 0l;
2501         ce.xclient.data.l[4] = 0l;
2502         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2503     }
2504
2505 #ifdef DEBUG_FOCUS
2506     ob_debug("%sively focusing %lx at %d\n",
2507              (self->can_focus ? "act" : "pass"),
2508              self->window, (int) event_lasttime);
2509 #endif
2510
2511     /* Cause the FocusIn to come back to us. Important for desktop switches,
2512        since otherwise we'll have no FocusIn on the queue and send it off to
2513        the focus_backup. */
2514     XSync(ob_display, FALSE);
2515     return TRUE;
2516 }
2517
2518 void client_unfocus(ObClient *self)
2519 {
2520     g_assert(focus_client == self);
2521 #ifdef DEBUG_FOCUS
2522     ob_debug("client_unfocus for %lx\n", self->window);
2523 #endif
2524     focus_fallback(OB_FOCUS_FALLBACK_UNFOCUSING);
2525 }
2526
2527 void client_activate(ObClient *self)
2528 {
2529     if (client_normal(self) && screen_showing_desktop)
2530         screen_show_desktop(FALSE);
2531     if (self->iconic)
2532         client_iconify(self, FALSE, FALSE);
2533     if (self->desktop != DESKTOP_ALL &&
2534         self->desktop != screen_desktop)
2535         screen_set_desktop(self->desktop);
2536     else if (!self->frame->visible)
2537         /* if its not visible for other reasons, then don't mess
2538            with it */
2539         return;
2540     if (self->shaded)
2541         client_shade(self, FALSE);
2542     client_focus(self);
2543     stacking_raise(CLIENT_AS_WINDOW(self));
2544 }
2545
2546 gboolean client_focused(ObClient *self)
2547 {
2548     return self == focus_client;
2549 }
2550
2551 ObClientIcon *client_icon(ObClient *self, int w, int h)
2552 {
2553     guint i;
2554     /* si is the smallest image >= req */
2555     /* li is the largest image < req */
2556     unsigned long size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2557
2558     if (!self->nicons) return NULL;
2559
2560     for (i = 0; i < self->nicons; ++i) {
2561         size = self->icons[i].width * self->icons[i].height;
2562         if (size < smallest && size >= (unsigned)(w * h)) {
2563             smallest = size;
2564             si = i;
2565         }
2566         if (size > largest && size <= (unsigned)(w * h)) {
2567             largest = size;
2568             li = i;
2569         }
2570     }
2571     if (largest == 0) /* didnt find one smaller than the requested size */
2572         return &self->icons[si];
2573     return &self->icons[li];
2574 }
2575
2576 /* this be mostly ripped from fvwm */
2577 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2578 {
2579     int my_cx, my_cy, his_cx, his_cy;
2580     int offset = 0;
2581     int distance = 0;
2582     int score, best_score;
2583     ObClient *best_client, *cur;
2584     GList *it;
2585
2586     if(!client_list)
2587         return NULL;
2588
2589     /* first, find the centre coords of the currently focused window */
2590     my_cx = c->frame->area.x + c->frame->area.width / 2;
2591     my_cy = c->frame->area.y + c->frame->area.height / 2;
2592
2593     best_score = -1;
2594     best_client = NULL;
2595
2596     for(it = g_list_first(client_list); it; it = it->next) {
2597         cur = it->data;
2598
2599         /* the currently selected window isn't interesting */
2600         if(cur == c)
2601             continue;
2602         if (!client_normal(cur))
2603             continue;
2604         if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2605             continue;
2606         if(cur->iconic)
2607             continue;
2608         if(client_focus_target(cur) == cur &&
2609            !(cur->can_focus || cur->focus_notify))
2610             continue;
2611
2612         /* find the centre coords of this window, from the
2613          * currently focused window's point of view */
2614         his_cx = (cur->frame->area.x - my_cx)
2615             + cur->frame->area.width / 2;
2616         his_cy = (cur->frame->area.y - my_cy)
2617             + cur->frame->area.height / 2;
2618
2619         if(dir > 3) { 
2620             int tx;
2621             /* Rotate the diagonals 45 degrees counterclockwise.
2622              * To do this, multiply the matrix /+h +h\ with the
2623              * vector (x y).                   \-h +h/
2624              * h = sqrt(0.5). We can set h := 1 since absolute
2625              * distance doesn't matter here. */
2626             tx = his_cx + his_cy;
2627             his_cy = -his_cx + his_cy;
2628             his_cx = tx;
2629         }
2630
2631         switch(dir) {
2632         case OB_DIRECTION_NORTH:
2633         case OB_DIRECTION_SOUTH:
2634         case OB_DIRECTION_NORTHEAST:
2635         case OB_DIRECTION_SOUTHWEST:
2636             offset = (his_cx < 0) ? -his_cx : his_cx;
2637             distance = ((dir == OB_DIRECTION_NORTH ||
2638                         dir == OB_DIRECTION_NORTHEAST) ?
2639                         -his_cy : his_cy);
2640             break;
2641         case OB_DIRECTION_EAST:
2642         case OB_DIRECTION_WEST:
2643         case OB_DIRECTION_SOUTHEAST:
2644         case OB_DIRECTION_NORTHWEST:
2645             offset = (his_cy < 0) ? -his_cy : his_cy;
2646             distance = ((dir == OB_DIRECTION_WEST ||
2647                         dir == OB_DIRECTION_NORTHWEST) ?
2648                         -his_cx : his_cx);
2649             break;
2650         }
2651
2652         /* the target must be in the requested direction */
2653         if(distance <= 0)
2654             continue;
2655
2656         /* Calculate score for this window.  The smaller the better. */
2657         score = distance + offset;
2658
2659         /* windows more than 45 degrees off the direction are
2660          * heavily penalized and will only be chosen if nothing
2661          * else within a million pixels */
2662         if(offset > distance)
2663             score += 1000000;
2664
2665         if(best_score == -1 || score < best_score)
2666             best_client = cur,
2667                 best_score = score;
2668     }
2669
2670     return best_client;
2671 }
2672
2673 void client_set_layer(ObClient *self, int layer)
2674 {
2675     if (layer < 0) {
2676         self->below = TRUE;
2677         self->above = FALSE;
2678     } else if (layer == 0) {
2679         self->below = self->above = FALSE;
2680     } else {
2681         self->below = FALSE;
2682         self->above = TRUE;
2683     }
2684     client_calc_layer(self);
2685     client_change_state(self); /* reflect this in the state hints */
2686 }
2687
2688 guint client_monitor(ObClient *self)
2689 {
2690     guint i;
2691
2692     for (i = 0; i < screen_num_monitors; ++i) {
2693         Rect *area = screen_physical_area_monitor(i);
2694         if (RECT_INTERSECTS_RECT(*area, self->frame->area))
2695             break;
2696     }
2697     if (i == screen_num_monitors) i = 0;
2698     g_assert(i < screen_num_monitors);
2699     return i;
2700 }
2701
2702 ObClient *client_search_top_transient(ObClient *self)
2703 {
2704     /* move up the transient chain as far as possible */
2705     if (self->transient_for) {
2706         if (self->transient_for != OB_TRAN_GROUP) {
2707             return client_search_top_transient(self->transient_for);
2708         } else {
2709             GSList *it;
2710
2711             for (it = self->group->members; it; it = it->next) {
2712                 ObClient *c = it->data;
2713
2714                 /* checking transient_for prevents infinate loops! */
2715                 if (c != self && !c->transient_for)
2716                     break;
2717             }
2718             if (it)
2719                 return it->data;
2720         }
2721     }
2722
2723     return self;
2724 }
2725
2726 ObClient *client_search_transient(ObClient *self, ObClient *search)
2727 {
2728     GSList *sit;
2729
2730     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
2731         if (sit->data == search)
2732             return search;
2733         if (client_search_transient(sit->data, search))
2734             return search;
2735     }
2736     return NULL;
2737 }
2738
2739 gchar* client_get_sm_client_id(ObClient *self)
2740 {
2741     gchar *id = NULL;
2742
2743     if (!PROP_GETS(self->window, sm_client_id, locale, &id) && self->group)
2744         PROP_GETS(self->group->leader, sm_client_id, locale, &id);
2745     return id;
2746 }