]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
check for new windows always being onscreen.
[dana/openbox.git] / openbox / client.c
1 #include "client.h"
2 #include "screen.h"
3 #include "prop.h"
4 #include "extensions.h"
5 #include "frame.h"
6 #include "engine.h"
7 #include "event.h"
8 #include "grab.h"
9 #include "focus.h"
10 #include "stacking.h"
11 #include "dispatch.h"
12 #include "group.h"
13
14 #include <glib.h>
15 #include <X11/Xutil.h>
16
17 /*! The event mask to grab on client windows */
18 #define CLIENT_EVENTMASK (PropertyChangeMask | FocusChangeMask | \
19                           StructureNotifyMask)
20
21 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
22                                 ButtonMotionMask)
23
24 GList      *client_list      = NULL;
25 GHashTable *client_map       = NULL;
26
27 static Window *client_startup_stack_order = NULL;
28 static gulong  client_startup_stack_size = 0;
29
30 static void client_get_all(Client *self);
31 static void client_toggle_border(Client *self, gboolean show);
32 static void client_get_area(Client *self);
33 static void client_get_desktop(Client *self);
34 static void client_get_state(Client *self);
35 static void client_get_shaped(Client *self);
36 static void client_get_mwm_hints(Client *self);
37 static void client_get_gravity(Client *self);
38 static void client_showhide(Client *self);
39 static void client_change_allowed_actions(Client *self);
40 static void client_change_state(Client *self);
41 static void client_move_onscreen(Client *self);
42 static Client *search_focus_tree(Client *node, Client *skip);
43 static void client_apply_startup_state(Client *self);
44 static Client *search_modal_tree(Client *node, Client *skip);
45
46 static guint map_hash(Window *w) { return *w; }
47 static gboolean map_key_comp(Window *w1, Window *w2) { return *w1 == *w2; }
48
49 void client_startup()
50 {
51     client_map = g_hash_table_new((GHashFunc)map_hash,
52                                   (GEqualFunc)map_key_comp);
53
54     /* save the stacking order on startup! */
55     PROP_GET32U(ob_root, net_client_list_stacking, window,
56                 client_startup_stack_order, client_startup_stack_size);
57
58     client_set_list();
59 }
60
61 void client_shutdown()
62 {
63     g_hash_table_destroy(client_map);
64 }
65
66 void client_set_list()
67 {
68     Window *windows, *win_it;
69     GList *it;
70     guint size = g_list_length(client_list);
71
72     /* create an array of the window ids */
73     if (size > 0) {
74         windows = g_new(Window, size);
75         win_it = windows;
76         for (it = client_list; it != NULL; it = it->next, ++win_it)
77             *win_it = ((Client*)it->data)->window;
78     } else
79         windows = NULL;
80
81     PROP_SET32A(ob_root, net_client_list, window, windows, size);
82
83     if (windows)
84         g_free(windows);
85
86     stacking_set_list();
87 }
88
89 void client_manage_all()
90 {
91     unsigned int i, j, nchild;
92     Window w, *children;
93     XWMHints *wmhints;
94     XWindowAttributes attrib;
95
96     XQueryTree(ob_display, ob_root, &w, &w, &children, &nchild);
97
98     /* remove all icon windows from the list */
99     for (i = 0; i < nchild; i++) {
100         if (children[i] == None) continue;
101         wmhints = XGetWMHints(ob_display, children[i]);
102         if (wmhints) {
103             if ((wmhints->flags & IconWindowHint) &&
104                 (wmhints->icon_window != children[i]))
105                 for (j = 0; j < nchild; j++)
106                     if (children[j] == wmhints->icon_window) {
107                         children[j] = None;
108                         break;
109                     }
110             XFree(wmhints);
111         }
112     }
113
114     for (i = 0; i < nchild; ++i) {
115         if (children[i] == None)
116             continue;
117         if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
118             if (attrib.override_redirect) continue;
119
120             if (attrib.map_state != IsUnmapped)
121                 client_manage(children[i]);
122         }
123     }
124     XFree(children);
125
126     /* stack them as they were on startup!
127        why with stacking_lower? Why, because then windows who aren't in the
128        stacking list are on the top where you can see them instead of buried
129        at the bottom! */
130     for (i = client_startup_stack_size; i > 0; --i) {
131         Client *c;
132
133         w = client_startup_stack_order[i-1];
134         c = g_hash_table_lookup(client_map, &w);
135         if (c) stacking_lower(c);
136     }
137     g_free(client_startup_stack_order);
138     client_startup_stack_order = NULL;
139     client_startup_stack_size = 0;
140
141     if (focus_new)
142         focus_fallback(FALSE);
143 }
144
145 void client_manage(Window window)
146 {
147     Client *self;
148     XEvent e;
149     XWindowAttributes attrib;
150     XSetWindowAttributes attrib_set;
151 /*    XWMHints *wmhint; */
152     guint i;
153
154     grab_server(TRUE);
155
156     /* check if it has already been unmapped by the time we started mapping
157        the grab does a sync so we don't have to here */
158     if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
159         XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) {
160         XPutBackEvent(ob_display, &e);
161
162         grab_server(FALSE);
163         return; /* don't manage it */
164     }
165
166     /* make sure it isn't an override-redirect window */
167     if (!XGetWindowAttributes(ob_display, window, &attrib) ||
168         attrib.override_redirect) {
169         grab_server(FALSE);
170         return; /* don't manage it */
171     }
172   
173 /*    /\* is the window a docking app *\/
174     if ((wmhint = XGetWMHints(ob_display, window))) {
175         if ((wmhint->flags & StateHint) &&
176             wmhint->initial_state == WithdrawnState) {
177             /\* XXX: make dock apps work! *\/
178             grab_server(FALSE);
179             XFree(wmhint);
180             return;
181         }
182         XFree(wmhint);
183     }
184 */
185
186     /* choose the events we want to receive on the CLIENT window */
187     attrib_set.event_mask = CLIENT_EVENTMASK;
188     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
189     XChangeWindowAttributes(ob_display, window,
190                             CWEventMask|CWDontPropagate, &attrib_set);
191
192
193     /* create the Client struct, and populate it from the hints on the
194        window */
195     self = g_new(Client, 1);
196     self->window = window;
197     client_get_all(self);
198
199     /* remove the client's border (and adjust re gravity) */
200     client_toggle_border(self, FALSE);
201      
202     /* specify that if we exit, the window should not be destroyed and should
203        be reparented back to root automatically */
204     XChangeSaveSet(ob_display, window, SetModeInsert);
205
206     /* create the decoration frame for the client window */
207     self->frame = engine_frame_new();
208
209     engine_frame_grab_client(self->frame, self);
210
211     client_apply_startup_state(self);
212
213     grab_server(FALSE);
214      
215     client_list = g_list_append(client_list, self);
216     stacking_list = g_list_append(stacking_list, self);
217     g_assert(!g_hash_table_lookup(client_map, &self->window));
218     g_hash_table_insert(client_map, &self->window, self);
219
220     /* update the focus lists */
221     if (self->desktop == DESKTOP_ALL) {
222         for (i = 0; i < screen_num_desktops; ++i)
223             focus_order[i] = g_list_append(focus_order[i], self);
224     } else {
225         i = self->desktop;
226         focus_order[i] = g_list_append(focus_order[i], self);
227     }
228
229     stacking_raise(self);
230
231     screen_update_struts();
232
233     dispatch_client(Event_Client_New, self, 0, 0);
234
235     client_showhide(self);
236
237     dispatch_client(Event_Client_Mapped, self, 0, 0);
238
239     if (ob_state != State_Starting && focus_new)
240         client_focus(self);
241
242     /* update the list hints */
243     client_set_list();
244
245     /* make sure the window is visible */
246     client_move_onscreen(self);
247
248 /*    g_message("Managed window 0x%lx", window);*/
249 }
250
251 void client_unmanage_all()
252 {
253     while (client_list != NULL)
254         client_unmanage(client_list->data);
255 }
256
257 void client_unmanage(Client *self)
258 {
259     guint i;
260     int j;
261     GSList *it;
262
263 /*    g_message("Unmanaging window: %lx", self->window);*/
264
265     dispatch_client(Event_Client_Destroy, self, 0, 0);
266     g_assert(self != NULL);
267
268     /* remove the window from our save set */
269     XChangeSaveSet(ob_display, self->window, SetModeDelete);
270
271     /* we dont want events no more */
272     XSelectInput(ob_display, self->window, NoEventMask);
273
274     engine_frame_hide(self->frame);
275
276     client_list = g_list_remove(client_list, self);
277     stacking_list = g_list_remove(stacking_list, self);
278     g_hash_table_remove(client_map, &self->window);
279
280     /* update the focus lists */
281     if (self->desktop == DESKTOP_ALL) {
282         for (i = 0; i < screen_num_desktops; ++i)
283             focus_order[i] = g_list_remove(focus_order[i], self);
284     } else {
285         i = self->desktop;
286         focus_order[i] = g_list_remove(focus_order[i], self);
287     }
288
289     /* once the client is out of the list, update the struts to remove it's
290        influence */
291     screen_update_struts();
292
293     /* tell our parent(s) that we're gone */
294     if (self->transient_for == TRAN_GROUP) { /* transient of group */
295         GSList *it;
296
297         for (it = self->group->members; it; it = it->next)
298             if (it->data != self)
299                 ((Client*)it->data)->transients =
300                     g_slist_remove(((Client*)it->data)->transients, self);
301     } else if (self->transient_for) {        /* transient of window */
302         self->transient_for->transients =
303             g_slist_remove(self->transient_for->transients, self);
304     }
305
306     /* tell our transients that we're gone */
307     for (it = self->transients; it != NULL; it = it->next) {
308         if (((Client*)it->data)->transient_for != TRAN_GROUP) {
309             ((Client*)it->data)->transient_for = NULL;
310             client_calc_layer(it->data);
311         }
312     }
313
314     /* remove from its group */
315     if (self->group) {
316         group_remove(self->group, self);
317         self->group = NULL;
318     }
319
320     /* dispatch the unmapped event */
321     dispatch_client(Event_Client_Unmapped, self, 0, 0);
322     g_assert(self != NULL);
323
324     /* give the client its border back */
325     client_toggle_border(self, TRUE);
326
327     /* reparent the window out of the frame, and free the frame */
328     engine_frame_release_client(self->frame, self);
329     self->frame = NULL;
330      
331     if (ob_state != State_Exiting) {
332         /* these values should not be persisted across a window
333            unmapping/mapping */
334         prop_erase(self->window, prop_atoms.net_wm_desktop);
335         prop_erase(self->window, prop_atoms.net_wm_state);
336     } else {
337         /* if we're left in an iconic state, the client wont be mapped. this is
338            bad, since we will no longer be managing the window on restart */
339         if (self->iconic)
340             XMapWindow(ob_display, self->window);
341     }
342
343     /* free all data allocated in the client struct */
344     g_slist_free(self->transients);
345     for (j = 0; j < self->nicons; ++j)
346         g_free(self->icons[j].data);
347     if (self->nicons > 0)
348         g_free(self->icons);
349     g_free(self->title);
350     g_free(self->icon_title);
351     g_free(self->name);
352     g_free(self->class);
353     g_free(self->role);
354     g_free(self);
355      
356     /* update the list hints */
357     client_set_list();
358 }
359
360 static void client_move_onscreen(Client *self)
361 {
362     Rect *a;
363     int x = self->frame->area.x, y = self->frame->area.y;
364
365     a = screen_area(self->desktop);
366     if (x >= a->x + a->width - 1)
367         x = a->x + a->width - self->frame->area.width;
368     if (y >= a->y + a->height - 1)
369         y = a->y + a->height - self->frame->area.height;
370     if (x + self->frame->area.width - 1 < a->x)
371         x = a->x;
372     if (y + self->frame->area.height - 1< a->y)
373         y = a->y;
374
375     frame_frame_gravity(self->frame, &x, &y); /* get where the client
376                                               should be */
377     client_configure(self , Corner_TopLeft, x, y,
378                      self->area.width, self->area.height,
379                      TRUE, TRUE);
380 }
381
382 static void client_toggle_border(Client *self, gboolean show)
383 {
384     /* adjust our idea of where the client is, based on its border. When the
385        border is removed, the client should now be considered to be in a
386        different position.
387        when re-adding the border to the client, the same operation needs to be
388        reversed. */
389     int oldx = self->area.x, oldy = self->area.y;
390     int x = oldx, y = oldy;
391     switch(self->gravity) {
392     default:
393     case NorthWestGravity:
394     case WestGravity:
395     case SouthWestGravity:
396         break;
397     case NorthEastGravity:
398     case EastGravity:
399     case SouthEastGravity:
400         if (show) x -= self->border_width * 2;
401         else      x += self->border_width * 2;
402         break;
403     case NorthGravity:
404     case SouthGravity:
405     case CenterGravity:
406     case ForgetGravity:
407     case StaticGravity:
408         if (show) x -= self->border_width;
409         else      x += self->border_width;
410         break;
411     }
412     switch(self->gravity) {
413     default:
414     case NorthWestGravity:
415     case NorthGravity:
416     case NorthEastGravity:
417         break;
418     case SouthWestGravity:
419     case SouthGravity:
420     case SouthEastGravity:
421         if (show) y -= self->border_width * 2;
422         else      y += self->border_width * 2;
423         break;
424     case WestGravity:
425     case EastGravity:
426     case CenterGravity:
427     case ForgetGravity:
428     case StaticGravity:
429         if (show) y -= self->border_width;
430         else      y += self->border_width;
431         break;
432     }
433     self->area.x = x;
434     self->area.y = y;
435
436     if (show) {
437         XSetWindowBorderWidth(ob_display, self->window, self->border_width);
438
439         /* move the client so it is back it the right spot _with_ its
440            border! */
441         if (x != oldx || y != oldy)
442             XMoveWindow(ob_display, self->window, x, y);
443     } else
444         XSetWindowBorderWidth(ob_display, self->window, 0);
445 }
446
447
448 static void client_get_all(Client *self)
449 {
450     /* update EVERYTHING!! */
451
452     self->ignore_unmaps = 0;
453   
454     /* defaults */
455     self->frame = NULL;
456     self->title = self->icon_title = NULL;
457     self->name = self->class = self->role = NULL;
458     self->wmstate = NormalState;
459     self->transient = FALSE;
460     self->transients = NULL;
461     self->transient_for = NULL;
462     self->layer = -1;
463     self->urgent = FALSE;
464     self->positioned = FALSE;
465     self->disabled_decorations = 0;
466     self->group = NULL;
467     self->nicons = 0;
468
469     client_get_area(self);
470     client_get_desktop(self);
471     client_get_state(self);
472     client_get_shaped(self);
473
474     client_update_transient_for(self);
475     client_get_mwm_hints(self);
476     client_get_type(self);/* this can change the mwmhints for special cases */
477
478     client_update_protocols(self);
479
480     client_get_gravity(self); /* get the attribute gravity */
481     client_update_normal_hints(self); /* this may override the attribute
482                                          gravity */
483
484     /* got the type, the mwmhints, the protocols, and the normal hints
485        (min/max sizes), so we're ready to set up the decorations/functions */
486     client_setup_decor_and_functions(self);
487   
488     client_update_wmhints(self);
489     client_update_title(self);
490     client_update_icon_title(self);
491     client_update_class(self);
492     client_update_strut(self);
493     client_update_icons(self);
494     client_update_kwm_icon(self);
495
496     /* this makes sure that these windows appear on all desktops */
497     if (self->type == Type_Desktop)
498         self->desktop = DESKTOP_ALL;
499
500     /* set the desktop hint, to make sure that it always exists, and to
501        reflect any changes we've made here */
502     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
503
504     client_change_state(self);
505 }
506
507 static void client_get_area(Client *self)
508 {
509     XWindowAttributes wattrib;
510     Status ret;
511   
512     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
513     g_assert(ret != BadWindow);
514
515     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
516     self->border_width = wattrib.border_width;
517 }
518
519 static void client_get_desktop(Client *self)
520 {
521     unsigned int d;
522
523     if (PROP_GET32(self->window, net_wm_desktop, cardinal, d)) {
524         if (d >= screen_num_desktops && d != DESKTOP_ALL)
525             d = screen_num_desktops - 1;
526         self->desktop = d;
527     } else {
528         /* defaults to the current desktop */
529         self->desktop = screen_desktop;
530     }
531 }
532
533 static void client_get_state(Client *self)
534 {
535     gulong *state;
536     gulong num;
537   
538     self->modal = self->shaded = self->max_horz = self->max_vert =
539         self->fullscreen = self->above = self->below = self->iconic =
540         self->skip_taskbar = self->skip_pager = FALSE;
541
542     if (PROP_GET32U(self->window, net_wm_state, atom, state, num)) {
543         gulong i;
544         for (i = 0; i < num; ++i) {
545             if (state[i] == prop_atoms.net_wm_state_modal)
546                 self->modal = TRUE;
547             else if (state[i] == prop_atoms.net_wm_state_shaded)
548                 self->shaded = TRUE;
549             else if (state[i] == prop_atoms.net_wm_state_hidden)
550                 self->iconic = TRUE;
551             else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
552                 self->skip_taskbar = TRUE;
553             else if (state[i] == prop_atoms.net_wm_state_skip_pager)
554                 self->skip_pager = TRUE;
555             else if (state[i] == prop_atoms.net_wm_state_fullscreen)
556                 self->fullscreen = TRUE;
557             else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
558                 self->max_vert = TRUE;
559             else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
560                 self->max_horz = TRUE;
561             else if (state[i] == prop_atoms.net_wm_state_above)
562                 self->above = TRUE;
563             else if (state[i] == prop_atoms.net_wm_state_below)
564                 self->below = TRUE;
565         }
566
567         g_free(state);
568     }
569 }
570
571 static void client_get_shaped(Client *self)
572 {
573     self->shaped = FALSE;
574 #ifdef   SHAPE
575     if (extensions_shape) {
576         int foo;
577         guint ufoo;
578         int s;
579
580         XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
581
582         XShapeQueryExtents(ob_display, self->window, &s, &foo,
583                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
584                            &ufoo);
585         self->shaped = (s != 0);
586     }
587 #endif
588 }
589
590 void client_update_transient_for(Client *self)
591 {
592     Window t = None;
593     Client *c = NULL;
594
595     if (XGetTransientForHint(ob_display, self->window, &t) &&
596         t != self->window) { /* cant be transient to itself! */
597         self->transient = TRUE;
598         c = g_hash_table_lookup(client_map, &t);
599         g_assert(c != self);/* if this happens then we need to check for it*/
600
601         if (!c && self->group) {
602             /* not transient to a client, see if it is transient for a
603                group */
604             if (t == self->group->leader ||
605                 t == None ||
606                 t == ob_root) {
607                 /* window is a transient for its group! */
608                 c = TRAN_GROUP;
609             }
610         }
611     } else
612         self->transient = FALSE;
613
614     /* if anything has changed... */
615     if (c != self->transient_for) {
616         if (self->transient_for == TRAN_GROUP) { /* transient of group */
617             GSList *it;
618
619             /* remove from old parents */
620             for (it = self->group->members; it; it = it->next)
621                 if (it->data != self)
622                     ((Client*)it->data)->transients =
623                         g_slist_remove(((Client*)it->data)->transients, self);
624         } else if (self->transient_for != NULL) { /* transient of window */
625             /* remove from old parent */
626             self->transient_for->transients =
627                 g_slist_remove(self->transient_for->transients, self);
628         }
629         self->transient_for = c;
630         if (self->transient_for == TRAN_GROUP) { /* transient of group */
631             GSList *it;
632
633             /* add to new parents */
634             for (it = self->group->members; it; it = it->next)
635                 if (it->data != self)
636                     ((Client*)it->data)->transients =
637                         g_slist_append(((Client*)it->data)->transients, self);
638         } else if (self->transient_for != NULL) { /* transient of window */
639             /* add to new parent */
640             self->transient_for->transients =
641                 g_slist_append(self->transient_for->transients, self);
642         }
643     }
644 }
645
646 static void client_get_mwm_hints(Client *self)
647 {
648     unsigned long num;
649     unsigned long *hints;
650
651     self->mwmhints.flags = 0; /* default to none */
652
653     if (PROP_GET32U(self->window, motif_wm_hints, motif_wm_hints, hints, num)) {
654         if (num >= MWM_ELEMENTS) {
655             self->mwmhints.flags = hints[0];
656             self->mwmhints.functions = hints[1];
657             self->mwmhints.decorations = hints[2];
658         }
659         g_free(hints);
660     }
661 }
662
663 void client_get_type(Client *self)
664 {
665     gulong *val, num, i;
666
667     self->type = -1;
668   
669     if (PROP_GET32U(self->window, net_wm_window_type, atom, val, num)) {
670         /* use the first value that we know about in the array */
671         for (i = 0; i < num; ++i) {
672             if (val[i] == prop_atoms.net_wm_window_type_desktop)
673                 self->type = Type_Desktop;
674             else if (val[i] == prop_atoms.net_wm_window_type_dock)
675                 self->type = Type_Dock;
676             else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
677                 self->type = Type_Toolbar;
678             else if (val[i] == prop_atoms.net_wm_window_type_menu)
679                 self->type = Type_Menu;
680             else if (val[i] == prop_atoms.net_wm_window_type_utility)
681                 self->type = Type_Utility;
682             else if (val[i] == prop_atoms.net_wm_window_type_splash)
683                 self->type = Type_Splash;
684             else if (val[i] == prop_atoms.net_wm_window_type_dialog)
685                 self->type = Type_Dialog;
686             else if (val[i] == prop_atoms.net_wm_window_type_normal)
687                 self->type = Type_Normal;
688             else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
689                 /* prevent this window from getting any decor or
690                    functionality */
691                 self->mwmhints.flags &= (MwmFlag_Functions |
692                                          MwmFlag_Decorations);
693                 self->mwmhints.decorations = 0;
694                 self->mwmhints.functions = 0;
695             }
696             if (self->type != (WindowType) -1)
697                 break; /* grab the first legit type */
698         }
699         g_free(val);
700     }
701     
702     if (self->type == (WindowType) -1) {
703         /*the window type hint was not set, which means we either classify
704           ourself as a normal window or a dialog, depending on if we are a
705           transient. */
706         if (self->transient)
707             self->type = Type_Dialog;
708         else
709             self->type = Type_Normal;
710     }
711 }
712
713 void client_update_protocols(Client *self)
714 {
715     Atom *proto;
716     gulong num_return, i;
717
718     self->focus_notify = FALSE;
719     self->delete_window = FALSE;
720
721     if (PROP_GET32U(self->window, wm_protocols, atom, proto, num_return)) {
722         for (i = 0; i < num_return; ++i) {
723             if (proto[i] == prop_atoms.wm_delete_window) {
724                 /* this means we can request the window to close */
725                 self->delete_window = TRUE;
726             } else if (proto[i] == prop_atoms.wm_take_focus)
727                 /* if this protocol is requested, then the window will be
728                    notified whenever we want it to receive focus */
729                 self->focus_notify = TRUE;
730         }
731         g_free(proto);
732     }
733 }
734
735 static void client_get_gravity(Client *self)
736 {
737     XWindowAttributes wattrib;
738     Status ret;
739
740     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
741     g_assert(ret != BadWindow);
742     self->gravity = wattrib.win_gravity;
743 }
744
745 void client_update_normal_hints(Client *self)
746 {
747     XSizeHints size;
748     long ret;
749     int oldgravity = self->gravity;
750
751     /* defaults */
752     self->min_ratio = 0.0f;
753     self->max_ratio = 0.0f;
754     SIZE_SET(self->size_inc, 1, 1);
755     SIZE_SET(self->base_size, 0, 0);
756     SIZE_SET(self->min_size, 0, 0);
757     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
758
759     /* get the hints from the window */
760     if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
761         self->positioned = !!(size.flags & (PPosition|USPosition));
762
763         if (size.flags & PWinGravity) {
764             self->gravity = size.win_gravity;
765       
766             /* if the client has a frame, i.e. has already been mapped and
767                is changing its gravity */
768             if (self->frame && self->gravity != oldgravity) {
769                 /* move our idea of the client's position based on its new
770                    gravity */
771                 self->area.x = self->frame->area.x;
772                 self->area.y = self->frame->area.y;
773                 frame_frame_gravity(self->frame, &self->area.x, &self->area.y);
774             }
775         }
776
777         if (size.flags & PAspect) {
778             if (size.min_aspect.y)
779                 self->min_ratio = (float)size.min_aspect.x / size.min_aspect.y;
780             if (size.max_aspect.y)
781                 self->max_ratio = (float)size.max_aspect.x / size.max_aspect.y;
782         }
783
784         if (size.flags & PMinSize)
785             SIZE_SET(self->min_size, size.min_width, size.min_height);
786     
787         if (size.flags & PMaxSize)
788             SIZE_SET(self->max_size, size.max_width, size.max_height);
789     
790         if (size.flags & PBaseSize)
791             SIZE_SET(self->base_size, size.base_width, size.base_height);
792     
793         if (size.flags & PResizeInc)
794             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
795     }
796 }
797
798 void client_setup_decor_and_functions(Client *self)
799 {
800     /* start with everything (cept fullscreen) */
801     self->decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
802         Decor_Icon | Decor_AllDesktops | Decor_Iconify | Decor_Maximize |
803         Decor_Shade;
804     self->functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
805         Func_Shade;
806     if (self->delete_window) {
807         self->decorations |= Decor_Close;
808         self->functions |= Func_Close;
809     }
810
811     if (!(self->min_size.width < self->max_size.width ||
812           self->min_size.height < self->max_size.height)) {
813         self->decorations &= ~(Decor_Maximize | Decor_Handle);
814         self->functions &= ~(Func_Resize | Func_Maximize);
815     }
816
817     switch (self->type) {
818     case Type_Normal:
819         /* normal windows retain all of the possible decorations and
820            functionality, and are the only windows that you can fullscreen */
821         self->functions |= Func_Fullscreen;
822         break;
823
824     case Type_Dialog:
825         /* dialogs cannot be maximized */
826         self->decorations &= ~Decor_Maximize;
827         self->functions &= ~Func_Maximize;
828         break;
829
830     case Type_Menu:
831     case Type_Toolbar:
832     case Type_Utility:
833         /* these windows get less functionality */
834         self->decorations &= ~(Decor_Iconify | Decor_Handle);
835         self->functions &= ~(Func_Iconify | Func_Resize);
836         break;
837
838     case Type_Desktop:
839     case Type_Dock:
840     case Type_Splash:
841         /* none of these windows are manipulated by the window manager */
842         self->decorations = 0;
843         self->functions = 0;
844         break;
845     }
846
847     /* Mwm Hints are applied subtractively to what has already been chosen for
848        decor and functionality */
849     if (self->mwmhints.flags & MwmFlag_Decorations) {
850         if (! (self->mwmhints.decorations & MwmDecor_All)) {
851             if (! (self->mwmhints.decorations & MwmDecor_Border))
852                 self->decorations &= ~Decor_Border;
853             if (! (self->mwmhints.decorations & MwmDecor_Handle))
854                 self->decorations &= ~Decor_Handle;
855             if (! (self->mwmhints.decorations & MwmDecor_Title))
856                 self->decorations &= ~Decor_Titlebar;
857             if (! (self->mwmhints.decorations & MwmDecor_Iconify))
858                 self->decorations &= ~Decor_Iconify;
859             if (! (self->mwmhints.decorations & MwmDecor_Maximize))
860                 self->decorations &= ~Decor_Maximize;
861         }
862     }
863
864     if (self->mwmhints.flags & MwmFlag_Functions) {
865         if (! (self->mwmhints.functions & MwmFunc_All)) {
866             if (! (self->mwmhints.functions & MwmFunc_Resize))
867                 self->functions &= ~Func_Resize;
868             if (! (self->mwmhints.functions & MwmFunc_Move))
869                 self->functions &= ~Func_Move;
870             if (! (self->mwmhints.functions & MwmFunc_Iconify))
871                 self->functions &= ~Func_Iconify;
872             if (! (self->mwmhints.functions & MwmFunc_Maximize))
873                 self->functions &= ~Func_Maximize;
874             /* dont let mwm hints kill the close button
875                if (! (self->mwmhints.functions & MwmFunc_Close))
876                self->functions &= ~Func_Close; */
877         }
878     }
879
880     /* can't maximize without moving/resizing */
881     if (!((self->functions & Func_Move) && (self->functions & Func_Resize)))
882         self->functions &= ~(Func_Maximize | Func_Fullscreen);
883
884     /* finally, user specified disabled decorations are applied to subtract
885        decorations */
886     if (self->disabled_decorations & Decor_Titlebar)
887         self->decorations &= ~Decor_Titlebar;
888     if (self->disabled_decorations & Decor_Handle)
889         self->decorations &= ~Decor_Handle;
890     if (self->disabled_decorations & Decor_Border)
891         self->decorations &= ~Decor_Border;
892     if (self->disabled_decorations & Decor_Iconify)
893         self->decorations &= ~Decor_Iconify;
894     if (self->disabled_decorations & Decor_Maximize)
895         self->decorations &= ~Decor_Maximize;
896     if (self->disabled_decorations & Decor_AllDesktops)
897         self->decorations &= ~Decor_AllDesktops;
898     if (self->disabled_decorations & Decor_Shade)
899         self->decorations &= ~Decor_Shade;
900     if (self->disabled_decorations & Decor_Close)
901         self->decorations &= ~Decor_Close;
902
903     /* if we don't have a titlebar, then we cannot shade! */
904     if (!(self->decorations & Decor_Titlebar))
905         self->functions &= ~Func_Shade;
906
907     client_change_allowed_actions(self);
908
909     if (self->frame) {
910         /* change the decors on the frame, and with more/less decorations,
911            we may also need to be repositioned */
912         engine_frame_adjust_area(self->frame, TRUE, TRUE);
913         /* with new decor, the window's maximized size may change */
914         client_remaximize(self);
915     }
916 }
917
918 static void client_change_allowed_actions(Client *self)
919 {
920     Atom actions[9];
921     int num = 0;
922
923     actions[num++] = prop_atoms.net_wm_action_change_desktop;
924
925     if (self->functions & Func_Shade)
926         actions[num++] = prop_atoms.net_wm_action_shade;
927     if (self->functions & Func_Close)
928         actions[num++] = prop_atoms.net_wm_action_close;
929     if (self->functions & Func_Move)
930         actions[num++] = prop_atoms.net_wm_action_move;
931     if (self->functions & Func_Iconify)
932         actions[num++] = prop_atoms.net_wm_action_minimize;
933     if (self->functions & Func_Resize)
934         actions[num++] = prop_atoms.net_wm_action_resize;
935     if (self->functions & Func_Fullscreen)
936         actions[num++] = prop_atoms.net_wm_action_fullscreen;
937     if (self->functions & Func_Maximize) {
938         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
939         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
940     }
941
942     PROP_SET32A(self->window, net_wm_allowed_actions, atom, actions, num);
943
944     /* make sure the window isn't breaking any rules now */
945
946     if (!(self->functions & Func_Shade) && self->shaded) {
947         if (self->frame) client_shade(self, FALSE);
948         else self->shaded = FALSE;
949     }
950     if (!(self->functions & Func_Iconify) && self->iconic) {
951         g_message("UNSETTING ICONIC");
952         if (self->frame) client_iconify(self, FALSE, TRUE);
953         else self->iconic = FALSE;
954     }
955     if (!(self->functions & Func_Fullscreen) && self->fullscreen) {
956         if (self->frame) client_fullscreen(self, FALSE, TRUE);
957         else self->fullscreen = FALSE;
958     }
959     if (!(self->functions & Func_Maximize) && (self->max_horz ||
960                                                self->max_vert)) {
961         if (self->frame) client_maximize(self, FALSE, 0, TRUE);
962         else self->max_vert = self->max_horz = FALSE;
963     }
964 }
965
966 void client_remaximize(Client *self)
967 {
968     int dir;
969     if (self->max_horz && self->max_vert)
970         dir = 0;
971     else if (self->max_horz)
972         dir = 1;
973     else if (self->max_vert)
974         dir = 2;
975     else
976         return; /* not maximized */
977     self->max_horz = self->max_vert = FALSE;
978     client_maximize(self, TRUE, dir, FALSE);
979 }
980
981 void client_update_wmhints(Client *self)
982 {
983     XWMHints *hints;
984     gboolean ur = FALSE;
985
986     /* assume a window takes input if it doesnt specify */
987     self->can_focus = TRUE;
988   
989     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
990         if (hints->flags & InputHint)
991             self->can_focus = hints->input;
992
993         /* only do this when first managing the window *AND* when we aren't
994            starting up! */
995         if (ob_state != State_Starting && self->frame == NULL)
996             if (hints->flags & StateHint)
997                 self->iconic = hints->initial_state == IconicState;
998
999         if (hints->flags & XUrgencyHint)
1000             ur = TRUE;
1001
1002         if (!(hints->flags & WindowGroupHint))
1003             hints->window_group = None; /* no group */
1004         /* did the group state change? */
1005         if (hints->window_group != (self->group ? self->group->leader : None)){
1006             /* remove from the old group if there was one */
1007             if (self->group != NULL)
1008                 group_remove(self->group, self);
1009             if (hints->window_group != None)
1010                 self->group = group_add(hints->window_group, self);
1011
1012             /* because the self->transient flag wont change from this call,
1013                we don't need to update the window's type and such, only its
1014                transient_for, and the transients lists of other windows in the
1015                group may be affected */
1016             client_update_transient_for(self);
1017         }
1018
1019         if (hints->flags & IconPixmapHint) {
1020             client_update_kwm_icon(self);
1021             /* try get the kwm icon first, this is a fallback only */
1022             if (self->pixmap_icon == None) {
1023                 self->pixmap_icon = hints->icon_pixmap;
1024                 if (hints->flags & IconMaskHint)
1025                     self->pixmap_icon_mask = hints->icon_mask;
1026                 else
1027                     self->pixmap_icon_mask = None;
1028
1029                 if (self->frame)
1030                     engine_frame_adjust_icon(self->frame);
1031             }
1032         }
1033
1034         XFree(hints);
1035     }
1036
1037     if (ur != self->urgent) {
1038         self->urgent = ur;
1039         g_message("Urgent Hint for 0x%lx: %s", self->window,
1040                   ur ? "ON" : "OFF");
1041         /* fire the urgent callback if we're mapped, otherwise, wait until
1042            after we're mapped */
1043         if (self->frame)
1044             dispatch_client(Event_Client_Urgent, self, self->urgent, 0);
1045     }
1046 }
1047
1048 void client_update_title(Client *self)
1049 {
1050     gchar *data = NULL;
1051
1052     g_free(self->title);
1053      
1054     /* try netwm */
1055     if (!PROP_GETS(self->window, net_wm_name, utf8, data)) {
1056         /* try old x stuff */
1057         if (PROP_GETS(self->window, wm_name, string, data)) {
1058             /* convert it to UTF-8 */
1059             gsize r, w;
1060             gchar *u;
1061
1062             u = g_locale_to_utf8(data, -1, &r, &w, NULL);
1063             if (u == NULL) {
1064                 g_warning("Unable to convert string to UTF-8");
1065             } else {
1066                 g_free(data);
1067                 data = u;
1068             }
1069         }
1070         if (data == NULL)
1071             data = g_strdup("Unnamed Window");
1072
1073         PROP_SETS(self->window, net_wm_visible_name, utf8, data);
1074     }
1075
1076     self->title = data;
1077
1078     if (self->frame)
1079         engine_frame_adjust_title(self->frame);
1080 }
1081
1082 void client_update_icon_title(Client *self)
1083 {
1084     gchar *data = NULL;
1085
1086     g_free(self->icon_title);
1087      
1088     /* try netwm */
1089     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, data)) {
1090         /* try old x stuff */
1091         if (PROP_GETS(self->window, wm_icon_name, string, data)) {
1092             /* convert it to UTF-8 */
1093             gsize r, w;
1094             gchar *u;
1095
1096             u = g_locale_to_utf8(data, -1, &r, &w, NULL);
1097             if (u == NULL) {
1098                 g_warning("Unable to convert string to UTF-8");
1099             } else {
1100                 g_free(data);
1101                 data = u;
1102             }
1103         }
1104         if (data == NULL)
1105             data = g_strdup("Unnamed Window");
1106
1107         PROP_SETS(self->window, net_wm_visible_icon_name, utf8, data);
1108     }
1109
1110     self->icon_title = data;
1111 }
1112
1113 void client_update_class(Client *self)
1114 {
1115     GPtrArray *data;
1116     gchar *s;
1117     guint i;
1118
1119     if (self->name) g_free(self->name);
1120     if (self->class) g_free(self->class);
1121     if (self->role) g_free(self->role);
1122
1123     self->name = self->class = self->role = NULL;
1124
1125     data = g_ptr_array_new();
1126      
1127     if (PROP_GETSA(self->window, wm_class, string, data)) {
1128         if (data->len > 0)
1129             self->name = g_strdup(g_ptr_array_index(data, 0));
1130         if (data->len > 1)
1131             self->class = g_strdup(g_ptr_array_index(data, 1));
1132     }
1133      
1134     for (i = 0; i < data->len; ++i)
1135         g_free(g_ptr_array_index(data, i));
1136     g_ptr_array_free(data, TRUE);
1137
1138     if (PROP_GETS(self->window, wm_window_role, string, s))
1139         self->role = g_strdup(s);
1140
1141     if (self->name == NULL) self->name = g_strdup("");
1142     if (self->class == NULL) self->class = g_strdup("");
1143     if (self->role == NULL) self->role = g_strdup("");
1144 }
1145
1146 void client_update_strut(Client *self)
1147 {
1148     gulong *data;
1149
1150     if (PROP_GET32A(self->window, net_wm_strut, cardinal, data, 4)) {
1151         STRUT_SET(self->strut, data[0], data[2], data[1], data[3]);
1152         g_free(data);
1153     } else
1154         STRUT_SET(self->strut, 0, 0, 0, 0);
1155
1156     /* updating here is pointless while we're being mapped cuz we're not in
1157        the client list yet */
1158     if (self->frame)
1159         screen_update_struts();
1160 }
1161
1162 void client_update_icons(Client *self)
1163 {
1164     unsigned long num;
1165     unsigned long *data;
1166     unsigned long w, h, i;
1167     int j;
1168
1169     for (j = 0; j < self->nicons; ++j)
1170         g_free(self->icons[j].data);
1171     if (self->nicons > 0)
1172         g_free(self->icons);
1173     self->nicons = 0;
1174
1175     if (PROP_GET32U(self->window, net_wm_icon, cardinal, data, num)) {
1176         /* figure out how many valid icons are in here */
1177         i = 0;
1178         while (num - i > 2) {
1179             w = data[i++];
1180             h = data[i++];
1181             i += w * h;
1182             if (i > num) break;
1183             ++self->nicons;
1184         }
1185
1186         self->icons = g_new(Icon, self->nicons);
1187     
1188         /* store the icons */
1189         i = 0;
1190         for (j = 0; j < self->nicons; ++j) {
1191             w = self->icons[j].width = data[i++];
1192             h = self->icons[j].height = data[i++];
1193             self->icons[j].data =
1194                 g_memdup(&data[i], w * h * sizeof(gulong));
1195             i += w * h;
1196             g_assert(i <= num);
1197         }
1198
1199         g_free(data);
1200     }
1201
1202     if (self->frame)
1203         engine_frame_adjust_icon(self->frame);
1204 }
1205
1206 void client_update_kwm_icon(Client *self)
1207 {
1208     Pixmap *data;
1209
1210     if (PROP_GET32A(self->window, kwm_win_icon, kwm_win_icon, data, 2)) {
1211         self->pixmap_icon = data[0];
1212         self->pixmap_icon_mask = data[1];
1213         g_free(data);
1214     } else {
1215         self->pixmap_icon = self->pixmap_icon_mask = None;
1216     }
1217     if (self->frame)
1218         engine_frame_adjust_icon(self->frame);
1219 }
1220
1221 static void client_change_state(Client *self)
1222 {
1223     unsigned long state[2];
1224     Atom netstate[10];
1225     int num;
1226
1227     state[0] = self->wmstate;
1228     state[1] = None;
1229     PROP_SET32A(self->window, wm_state, wm_state, state, 2);
1230
1231     num = 0;
1232     if (self->modal)
1233         netstate[num++] = prop_atoms.net_wm_state_modal;
1234     if (self->shaded)
1235         netstate[num++] = prop_atoms.net_wm_state_shaded;
1236     if (self->iconic)
1237         netstate[num++] = prop_atoms.net_wm_state_hidden;
1238     if (self->skip_taskbar)
1239         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1240     if (self->skip_pager)
1241         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1242     if (self->fullscreen)
1243         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1244     if (self->max_vert)
1245         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1246     if (self->max_horz)
1247         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1248     if (self->above)
1249         netstate[num++] = prop_atoms.net_wm_state_above;
1250     if (self->below)
1251         netstate[num++] = prop_atoms.net_wm_state_below;
1252     PROP_SET32A(self->window, net_wm_state, atom, netstate, num);
1253
1254     client_calc_layer(self);
1255
1256     if (self->frame)
1257         engine_frame_adjust_state(self->frame);
1258 }
1259
1260 static Client *search_focus_tree(Client *node, Client *skip)
1261 {
1262     GSList *it;
1263     Client *ret;
1264
1265     for (it = node->transients; it != NULL; it = it->next) {
1266         Client *c = it->data;
1267         if (c == skip) continue; /* circular? */
1268         if ((ret = search_focus_tree(c, skip))) return ret;
1269         if (client_focused(c)) return c;
1270     }
1271     return NULL;
1272 }
1273
1274 void client_calc_layer(Client *self)
1275 {
1276     StackLayer l;
1277     gboolean fs;
1278     Client *c;
1279
1280     /* are we fullscreen, or do we have a fullscreen transient parent? */
1281     c = self;
1282     fs = FALSE;
1283     while (c && c != TRAN_GROUP) { /* XXX do smthng with the TRAN_GROUP case?*/
1284         if (c->fullscreen) {
1285             fs = TRUE;
1286             break;
1287         }
1288         c = c->transient_for;
1289     }
1290     if (!fs && self->fullscreen) {
1291         /* is one of our transients focused? */
1292         c = search_focus_tree(self, self);
1293         if (c != NULL) fs = TRUE;
1294     }
1295   
1296     if (self->iconic) l = Layer_Icon;
1297     else if (fs) l = Layer_Fullscreen;
1298     else if (self->type == Type_Desktop) l = Layer_Desktop;
1299     else if (self->type == Type_Dock) {
1300         if (!self->below) l = Layer_Top;
1301         else l = Layer_Normal;
1302     }
1303     else if (self->above) l = Layer_Above;
1304     else if (self->below) l = Layer_Below;
1305     else l = Layer_Normal;
1306      
1307     if (l != self->layer) {
1308         self->layer = l;
1309         if (self->frame)
1310             stacking_raise(self);
1311     }
1312 }
1313
1314 gboolean client_should_show(Client *self)
1315 {
1316     if (self->iconic) return FALSE;
1317     else if (!(self->desktop == screen_desktop ||
1318                self->desktop == DESKTOP_ALL)) return FALSE;
1319     else if (client_normal(self) && screen_showing_desktop) return FALSE;
1320     
1321     return TRUE;
1322 }
1323
1324 static void client_showhide(Client *self)
1325 {
1326
1327     if (client_should_show(self))
1328         engine_frame_show(self->frame);
1329     else
1330         engine_frame_hide(self->frame);
1331 }
1332
1333 gboolean client_normal(Client *self) {
1334     return ! (self->type == Type_Desktop || self->type == Type_Dock ||
1335               self->type == Type_Splash);
1336 }
1337
1338 static void client_apply_startup_state(Client *self)
1339 {
1340     /* these are in a carefully crafted order.. */
1341
1342     if (self->iconic) {
1343         self->iconic = FALSE;
1344         client_iconify(self, TRUE, FALSE);
1345     }
1346     if (self->fullscreen) {
1347         self->fullscreen = FALSE;
1348         client_fullscreen(self, TRUE, FALSE);
1349     }
1350     if (self->shaded) {
1351         self->shaded = FALSE;
1352         client_shade(self, TRUE);
1353     }
1354     if (self->urgent)
1355         dispatch_client(Event_Client_Urgent, self, self->urgent, 0);
1356   
1357     if (self->max_vert && self->max_horz) {
1358         self->max_vert = self->max_horz = FALSE;
1359         client_maximize(self, TRUE, 0, FALSE);
1360     } else if (self->max_vert) {
1361         self->max_vert = FALSE;
1362         client_maximize(self, TRUE, 2, FALSE);
1363     } else if (self->max_horz) {
1364         self->max_horz = FALSE;
1365         client_maximize(self, TRUE, 1, FALSE);
1366     }
1367
1368     /* nothing to do for the other states:
1369        skip_taskbar
1370        skip_pager
1371        modal
1372        above
1373        below
1374     */
1375 }
1376
1377 void client_configure(Client *self, Corner anchor, int x, int y, int w, int h,
1378                       gboolean user, gboolean final)
1379 {
1380     gboolean moved = FALSE, resized = FALSE;
1381
1382     /* gets the frame's position */
1383     frame_client_gravity(self->frame, &x, &y);
1384
1385     /* these positions are frame positions, not client positions */
1386
1387     /* set the size and position if fullscreen */
1388     if (self->fullscreen) {
1389         x = 0;
1390         y = 0;
1391         w = screen_physical_size.width;
1392         h = screen_physical_size.height;
1393     } else {
1394         /* set the size and position if maximized */
1395         if (self->max_horz) {
1396             x = screen_area(self->desktop)->x - self->frame->size.left;
1397             w = screen_area(self->desktop)->width;
1398         }
1399         if (self->max_vert) {
1400             y = screen_area(self->desktop)->y;
1401             h = screen_area(self->desktop)->height -
1402                 self->frame->size.top - self->frame->size.bottom;
1403         }
1404     }
1405
1406     /* gets the client's position */
1407     frame_frame_gravity(self->frame, &x, &y);
1408
1409     /* these override the above states! if you cant move you can't move! */
1410     if (user) {
1411         if (!(self->functions & Func_Move)) {
1412             x = self->area.x;
1413             y = self->area.y;
1414         }
1415         if (!(self->functions & Func_Resize)) {
1416             w = self->area.width;
1417             h = self->area.height;
1418         }
1419     }
1420
1421     if (!(w == self->area.width && h == self->area.height)) {
1422         w -= self->base_size.width;
1423         h -= self->base_size.height;
1424
1425         if (user) {
1426             /* for interactive resizing. have to move half an increment in each
1427                direction. */
1428
1429             /* how far we are towards the next size inc */
1430             int mw = w % self->size_inc.width; 
1431             int mh = h % self->size_inc.height;
1432             /* amount to add */
1433             int aw = self->size_inc.width / 2;
1434             int ah = self->size_inc.height / 2;
1435             /* don't let us move into a new size increment */
1436             if (mw + aw >= self->size_inc.width)
1437                 aw = self->size_inc.width - mw - 1;
1438             if (mh + ah >= self->size_inc.height)
1439                 ah = self->size_inc.height - mh - 1;
1440             w += aw;
1441             h += ah;
1442     
1443             /* if this is a user-requested resize, then check against min/max
1444                sizes and aspect ratios */
1445
1446             /* smaller than min size or bigger than max size? */
1447             if (w > self->max_size.width) w = self->max_size.width;
1448             if (w < self->min_size.width) w = self->min_size.width;
1449             if (h > self->max_size.height) h = self->max_size.height;
1450             if (h < self->min_size.height) h = self->min_size.height;
1451
1452             /* adjust the height ot match the width for the aspect ratios */
1453             if (self->min_ratio)
1454                 if (h * self->min_ratio > w) h = (int)(w / self->min_ratio);
1455             if (self->max_ratio)
1456                 if (h * self->max_ratio < w) h = (int)(w / self->max_ratio);
1457         }
1458
1459         /* keep to the increments */
1460         w /= self->size_inc.width;
1461         h /= self->size_inc.height;
1462
1463         /* you cannot resize to nothing */
1464         if (w < 1) w = 1;
1465         if (h < 1) h = 1;
1466   
1467         /* store the logical size */
1468         SIZE_SET(self->logical_size, w, h);
1469
1470         w *= self->size_inc.width;
1471         h *= self->size_inc.height;
1472
1473         w += self->base_size.width;
1474         h += self->base_size.height;
1475     }
1476
1477     switch (anchor) {
1478     case Corner_TopLeft:
1479         break;
1480     case Corner_TopRight:
1481         x -= w - self->area.width;
1482         break;
1483     case Corner_BottomLeft:
1484         y -= h - self->area.height;
1485         break;
1486     case Corner_BottomRight:
1487         x -= w - self->area.width;
1488         y -= h - self->area.height;
1489         break;
1490     }
1491
1492     moved = x != self->area.x || y != self->area.y;
1493     resized = w != self->area.width || h != self->area.height;
1494
1495     RECT_SET(self->area, x, y, w, h);
1496
1497     if (resized)
1498         XResizeWindow(ob_display, self->window, w, h);
1499
1500     /* move/resize the frame to match the request */
1501     if (self->frame) {
1502         if (moved || resized)
1503             engine_frame_adjust_area(self->frame, moved, resized);
1504
1505         if (!user || final) {
1506             XEvent event;
1507             event.type = ConfigureNotify;
1508             event.xconfigure.display = ob_display;
1509             event.xconfigure.event = self->window;
1510             event.xconfigure.window = self->window;
1511     
1512             /* root window coords with border in mind */
1513             event.xconfigure.x = x - self->border_width +
1514                 self->frame->size.left;
1515             event.xconfigure.y = y - self->border_width +
1516                 self->frame->size.top;
1517     
1518             event.xconfigure.width = self->area.width;
1519             event.xconfigure.height = self->area.height;
1520             event.xconfigure.border_width = self->border_width;
1521             event.xconfigure.above = self->frame->plate;
1522             event.xconfigure.override_redirect = FALSE;
1523             XSendEvent(event.xconfigure.display, event.xconfigure.window,
1524                        FALSE, StructureNotifyMask, &event);
1525         }
1526     }
1527 }
1528
1529 void client_fullscreen(Client *self, gboolean fs, gboolean savearea)
1530 {
1531     int x, y, w, h;
1532
1533     if (!(self->functions & Func_Fullscreen) || /* can't */
1534         self->fullscreen == fs) return;         /* already done */
1535
1536     self->fullscreen = fs;
1537     client_change_state(self); /* change the state hints on the client */
1538
1539     if (fs) {
1540         /* save the functions and remove them */
1541         self->pre_fs_func = self->functions;
1542         self->functions &= (Func_Close | Func_Fullscreen |
1543                             Func_Iconify);
1544         /* save the decorations and remove them */
1545         self->pre_fs_decor = self->decorations;
1546         self->decorations = 0;
1547         if (savearea) {
1548             long dimensions[4];
1549             dimensions[0] = self->area.x;
1550             dimensions[1] = self->area.y;
1551             dimensions[2] = self->area.width;
1552             dimensions[3] = self->area.height;
1553   
1554             PROP_SET32A(self->window, openbox_premax, cardinal,
1555                         dimensions, 4);
1556         }
1557
1558         /* these are not actually used cuz client_configure will set them
1559            as appropriate when the window is fullscreened */
1560         x = y = w = h = 0;
1561     } else {
1562         long *dimensions;
1563
1564         self->functions = self->pre_fs_func;
1565         self->decorations = self->pre_fs_decor;
1566           
1567         if (PROP_GET32A(self->window, openbox_premax, cardinal,
1568                         dimensions, 4)) {
1569             x = dimensions[0];
1570             y = dimensions[1];
1571             w = dimensions[2];
1572             h = dimensions[3];
1573             g_free(dimensions);
1574         } else {
1575             /* pick some fallbacks... */
1576             x = screen_area(self->desktop)->x +
1577                 screen_area(self->desktop)->width / 4;
1578             y = screen_area(self->desktop)->y +
1579                 screen_area(self->desktop)->height / 4;
1580             w = screen_area(self->desktop)->width / 2;
1581             h = screen_area(self->desktop)->height / 2;
1582         }
1583     }
1584
1585     client_change_allowed_actions(self); /* based on the new _functions */
1586
1587     /* when fullscreening, don't obey things like increments, fill the
1588        screen */
1589     client_configure(self, Corner_TopLeft, x, y, w, h, !fs, TRUE);
1590
1591     /* raise (back) into our stacking layer */
1592     stacking_raise(self);
1593
1594     /* try focus us when we go into fullscreen mode */
1595     client_focus(self);
1596 }
1597
1598 void client_iconify(Client *self, gboolean iconic, gboolean curdesk)
1599 {
1600     if (self->iconic == iconic) return; /* nothing to do */
1601
1602     g_message("%sconifying window: 0x%lx", (iconic ? "I" : "Uni"),
1603               self->window);
1604
1605     self->iconic = iconic;
1606
1607     if (iconic) {
1608         self->wmstate = IconicState;
1609         self->ignore_unmaps++;
1610         /* we unmap the client itself so that we can get MapRequest events,
1611            and because the ICCCM tells us to! */
1612         XUnmapWindow(ob_display, self->window);
1613     } else {
1614         if (curdesk)
1615             client_set_desktop(self, screen_desktop, FALSE);
1616         self->wmstate = self->shaded ? IconicState : NormalState;
1617         XMapWindow(ob_display, self->window);
1618     }
1619     client_change_state(self);
1620     client_showhide(self);
1621     screen_update_struts();
1622
1623     dispatch_client(iconic ? Event_Client_Unmapped : Event_Client_Mapped,
1624                     self, 0, 0);
1625
1626     /* iconify all transients */
1627     if (self->transients) {
1628         GSList *it;
1629
1630         for (it = self->transients; it != NULL; it = it->next)
1631             if (it->data != self) client_iconify(it->data, iconic, curdesk);
1632     }
1633 }
1634
1635 void client_maximize(Client *self, gboolean max, int dir, gboolean savearea)
1636 {
1637     int x, y, w, h;
1638      
1639     g_assert(dir == 0 || dir == 1 || dir == 2);
1640     if (!(self->functions & Func_Maximize)) return; /* can't */
1641
1642     /* check if already done */
1643     if (max) {
1644         if (dir == 0 && self->max_horz && self->max_vert) return;
1645         if (dir == 1 && self->max_horz) return;
1646         if (dir == 2 && self->max_vert) return;
1647     } else {
1648         if (dir == 0 && !self->max_horz && !self->max_vert) return;
1649         if (dir == 1 && !self->max_horz) return;
1650         if (dir == 2 && !self->max_vert) return;
1651     }
1652
1653     /* work with the frame's coords */
1654     x = self->frame->area.x;
1655     y = self->frame->area.y;
1656     w = self->area.width;
1657     h = self->area.height;
1658
1659     if (max) {
1660         if (savearea) {
1661             long dimensions[4];
1662             long *readdim;
1663
1664             dimensions[0] = x;
1665             dimensions[1] = y;
1666             dimensions[2] = w;
1667             dimensions[3] = h;
1668
1669             /* get the property off the window and use it for the dimensions
1670                we are already maxed on */
1671             if (PROP_GET32A(self->window, openbox_premax, cardinal,
1672                             readdim, 4)) {
1673                 if (self->max_horz) {
1674                     dimensions[0] = readdim[0];
1675                     dimensions[2] = readdim[2];
1676                 }
1677                 if (self->max_vert) {
1678                     dimensions[1] = readdim[1];
1679                     dimensions[3] = readdim[3];
1680                 }
1681                 g_free(readdim);
1682             }
1683
1684             PROP_SET32A(self->window, openbox_premax, cardinal,
1685                         dimensions, 4);
1686         }
1687     } else {
1688         long *dimensions;
1689
1690         if (PROP_GET32A(self->window, openbox_premax, cardinal,
1691                         dimensions, 4)) {
1692             if (dir == 0 || dir == 1) { /* horz */
1693                 x = dimensions[0];
1694                 w = dimensions[2];
1695             }
1696             if (dir == 0 || dir == 2) { /* vert */
1697                 y = dimensions[1];
1698                 h = dimensions[3];
1699             }
1700             g_free(dimensions);
1701         } else {
1702             /* pick some fallbacks... */
1703             if (dir == 0 || dir == 1) { /* horz */
1704                 x = screen_area(self->desktop)->x +
1705                     screen_area(self->desktop)->width / 4;
1706                 w = screen_area(self->desktop)->width / 2;
1707             }
1708             if (dir == 0 || dir == 2) { /* vert */
1709                 y = screen_area(self->desktop)->y +
1710                     screen_area(self->desktop)->height / 4;
1711                 h = screen_area(self->desktop)->height / 2;
1712             }
1713         }
1714     }
1715
1716     if (dir == 0 || dir == 1) /* horz */
1717         self->max_horz = max;
1718     if (dir == 0 || dir == 2) /* vert */
1719         self->max_vert = max;
1720
1721     if (!self->max_horz && !self->max_vert)
1722         PROP_ERASE(self->window, openbox_premax);
1723
1724     client_change_state(self); /* change the state hints on the client */
1725
1726     /* figure out where the client should be going */
1727     frame_frame_gravity(self->frame, &x, &y);
1728     client_configure(self, Corner_TopLeft, x, y, w, h, TRUE, TRUE);
1729 }
1730
1731 void client_shade(Client *self, gboolean shade)
1732 {
1733     if ((!(self->functions & Func_Shade) && shade) || /* can't shade */
1734         self->shaded == shade) return;     /* already done */
1735
1736     /* when we're iconic, don't change the wmstate */
1737     if (!self->iconic)
1738         self->wmstate = shade ? IconicState : NormalState;
1739     self->shaded = shade;
1740     client_change_state(self);
1741     /* resize the frame to just the titlebar */
1742     engine_frame_adjust_area(self->frame, FALSE, FALSE);
1743 }
1744
1745 void client_close(Client *self)
1746 {
1747     XEvent ce;
1748
1749     if (!(self->functions & Func_Close)) return;
1750
1751     /*
1752       XXX: itd be cool to do timeouts and shit here for killing the client's
1753       process off
1754       like... if the window is around after 5 seconds, then the close button
1755       turns a nice red, and if this function is called again, the client is
1756       explicitly killed.
1757     */
1758
1759     ce.xclient.type = ClientMessage;
1760     ce.xclient.message_type =  prop_atoms.wm_protocols;
1761     ce.xclient.display = ob_display;
1762     ce.xclient.window = self->window;
1763     ce.xclient.format = 32;
1764     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
1765     ce.xclient.data.l[1] = event_lasttime;
1766     ce.xclient.data.l[2] = 0l;
1767     ce.xclient.data.l[3] = 0l;
1768     ce.xclient.data.l[4] = 0l;
1769     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
1770 }
1771
1772 void client_kill(Client *self)
1773 {
1774     XKillClient(ob_display, self->window);
1775 }
1776
1777 void client_set_desktop(Client *self, guint target, gboolean donthide)
1778 {
1779     guint old, i;
1780
1781     if (target == self->desktop) return;
1782   
1783     g_message("Setting desktop %u", target);
1784
1785     g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
1786
1787     old = self->desktop;
1788     self->desktop = target;
1789     PROP_SET32(self->window, net_wm_desktop, cardinal, target);
1790     /* the frame can display the current desktop state */
1791     engine_frame_adjust_state(self->frame);
1792     /* 'move' the window to the new desktop */
1793     if (!donthide)
1794         client_showhide(self);
1795     /* raise if it was not already on the desktop */
1796     if (old != DESKTOP_ALL)
1797         stacking_raise(self);
1798     screen_update_struts();
1799
1800     /* update the focus lists */
1801     if (old == DESKTOP_ALL) {
1802         for (i = 0; i < screen_num_desktops; ++i)
1803             focus_order[i] = g_list_remove(focus_order[i], self);
1804     } else
1805         focus_order[old] = g_list_remove(focus_order[old], self);
1806     if (target == DESKTOP_ALL) {
1807         for (i = 0; i < screen_num_desktops; ++i) {
1808             if (focus_new)
1809                 focus_order[i] = g_list_prepend(focus_order[i], self);
1810             else
1811                 focus_order[i] = g_list_append(focus_order[i], self);
1812         }
1813     } else {
1814         if (focus_new)
1815             focus_order[target] = g_list_prepend(focus_order[target], self);
1816         else
1817             focus_order[target] = g_list_append(focus_order[target], self);
1818     }
1819
1820     dispatch_client(Event_Client_Desktop, self, target, old);
1821 }
1822
1823 static Client *search_modal_tree(Client *node, Client *skip)
1824 {
1825     GSList *it;
1826     Client *ret;
1827   
1828     for (it = node->transients; it != NULL; it = it->next) {
1829         Client *c = it->data;
1830         if (c == skip) continue; /* circular? */
1831         if ((ret = search_modal_tree(c, skip))) return ret;
1832         if (c->modal) return c;
1833     }
1834     return NULL;
1835 }
1836
1837 Client *client_find_modal_child(Client *self)
1838 {
1839     return search_modal_tree(self, self);
1840 }
1841
1842 gboolean client_validate(Client *self)
1843 {
1844     XEvent e; 
1845
1846     XSync(ob_display, FALSE); /* get all events on the server */
1847
1848     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
1849         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
1850         XPutBackEvent(ob_display, &e);
1851         return FALSE;
1852     }
1853
1854     return TRUE;
1855 }
1856
1857 void client_set_wm_state(Client *self, long state)
1858 {
1859     if (state == self->wmstate) return; /* no change */
1860   
1861     switch (state) {
1862     case IconicState:
1863         client_iconify(self, TRUE, TRUE);
1864         break;
1865     case NormalState:
1866         client_iconify(self, FALSE, TRUE);
1867         break;
1868     }
1869 }
1870
1871 void client_set_state(Client *self, Atom action, long data1, long data2)
1872 {
1873     gboolean shaded = self->shaded;
1874     gboolean fullscreen = self->fullscreen;
1875     gboolean max_horz = self->max_horz;
1876     gboolean max_vert = self->max_vert;
1877     int i;
1878
1879     if (!(action == prop_atoms.net_wm_state_add ||
1880           action == prop_atoms.net_wm_state_remove ||
1881           action == prop_atoms.net_wm_state_toggle))
1882         /* an invalid action was passed to the client message, ignore it */
1883         return; 
1884
1885     for (i = 0; i < 2; ++i) {
1886         Atom state = i == 0 ? data1 : data2;
1887     
1888         if (!state) continue;
1889
1890         /* if toggling, then pick whether we're adding or removing */
1891         if (action == prop_atoms.net_wm_state_toggle) {
1892             if (state == prop_atoms.net_wm_state_modal)
1893                 action = self->modal ? prop_atoms.net_wm_state_remove :
1894                     prop_atoms.net_wm_state_add;
1895             else if (state == prop_atoms.net_wm_state_maximized_vert)
1896                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
1897                     prop_atoms.net_wm_state_add;
1898             else if (state == prop_atoms.net_wm_state_maximized_horz)
1899                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
1900                     prop_atoms.net_wm_state_add;
1901             else if (state == prop_atoms.net_wm_state_shaded)
1902                 action = self->shaded ? prop_atoms.net_wm_state_remove :
1903                     prop_atoms.net_wm_state_add;
1904             else if (state == prop_atoms.net_wm_state_skip_taskbar)
1905                 action = self->skip_taskbar ?
1906                     prop_atoms.net_wm_state_remove :
1907                     prop_atoms.net_wm_state_add;
1908             else if (state == prop_atoms.net_wm_state_skip_pager)
1909                 action = self->skip_pager ?
1910                     prop_atoms.net_wm_state_remove :
1911                     prop_atoms.net_wm_state_add;
1912             else if (state == prop_atoms.net_wm_state_fullscreen)
1913                 action = self->fullscreen ?
1914                     prop_atoms.net_wm_state_remove :
1915                     prop_atoms.net_wm_state_add;
1916             else if (state == prop_atoms.net_wm_state_above)
1917                 action = self->above ? prop_atoms.net_wm_state_remove :
1918                     prop_atoms.net_wm_state_add;
1919             else if (state == prop_atoms.net_wm_state_below)
1920                 action = self->below ? prop_atoms.net_wm_state_remove :
1921                     prop_atoms.net_wm_state_add;
1922         }
1923     
1924         if (action == prop_atoms.net_wm_state_add) {
1925             if (state == prop_atoms.net_wm_state_modal) {
1926                 /* XXX raise here or something? */
1927                 self->modal = TRUE;
1928             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
1929                 max_vert = TRUE;
1930             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
1931                 max_horz = TRUE;
1932             } else if (state == prop_atoms.net_wm_state_shaded) {
1933                 shaded = TRUE;
1934             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
1935                 self->skip_taskbar = TRUE;
1936             } else if (state == prop_atoms.net_wm_state_skip_pager) {
1937                 self->skip_pager = TRUE;
1938             } else if (state == prop_atoms.net_wm_state_fullscreen) {
1939                 fullscreen = TRUE;
1940             } else if (state == prop_atoms.net_wm_state_above) {
1941                 self->above = TRUE;
1942             } else if (state == prop_atoms.net_wm_state_below) {
1943                 self->below = TRUE;
1944             }
1945
1946         } else { /* action == prop_atoms.net_wm_state_remove */
1947             if (state == prop_atoms.net_wm_state_modal) {
1948                 self->modal = FALSE;
1949             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
1950                 max_vert = FALSE;
1951             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
1952                 max_horz = FALSE;
1953             } else if (state == prop_atoms.net_wm_state_shaded) {
1954                 shaded = FALSE;
1955             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
1956                 self->skip_taskbar = FALSE;
1957             } else if (state == prop_atoms.net_wm_state_skip_pager) {
1958                 self->skip_pager = FALSE;
1959             } else if (state == prop_atoms.net_wm_state_fullscreen) {
1960                 fullscreen = FALSE;
1961             } else if (state == prop_atoms.net_wm_state_above) {
1962                 self->above = FALSE;
1963             } else if (state == prop_atoms.net_wm_state_below) {
1964                 self->below = FALSE;
1965             }
1966         }
1967     }
1968     if (max_horz != self->max_horz || max_vert != self->max_vert) {
1969         if (max_horz != self->max_horz && max_vert != self->max_vert) {
1970             /* toggling both */
1971             if (max_horz == max_vert) { /* both going the same way */
1972                 client_maximize(self, max_horz, 0, TRUE);
1973             } else {
1974                 client_maximize(self, max_horz, 1, TRUE);
1975                 client_maximize(self, max_vert, 2, TRUE);
1976             }
1977         } else {
1978             /* toggling one */
1979             if (max_horz != self->max_horz)
1980                 client_maximize(self, max_horz, 1, TRUE);
1981             else
1982                 client_maximize(self, max_vert, 2, TRUE);
1983         }
1984     }
1985     /* change fullscreen state before shading, as it will affect if the window
1986        can shade or not */
1987     if (fullscreen != self->fullscreen)
1988         client_fullscreen(self, fullscreen, TRUE);
1989     if (shaded != self->shaded)
1990         client_shade(self, shaded);
1991     client_calc_layer(self);
1992     client_change_state(self); /* change the hint to relect these changes */
1993 }
1994
1995 Client *client_focus_target(Client *self)
1996 {
1997     Client *child;
1998      
1999     /* if we have a modal child, then focus it, not us */
2000     child = client_find_modal_child(self);
2001     if (child) return child;
2002     return self;
2003 }
2004
2005 gboolean client_focusable(Client *self)
2006 {
2007     /* won't try focus if the client doesn't want it, or if the window isn't
2008        visible on the screen */
2009     return self->frame->visible &&
2010         (self->can_focus || self->focus_notify);
2011 }
2012
2013 gboolean client_focus(Client *self)
2014 {
2015     XEvent ev;
2016
2017     /* choose the correct target */
2018     self = client_focus_target(self);
2019
2020     if (!client_focusable(self))
2021         return FALSE;
2022
2023     /* do a check to see if the window has already been unmapped or destroyed
2024        do this intelligently while watching out for unmaps we've generated
2025        (ignore_unmaps > 0) */
2026     if (XCheckTypedWindowEvent(ob_display, self->window,
2027                                DestroyNotify, &ev)) {
2028         XPutBackEvent(ob_display, &ev);
2029         return FALSE;
2030     }
2031     while (XCheckTypedWindowEvent(ob_display, self->window,
2032                                   UnmapNotify, &ev)) {
2033         if (self->ignore_unmaps) {
2034             self->ignore_unmaps--;
2035         } else {
2036             XPutBackEvent(ob_display, &ev);
2037             return FALSE;
2038         }
2039     }
2040
2041     if (self->can_focus)
2042         /* RevertToPointerRoot causes much more headache than TevertToNone, so
2043            I choose to use it always, hopefully to find errors quicker, if any
2044            are left. (I hate X. I hate focus events.) */
2045         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
2046                        event_lasttime);
2047
2048     if (self->focus_notify) {
2049         XEvent ce;
2050         ce.xclient.type = ClientMessage;
2051         ce.xclient.message_type = prop_atoms.wm_protocols;
2052         ce.xclient.display = ob_display;
2053         ce.xclient.window = self->window;
2054         ce.xclient.format = 32;
2055         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2056         ce.xclient.data.l[1] = event_lasttime;
2057         ce.xclient.data.l[2] = 0l;
2058         ce.xclient.data.l[3] = 0l;
2059         ce.xclient.data.l[4] = 0l;
2060         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2061     }
2062
2063 #ifdef DEBUG_FOCUS
2064     g_message("focusing %lx", self->window);
2065 #endif
2066
2067     /* Cause the FocusIn to come back to us. Important for desktop switches,
2068        since otherwise we'll have no FocusIn on the queue and send it off to
2069        the focus_backup. */
2070     XSync(ob_display, FALSE);
2071     return TRUE;
2072 }
2073
2074 void client_unfocus(Client *self)
2075 {
2076     g_assert(focus_client == self);
2077     g_message("client_unfocus");
2078     focus_fallback(FALSE);
2079 }
2080
2081 gboolean client_focused(Client *self)
2082 {
2083     return self == focus_client;
2084 }
2085
2086 Icon *client_icon(Client *self, int w, int h)
2087 {
2088     int i;
2089     /* si is the smallest image >= req */
2090     /* li is the largest image < req */
2091     unsigned long size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2092
2093     if (!self->nicons) return NULL;
2094
2095     for (i = 0; i < self->nicons; ++i) {
2096         size = self->icons[i].width * self->icons[i].height;
2097         if (size < smallest && size >= (unsigned)(w * h)) {
2098             smallest = size;
2099             si = i;
2100         }
2101         if (size > largest && size <= (unsigned)(w * h)) {
2102             largest = size;
2103             li = i;
2104         }
2105     }
2106     if (largest == 0) /* didnt find one smaller than the requested size */
2107         return &self->icons[si];
2108     return &self->icons[li];
2109 }