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