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