per app settings
[dana/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2004        Mikael Magnusson
5    Copyright (c) 2003        Ben Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "xerror.h"
25 #include "screen.h"
26 #include "moveresize.h"
27 #include "place.h"
28 #include "prop.h"
29 #include "extensions.h"
30 #include "frame.h"
31 #include "session.h"
32 #include "event.h"
33 #include "grab.h"
34 #include "focus.h"
35 #include "stacking.h"
36 #include "openbox.h"
37 #include "group.h"
38 #include "config.h"
39 #include "menuframe.h"
40 #include "keyboard.h"
41 #include "mouse.h"
42 #include "render/render.h"
43 #include "per_app_settings.h"
44
45 #include <glib.h>
46 #include <X11/Xutil.h>
47
48 /*! The event mask to grab on client windows */
49 #define CLIENT_EVENTMASK (PropertyChangeMask | FocusChangeMask | \
50                           StructureNotifyMask)
51
52 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
53                                 ButtonMotionMask)
54
55 typedef struct
56 {
57     ObClientDestructor func;
58     gpointer data;
59 } Destructor;
60
61 GList      *client_list        = NULL;
62 GSList     *client_destructors = NULL;
63
64 static void client_get_all(ObClient *self);
65 static void client_toggle_border(ObClient *self, gboolean show);
66 static void client_get_startup_id(ObClient *self);
67 static void client_get_area(ObClient *self);
68 static void client_get_desktop(ObClient *self);
69 static void client_get_state(ObClient *self);
70 static void client_get_shaped(ObClient *self);
71 static void client_get_mwm_hints(ObClient *self);
72 static void client_get_gravity(ObClient *self);
73 static void client_showhide(ObClient *self);
74 static void client_change_allowed_actions(ObClient *self);
75 static void client_change_state(ObClient *self);
76 static void client_apply_startup_state(ObClient *self);
77 static void client_restore_session_state(ObClient *self);
78 static void client_restore_session_stacking(ObClient *self);
79 static void client_urgent_notify(ObClient *self);
80
81 void client_startup(gboolean reconfig)
82 {
83     if (reconfig) return;
84
85     client_set_list();
86 }
87
88 void client_shutdown(gboolean reconfig)
89 {
90 }
91
92 void client_add_destructor(ObClientDestructor func, gpointer data)
93 {
94     Destructor *d = g_new(Destructor, 1);
95     d->func = func;
96     d->data = data;
97     client_destructors = g_slist_prepend(client_destructors, d);
98 }
99
100 void client_remove_destructor(ObClientDestructor func)
101 {
102     GSList *it;
103
104     for (it = client_destructors; it; it = g_slist_next(it)) {
105         Destructor *d = it->data;
106         if (d->func == func) {
107             g_free(d);
108             client_destructors = g_slist_delete_link(client_destructors, it);
109             break;
110         }
111     }
112 }
113
114 void client_set_list()
115 {
116     Window *windows, *win_it;
117     GList *it;
118     guint size = g_list_length(client_list);
119
120     /* create an array of the window ids */
121     if (size > 0) {
122         windows = g_new(Window, size);
123         win_it = windows;
124         for (it = client_list; it; it = g_list_next(it), ++win_it)
125             *win_it = ((ObClient*)it->data)->window;
126     } else
127         windows = NULL;
128
129     PROP_SETA32(RootWindow(ob_display, ob_screen),
130                 net_client_list, window, (gulong*)windows, size);
131
132     if (windows)
133         g_free(windows);
134
135     stacking_set_list();
136 }
137
138 /*
139   void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
140   {
141   GSList *it;
142
143   for (it = self->transients; it; it = g_slist_next(it)) {
144   if (!func(it->data, data)) return;
145   client_foreach_transient(it->data, func, data);
146   }
147   }
148
149   void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
150   {
151   if (self->transient_for) {
152   if (self->transient_for != OB_TRAN_GROUP) {
153   if (!func(self->transient_for, data)) return;
154   client_foreach_ancestor(self->transient_for, func, data);
155   } else {
156   GSList *it;
157
158   for (it = self->group->members; it; it = g_slist_next(it))
159   if (it->data != self &&
160   !((ObClient*)it->data)->transient_for) {
161   if (!func(it->data, data)) return;
162   client_foreach_ancestor(it->data, func, data);
163   }
164   }
165   }
166   }
167 */
168
169 void client_manage_all()
170 {
171     guint i, j, nchild;
172     Window w, *children;
173     XWMHints *wmhints;
174     XWindowAttributes attrib;
175
176     XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
177                &w, &w, &children, &nchild);
178
179     /* remove all icon windows from the list */
180     for (i = 0; i < nchild; i++) {
181         if (children[i] == None) continue;
182         wmhints = XGetWMHints(ob_display, children[i]);
183         if (wmhints) {
184             if ((wmhints->flags & IconWindowHint) &&
185                 (wmhints->icon_window != children[i]))
186                 for (j = 0; j < nchild; j++)
187                     if (children[j] == wmhints->icon_window) {
188                         children[j] = None;
189                         break;
190                     }
191             XFree(wmhints);
192         }
193     }
194
195     for (i = 0; i < nchild; ++i) {
196         if (children[i] == None)
197             continue;
198         if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
199             if (attrib.override_redirect) continue;
200
201             if (attrib.map_state != IsUnmapped)
202                 client_manage(children[i]);
203         }
204     }
205     XFree(children);
206 }
207
208 void client_manage(Window window)
209 {
210     ObClient *self;
211     XEvent e;
212     XWindowAttributes attrib;
213     XSetWindowAttributes attrib_set;
214     XWMHints *wmhint;
215     gboolean activate = FALSE;
216     ObAppSetting *settings;
217
218     grab_server(TRUE);
219
220     /* check if it has already been unmapped by the time we started mapping
221        the grab does a sync so we don't have to here */
222     if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
223         XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) {
224         XPutBackEvent(ob_display, &e);
225
226         grab_server(FALSE);
227         return; /* don't manage it */
228     }
229
230     /* make sure it isn't an override-redirect window */
231     if (!XGetWindowAttributes(ob_display, window, &attrib) ||
232         attrib.override_redirect) {
233         grab_server(FALSE);
234         return; /* don't manage it */
235     }
236   
237     /* is the window a docking app */
238     if ((wmhint = XGetWMHints(ob_display, window))) {
239         if ((wmhint->flags & StateHint) &&
240             wmhint->initial_state == WithdrawnState) {
241             dock_add(window, wmhint);
242             grab_server(FALSE);
243             XFree(wmhint);
244             return;
245         }
246         XFree(wmhint);
247     }
248
249     ob_debug("Managing window: %lx\n", window);
250
251     /* choose the events we want to receive on the CLIENT window */
252     attrib_set.event_mask = CLIENT_EVENTMASK;
253     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
254     XChangeWindowAttributes(ob_display, window,
255                             CWEventMask|CWDontPropagate, &attrib_set);
256
257
258     /* create the ObClient struct, and populate it from the hints on the
259        window */
260     self = g_new0(ObClient, 1);
261     self->obwin.type = Window_Client;
262     self->window = window;
263
264     /* non-zero defaults */
265     self->title_count = 1;
266     self->wmstate = NormalState;
267     self->layer = -1;
268     self->desktop = screen_num_desktops; /* always an invalid value */
269
270     client_get_all(self);
271     client_restore_session_state(self);
272
273     sn_app_started(self->class);
274
275     /* update the focus lists, do this before the call to change_state or
276        it can end up in the list twice! */
277     focus_order_add_new(self);
278
279     client_change_state(self);
280
281     /* remove the client's border (and adjust re gravity) */
282     client_toggle_border(self, FALSE);
283      
284     /* specify that if we exit, the window should not be destroyed and should
285        be reparented back to root automatically */
286     XChangeSaveSet(ob_display, window, SetModeInsert);
287
288     /* create the decoration frame for the client window */
289     self->frame = frame_new();
290
291     frame_grab_client(self->frame, self);
292
293     grab_server(FALSE);
294
295     client_apply_startup_state(self);
296
297     /* get and set application level settings */
298     settings = (ObAppSetting *) get_client_settings(self);
299
300     if (settings) {
301         if (settings->shade && !settings->decor)
302             settings->decor = TRUE;
303         
304         client_shade(self, settings->shade);
305         client_set_undecorated(self, !settings->decor);
306         
307         if (settings->desktop != -1)
308             client_set_desktop(self, settings->desktop, FALSE);
309
310         client_set_layer(self, settings->layer);
311     }
312
313     stacking_add(CLIENT_AS_WINDOW(self));
314     client_restore_session_stacking(self);
315
316     /* focus the new window? */
317     if (ob_state() != OB_STATE_STARTING &&
318         (config_focus_new || client_search_focus_parent(self)) ||
319         (settings && settings->focus) &&
320         /* note the check against Type_Normal/Dialog, not client_normal(self),
321            which would also include other types. in this case we want more
322            strict rules for focus */
323         (self->type == OB_CLIENT_TYPE_NORMAL ||
324          self->type == OB_CLIENT_TYPE_DIALOG))
325     {        
326         activate = TRUE;
327 #if 0
328         if (self->desktop != screen_desktop) {
329             /* activate the window */
330             activate = TRUE;
331         } else {
332             gboolean group_foc = FALSE;
333
334             if (self->group) {
335                 GSList *it;
336
337                 for (it = self->group->members; it; it = g_slist_next(it))
338                 {
339                     if (client_focused(it->data))
340                     {
341                         group_foc = TRUE;
342                         break;
343                     }
344                 }
345             }
346             if ((group_foc ||
347                  (!self->transient_for && (!self->group ||
348                                            !self->group->members->next))) ||
349                 client_search_focus_tree_full(self) ||
350                 !focus_client ||
351                 !client_normal(focus_client))
352             {
353                 /* activate the window */
354                 activate = TRUE;
355             }
356         }
357 #endif
358     }
359
360     if (ob_state() == OB_STATE_RUNNING) {
361         gint x = self->area.x, ox = x;
362         gint y = self->area.y, oy = y;
363
364         place_client(self, &x, &y);
365
366         if (settings)
367             place_window_from_settings(settings, self, &x, &y);
368
369         /* make sure the window is visible. */
370         client_find_onscreen(self, &x, &y,
371                              self->frame->area.width,
372                              self->frame->area.height,
373                              /* non-normal clients has less rules, and
374                                 windows that are being restored from a
375                                 session do also. we can assume you want
376                                 it back where you saved it. Clients saying
377                                 they placed themselves are subjected to
378                                 harder rules, ones that are placed by
379                                 place.c or by the user are allowed partially
380                                 off-screen and on xinerama divides (ie,
381                                 it is up to the placement routines to avoid
382                                 the xinerama divides) */
383                              ((self->positioned & PPosition) &&
384                               !(self->positioned & USPosition)) &&
385                              client_normal(self) &&
386                              !self->session);
387         if (x != ox || y != oy)          
388             client_move(self, x, y);
389     }
390
391     keyboard_grab_for_client(self, TRUE);
392     mouse_grab_for_client(self, TRUE);
393
394     client_showhide(self);
395
396     /* use client_focus instead of client_activate cuz client_activate does
397        stuff like switch desktops etc and I'm not interested in all that when
398        a window maps since its not based on an action from the user like
399        clicking a window to activate is. so keep the new window out of the way
400        but do focus it. */
401     if (activate) {
402         /* if using focus_delay, stop the timer now so that focus doesn't go
403            moving on us */
404         event_halt_focus_delay();
405
406         client_focus(self);
407         /* since focus can change the stacking orders, if we focus the window
408            then the standard raise it gets is not enough, we need to queue one
409            for after the focus change takes place */
410         client_raise(self);
411     }
412
413     /* client_activate does this but we aret using it so we have to do it
414        here as well */
415     if (screen_showing_desktop)
416         screen_show_desktop(FALSE);
417
418     /* add to client list/map */
419     client_list = g_list_append(client_list, self);
420     g_hash_table_insert(window_map, &self->window, self);
421
422     /* this has to happen after we're in the client_list */
423     screen_update_areas();
424
425     /* update the list hints */
426     client_set_list();
427
428     ob_debug("Managed window 0x%lx (%s)\n", window, self->class);
429 }
430
431 void client_unmanage_all()
432 {
433     while (client_list != NULL)
434         client_unmanage(client_list->data);
435 }
436
437 void client_unmanage(ObClient *self)
438 {
439     guint j;
440     GSList *it;
441
442     ob_debug("Unmanaging window: %lx (%s)\n", self->window, self->class);
443
444     g_assert(self != NULL);
445
446     keyboard_grab_for_client(self, FALSE);
447     mouse_grab_for_client(self, FALSE);
448
449     /* potentially fix focusLast */
450     if (config_focus_last)
451         grab_pointer(TRUE, OB_CURSOR_NONE);
452
453     /* remove the window from our save set */
454     XChangeSaveSet(ob_display, self->window, SetModeDelete);
455
456     /* we dont want events no more */
457     XSelectInput(ob_display, self->window, NoEventMask);
458
459     frame_hide(self->frame);
460
461     client_list = g_list_remove(client_list, self);
462     stacking_remove(self);
463     g_hash_table_remove(window_map, &self->window);
464
465     /* update the focus lists */
466     focus_order_remove(self);
467
468     /* once the client is out of the list, update the struts to remove it's
469        influence */
470     screen_update_areas();
471
472     for (it = client_destructors; it; it = g_slist_next(it)) {
473         Destructor *d = it->data;
474         d->func(self, d->data);
475     }
476         
477     if (focus_client == self) {
478         XEvent e;
479
480         /* focus the last focused window on the desktop, and ignore enter
481            events from the unmap so it doesnt mess with the focus */
482         while (XCheckTypedEvent(ob_display, EnterNotify, &e));
483         /* remove these flags so we don't end up getting focused in the
484            fallback! */
485         self->can_focus = FALSE;
486         self->focus_notify = FALSE;
487         self->modal = FALSE;
488         client_unfocus(self);
489     }
490
491     /* tell our parent(s) that we're gone */
492     if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
493         for (it = self->group->members; it; it = g_slist_next(it))
494             if (it->data != self)
495                 ((ObClient*)it->data)->transients =
496                     g_slist_remove(((ObClient*)it->data)->transients, self);
497     } else if (self->transient_for) {        /* transient of window */
498         self->transient_for->transients =
499             g_slist_remove(self->transient_for->transients, self);
500     }
501
502     /* tell our transients that we're gone */
503     for (it = self->transients; it; it = g_slist_next(it)) {
504         if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
505             ((ObClient*)it->data)->transient_for = NULL;
506             client_calc_layer(it->data);
507         }
508     }
509
510     /* remove from its group */
511     if (self->group) {
512         group_remove(self->group, self);
513         self->group = NULL;
514     }
515
516     /* give the client its border back */
517     client_toggle_border(self, TRUE);
518
519     /* reparent the window out of the frame, and free the frame */
520     frame_release_client(self->frame, self);
521     self->frame = NULL;
522      
523     if (ob_state() != OB_STATE_EXITING) {
524         /* these values should not be persisted across a window
525            unmapping/mapping */
526         PROP_ERASE(self->window, net_wm_desktop);
527         PROP_ERASE(self->window, net_wm_state);
528         PROP_ERASE(self->window, wm_state);
529     } else {
530         /* if we're left in an unmapped state, the client wont be mapped. this
531            is bad, since we will no longer be managing the window on restart */
532         XMapWindow(ob_display, self->window);
533     }
534
535
536     ob_debug("Unmanaged window 0x%lx\n", self->window);
537
538     /* free all data allocated in the client struct */
539     g_slist_free(self->transients);
540     for (j = 0; j < self->nicons; ++j)
541         g_free(self->icons[j].data);
542     if (self->nicons > 0)
543         g_free(self->icons);
544     g_free(self->title);
545     g_free(self->icon_title);
546     g_free(self->name);
547     g_free(self->class);
548     g_free(self->role);
549     g_free(self->sm_client_id);
550     g_free(self);
551      
552     /* update the list hints */
553     client_set_list();
554
555     if (config_focus_last)
556         grab_pointer(FALSE, OB_CURSOR_NONE);
557 }
558
559 static void client_urgent_notify(ObClient *self)
560 {
561     if (self->urgent)
562         frame_flash_start(self->frame);
563     else
564         frame_flash_stop(self->frame);
565 }
566
567 static void client_restore_session_state(ObClient *self)
568 {
569     GList *it;
570
571     if (!(it = session_state_find(self)))
572         return;
573
574     self->session = it->data;
575
576     RECT_SET_POINT(self->area, self->session->x, self->session->y);
577     self->positioned = PPosition;
578     if (self->session->w > 0)
579         self->area.width = self->session->w;
580     if (self->session->h > 0)
581         self->area.height = self->session->h;
582     XResizeWindow(ob_display, self->window,
583                   self->area.width, self->area.height);
584
585     self->desktop = (self->session->desktop == DESKTOP_ALL ?
586                      self->session->desktop :
587                      MIN(screen_num_desktops - 1, self->session->desktop));
588     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
589
590     self->shaded = self->session->shaded;
591     self->iconic = self->session->iconic;
592     self->skip_pager = self->session->skip_pager;
593     self->skip_taskbar = self->session->skip_taskbar;
594     self->fullscreen = self->session->fullscreen;
595     self->above = self->session->above;
596     self->below = self->session->below;
597     self->max_horz = self->session->max_horz;
598     self->max_vert = self->session->max_vert;
599 }
600
601 static void client_restore_session_stacking(ObClient *self)
602 {
603     GList *it;
604
605     if (!self->session) return;
606
607     it = g_list_find(session_saved_state, self->session);
608     for (it = g_list_previous(it); it; it = g_list_previous(it)) {
609         GList *cit;
610
611         for (cit = client_list; cit; cit = g_list_next(cit))
612             if (session_state_cmp(it->data, cit->data))
613                 break;
614         if (cit) {
615             client_calc_layer(self);
616             stacking_below(CLIENT_AS_WINDOW(self),
617                            CLIENT_AS_WINDOW(cit->data));
618             break;
619         }
620     }
621 }
622
623 void client_move_onscreen(ObClient *self, gboolean rude)
624 {
625     gint x = self->area.x;
626     gint y = self->area.y;
627     if (client_find_onscreen(self, &x, &y,
628                              self->frame->area.width,
629                              self->frame->area.height, rude)) {
630         client_move(self, x, y);
631     }
632 }
633
634 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
635                               gboolean rude)
636 {
637     Rect *a;
638     gint ox = *x, oy = *y;
639
640     frame_client_gravity(self->frame, x, y); /* get where the frame
641                                                 would be */
642
643     /* XXX watch for xinerama dead areas */
644     /* This makes sure windows aren't entirely outside of the screen so you
645      * can't see them at all */
646     if (client_normal(self)) {
647         a = screen_area(self->desktop);
648         if (!self->strut.right && *x >= a->x + a->width - 1)
649             *x = a->x + a->width - self->frame->area.width;
650         if (!self->strut.bottom && *y >= a->y + a->height - 1)
651             *y = a->y + a->height - self->frame->area.height;
652         if (!self->strut.left && *x + self->frame->area.width - 1 < a->x)
653             *x = a->x;
654         if (!self->strut.top && *y + self->frame->area.height - 1 < a->y)
655             *y = a->y;
656     }
657
658     /* This here doesn't let windows even a pixel outside the screen,
659      * when called from client_manage, programs placing themselves are
660      * forced completely onscreen, while things like
661      * xterm -geometry resolution-width/2 will work fine. Trying to
662      * place it completely offscreen will be handled in the above code.
663      * Sorry for this confused comment, i am tired. */
664     if (rude) {
665         /* avoid the xinerama monitor divide while we're at it,
666          * remember to fix the placement stuff to avoid it also and
667          * then remove this XXX */
668         a = screen_physical_area_monitor(client_monitor(self));
669         /* dont let windows map/move into the strut unless they
670            are bigger than the available area */
671         if (w <= a->width) {
672             if (!self->strut.left && *x < a->x) *x = a->x;
673             if (!self->strut.right && *x + w > a->x + a->width)
674                 *x = a->x + a->width - w;
675         }
676         if (h <= a->height) {
677             if (!self->strut.top && *y < a->y) *y = a->y;
678             if (!self->strut.bottom && *y + h > a->y + a->height)
679                 *y = a->y + a->height - h;
680         }
681     }
682
683     frame_frame_gravity(self->frame, x, y); /* get where the client
684                                                should be */
685
686     return ox != *x || oy != *y;
687 }
688
689 static void client_toggle_border(ObClient *self, gboolean show)
690 {
691     /* adjust our idea of where the client is, based on its border. When the
692        border is removed, the client should now be considered to be in a
693        different position.
694        when re-adding the border to the client, the same operation needs to be
695        reversed. */
696     gint oldx = self->area.x, oldy = self->area.y;
697     gint x = oldx, y = oldy;
698     switch(self->gravity) {
699     default:
700     case NorthWestGravity:
701     case WestGravity:
702     case SouthWestGravity:
703         break;
704     case NorthEastGravity:
705     case EastGravity:
706     case SouthEastGravity:
707         if (show) x -= self->border_width * 2;
708         else      x += self->border_width * 2;
709         break;
710     case NorthGravity:
711     case SouthGravity:
712     case CenterGravity:
713     case ForgetGravity:
714     case StaticGravity:
715         if (show) x -= self->border_width;
716         else      x += self->border_width;
717         break;
718     }
719     switch(self->gravity) {
720     default:
721     case NorthWestGravity:
722     case NorthGravity:
723     case NorthEastGravity:
724         break;
725     case SouthWestGravity:
726     case SouthGravity:
727     case SouthEastGravity:
728         if (show) y -= self->border_width * 2;
729         else      y += self->border_width * 2;
730         break;
731     case WestGravity:
732     case EastGravity:
733     case CenterGravity:
734     case ForgetGravity:
735     case StaticGravity:
736         if (show) y -= self->border_width;
737         else      y += self->border_width;
738         break;
739     }
740     self->area.x = x;
741     self->area.y = y;
742
743     if (show) {
744         XSetWindowBorderWidth(ob_display, self->window, self->border_width);
745
746         /* move the client so it is back it the right spot _with_ its
747            border! */
748         if (x != oldx || y != oldy)
749             XMoveWindow(ob_display, self->window, x, y);
750     } else
751         XSetWindowBorderWidth(ob_display, self->window, 0);
752 }
753
754
755 static void client_get_all(ObClient *self)
756 {
757     client_get_area(self);
758     client_update_transient_for(self);
759     client_update_wmhints(self);
760     client_get_startup_id(self);
761     client_get_desktop(self);
762     client_get_shaped(self);
763
764     client_get_mwm_hints(self);
765     client_get_type(self);/* this can change the mwmhints for special cases */
766
767     /* The transient hint is used to pick a type, but the type can also affect
768        transiency (dialogs are always made transients). This is Havoc's idea,
769        but it is needed to make some apps work right (eg tsclient). */
770     client_update_transient_for(self);
771
772     client_get_state(self);
773
774     {
775         /* a couple type-based defaults for new windows */
776
777         /* this makes sure that these windows appear on all desktops */
778         if (self->type == OB_CLIENT_TYPE_DESKTOP)
779             self->desktop = DESKTOP_ALL;
780     }
781
782     client_update_protocols(self);
783
784     client_get_gravity(self); /* get the attribute gravity */
785     client_update_normal_hints(self); /* this may override the attribute
786                                          gravity */
787
788     /* got the type, the mwmhints, the protocols, and the normal hints
789        (min/max sizes), so we're ready to set up the decorations/functions */
790     client_setup_decor_and_functions(self);
791   
792     client_update_title(self);
793     client_update_class(self);
794     client_update_sm_client_id(self);
795     client_update_strut(self);
796     client_update_icons(self);
797 }
798
799 static void client_get_startup_id(ObClient *self)
800 {
801     if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
802         if (self->group)
803             PROP_GETS(self->group->leader,
804                       net_startup_id, utf8, &self->startup_id);
805 }
806
807 static void client_get_area(ObClient *self)
808 {
809     XWindowAttributes wattrib;
810     Status ret;
811   
812     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
813     g_assert(ret != BadWindow);
814
815     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
816     self->border_width = wattrib.border_width;
817 }
818
819 static void client_get_desktop(ObClient *self)
820 {
821     guint32 d = screen_num_desktops; /* an always-invalid value */
822
823     if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
824         if (d >= screen_num_desktops && d != DESKTOP_ALL)
825             self->desktop = screen_num_desktops - 1;
826         else
827             self->desktop = d;
828     } else {
829         gboolean trdesk = FALSE;
830
831         if (self->transient_for) {
832             if (self->transient_for != OB_TRAN_GROUP) {
833                 self->desktop = self->transient_for->desktop;
834                 trdesk = TRUE;
835             } else {
836                 GSList *it;
837
838                 for (it = self->group->members; it; it = g_slist_next(it))
839                     if (it->data != self &&
840                         !((ObClient*)it->data)->transient_for) {
841                         self->desktop = ((ObClient*)it->data)->desktop;
842                         trdesk = TRUE;
843                         break;
844                     }
845             }
846         }
847         if (!trdesk) {
848             /* try get from the startup-notification protocol */
849             if (sn_get_desktop(self->startup_id, &self->desktop)) {
850                 if (self->desktop >= screen_num_desktops &&
851                     self->desktop != DESKTOP_ALL)
852                     self->desktop = screen_num_desktops - 1;
853             } else
854                 /* defaults to the current desktop */
855                 self->desktop = screen_desktop;
856         }
857     }
858     if (self->desktop != d) {
859         /* set the desktop hint, to make sure that it always exists */
860         PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
861     }
862 }
863
864 static void client_get_state(ObClient *self)
865 {
866     guint32 *state;
867     guint num;
868   
869     if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
870         gulong i;
871         for (i = 0; i < num; ++i) {
872             if (state[i] == prop_atoms.net_wm_state_modal)
873                 self->modal = TRUE;
874             else if (state[i] == prop_atoms.net_wm_state_shaded)
875                 self->shaded = TRUE;
876             else if (state[i] == prop_atoms.net_wm_state_hidden)
877                 self->iconic = TRUE;
878             else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
879                 self->skip_taskbar = TRUE;
880             else if (state[i] == prop_atoms.net_wm_state_skip_pager)
881                 self->skip_pager = TRUE;
882             else if (state[i] == prop_atoms.net_wm_state_fullscreen)
883                 self->fullscreen = TRUE;
884             else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
885                 self->max_vert = TRUE;
886             else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
887                 self->max_horz = TRUE;
888             else if (state[i] == prop_atoms.net_wm_state_above)
889                 self->above = TRUE;
890             else if (state[i] == prop_atoms.net_wm_state_below)
891                 self->below = TRUE;
892             else if (state[i] == prop_atoms.ob_wm_state_undecorated)
893                 self->undecorated = TRUE;
894         }
895
896         g_free(state);
897     }
898
899     if (!(self->above || self->below)) {
900         if (self->group) {
901             /* apply stuff from the group */
902             GSList *it;
903             gint layer = -2;
904
905             for (it = self->group->members; it; it = g_slist_next(it)) {
906                 ObClient *c = it->data;
907                 if (c != self && !client_search_transient(self, c) &&
908                     client_normal(self) && client_normal(c))
909                 {
910                     layer = MAX(layer,
911                                 (c->above ? 1 : (c->below ? -1 : 0)));
912                 }
913             }
914             switch (layer) {
915             case -1:
916                 self->below = TRUE;
917                 break;
918             case -2:
919             case 0:
920                 break;
921             case 1:
922                 self->above = TRUE;
923                 break;
924             default:
925                 g_assert_not_reached();
926                 break;
927             }
928         }
929     }
930 }
931
932 static void client_get_shaped(ObClient *self)
933 {
934     self->shaped = FALSE;
935 #ifdef   SHAPE
936     if (extensions_shape) {
937         gint foo;
938         guint ufoo;
939         gint s;
940
941         XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
942
943         XShapeQueryExtents(ob_display, self->window, &s, &foo,
944                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
945                            &ufoo);
946         self->shaped = (s != 0);
947     }
948 #endif
949 }
950
951 void client_update_transient_for(ObClient *self)
952 {
953     Window t = None;
954     ObClient *target = NULL;
955
956     if (XGetTransientForHint(ob_display, self->window, &t)) {
957         self->transient = TRUE;
958         if (t != self->window) { /* cant be transient to itself! */
959             target = g_hash_table_lookup(window_map, &t);
960             /* if this happens then we need to check for it*/
961             g_assert(target != self);
962             if (target && !WINDOW_IS_CLIENT(target)) {
963                 /* this can happen when a dialog is a child of
964                    a dockapp, for example */
965                 target = NULL;
966             }
967             
968             if (!target && self->group) {
969                 /* not transient to a client, see if it is transient for a
970                    group */
971                 if (t == self->group->leader ||
972                     t == None ||
973                     t == RootWindow(ob_display, ob_screen))
974                 {
975                     /* window is a transient for its group! */
976                     target = OB_TRAN_GROUP;
977                 }
978             }
979         }
980     } else if (self->type == OB_CLIENT_TYPE_DIALOG && self->group) {
981         self->transient = TRUE;
982         target = OB_TRAN_GROUP;
983     } else
984         self->transient = FALSE;
985
986     /* if anything has changed... */
987     if (target != self->transient_for) {
988         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
989             GSList *it;
990
991             /* remove from old parents */
992             for (it = self->group->members; it; it = g_slist_next(it)) {
993                 ObClient *c = it->data;
994                 if (c != self && !c->transient_for)
995                     c->transients = g_slist_remove(c->transients, self);
996             }
997         } else if (self->transient_for != NULL) { /* transient of window */
998             /* remove from old parent */
999             self->transient_for->transients =
1000                 g_slist_remove(self->transient_for->transients, self);
1001         }
1002         self->transient_for = target;
1003         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
1004             GSList *it;
1005
1006             /* add to new parents */
1007             for (it = self->group->members; it; it = g_slist_next(it)) {
1008                 ObClient *c = it->data;
1009                 if (c != self && !c->transient_for)
1010                     c->transients = g_slist_append(c->transients, self);
1011             }
1012
1013             /* remove all transients which are in the group, that causes
1014                circlular pointer hell of doom */
1015             for (it = self->group->members; it; it = g_slist_next(it)) {
1016                 GSList *sit, *next;
1017                 for (sit = self->transients; sit; sit = next) {
1018                     next = g_slist_next(sit);
1019                     if (sit->data == it->data)
1020                         self->transients =
1021                             g_slist_delete_link(self->transients, sit);
1022                 }
1023             }
1024         } else if (self->transient_for != NULL) { /* transient of window */
1025             /* add to new parent */
1026             self->transient_for->transients =
1027                 g_slist_append(self->transient_for->transients, self);
1028         }
1029     }
1030 }
1031
1032 static void client_get_mwm_hints(ObClient *self)
1033 {
1034     guint num;
1035     guint32 *hints;
1036
1037     self->mwmhints.flags = 0; /* default to none */
1038
1039     if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
1040                     &hints, &num)) {
1041         if (num >= OB_MWM_ELEMENTS) {
1042             self->mwmhints.flags = hints[0];
1043             self->mwmhints.functions = hints[1];
1044             self->mwmhints.decorations = hints[2];
1045         }
1046         g_free(hints);
1047     }
1048 }
1049
1050 void client_get_type(ObClient *self)
1051 {
1052     guint num, i;
1053     guint32 *val;
1054
1055     self->type = -1;
1056   
1057     if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
1058         /* use the first value that we know about in the array */
1059         for (i = 0; i < num; ++i) {
1060             if (val[i] == prop_atoms.net_wm_window_type_desktop)
1061                 self->type = OB_CLIENT_TYPE_DESKTOP;
1062             else if (val[i] == prop_atoms.net_wm_window_type_dock)
1063                 self->type = OB_CLIENT_TYPE_DOCK;
1064             else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
1065                 self->type = OB_CLIENT_TYPE_TOOLBAR;
1066             else if (val[i] == prop_atoms.net_wm_window_type_menu)
1067                 self->type = OB_CLIENT_TYPE_MENU;
1068             else if (val[i] == prop_atoms.net_wm_window_type_utility)
1069                 self->type = OB_CLIENT_TYPE_UTILITY;
1070             else if (val[i] == prop_atoms.net_wm_window_type_splash)
1071                 self->type = OB_CLIENT_TYPE_SPLASH;
1072             else if (val[i] == prop_atoms.net_wm_window_type_dialog)
1073                 self->type = OB_CLIENT_TYPE_DIALOG;
1074             else if (val[i] == prop_atoms.net_wm_window_type_normal)
1075                 self->type = OB_CLIENT_TYPE_NORMAL;
1076             else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
1077                 /* prevent this window from getting any decor or
1078                    functionality */
1079                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1080                                          OB_MWM_FLAG_DECORATIONS);
1081                 self->mwmhints.decorations = 0;
1082                 self->mwmhints.functions = 0;
1083             }
1084             if (self->type != (ObClientType) -1)
1085                 break; /* grab the first legit type */
1086         }
1087         g_free(val);
1088     }
1089     
1090     if (self->type == (ObClientType) -1) {
1091         /*the window type hint was not set, which means we either classify
1092           ourself as a normal window or a dialog, depending on if we are a
1093           transient. */
1094         if (self->transient)
1095             self->type = OB_CLIENT_TYPE_DIALOG;
1096         else
1097             self->type = OB_CLIENT_TYPE_NORMAL;
1098     }
1099 }
1100
1101 void client_update_protocols(ObClient *self)
1102 {
1103     guint32 *proto;
1104     guint num_return, i;
1105
1106     self->focus_notify = FALSE;
1107     self->delete_window = FALSE;
1108
1109     if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1110         for (i = 0; i < num_return; ++i) {
1111             if (proto[i] == prop_atoms.wm_delete_window) {
1112                 /* this means we can request the window to close */
1113                 self->delete_window = TRUE;
1114             } else if (proto[i] == prop_atoms.wm_take_focus)
1115                 /* if this protocol is requested, then the window will be
1116                    notified whenever we want it to receive focus */
1117                 self->focus_notify = TRUE;
1118         }
1119         g_free(proto);
1120     }
1121 }
1122
1123 static void client_get_gravity(ObClient *self)
1124 {
1125     XWindowAttributes wattrib;
1126     Status ret;
1127
1128     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1129     g_assert(ret != BadWindow);
1130     self->gravity = wattrib.win_gravity;
1131 }
1132
1133 void client_update_normal_hints(ObClient *self)
1134 {
1135     XSizeHints size;
1136     glong ret;
1137     gint oldgravity = self->gravity;
1138
1139     /* defaults */
1140     self->min_ratio = 0.0f;
1141     self->max_ratio = 0.0f;
1142     SIZE_SET(self->size_inc, 1, 1);
1143     SIZE_SET(self->base_size, 0, 0);
1144     SIZE_SET(self->min_size, 0, 0);
1145     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1146
1147     /* get the hints from the window */
1148     if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1149         /* normal windows can't request placement! har har
1150         if (!client_normal(self))
1151         */
1152         self->positioned = (size.flags & (PPosition|USPosition));
1153
1154         if (size.flags & PWinGravity) {
1155             self->gravity = size.win_gravity;
1156       
1157             /* if the client has a frame, i.e. has already been mapped and
1158                is changing its gravity */
1159             if (self->frame && self->gravity != oldgravity) {
1160                 /* move our idea of the client's position based on its new
1161                    gravity */
1162                 self->area.x = self->frame->area.x;
1163                 self->area.y = self->frame->area.y;
1164                 frame_frame_gravity(self->frame, &self->area.x, &self->area.y);
1165             }
1166         }
1167
1168         if (size.flags & PAspect) {
1169             if (size.min_aspect.y)
1170                 self->min_ratio =
1171                     (gfloat) size.min_aspect.x / size.min_aspect.y;
1172             if (size.max_aspect.y)
1173                 self->max_ratio =
1174                     (gfloat) size.max_aspect.x / size.max_aspect.y;
1175         }
1176
1177         if (size.flags & PMinSize)
1178             SIZE_SET(self->min_size, size.min_width, size.min_height);
1179     
1180         if (size.flags & PMaxSize)
1181             SIZE_SET(self->max_size, size.max_width, size.max_height);
1182     
1183         if (size.flags & PBaseSize)
1184             SIZE_SET(self->base_size, size.base_width, size.base_height);
1185     
1186         if (size.flags & PResizeInc)
1187             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1188     }
1189 }
1190
1191 void client_setup_decor_and_functions(ObClient *self)
1192 {
1193     /* start with everything (cept fullscreen) */
1194     self->decorations =
1195         (OB_FRAME_DECOR_TITLEBAR |
1196          (ob_rr_theme->show_handle ? OB_FRAME_DECOR_HANDLE : 0) |
1197          OB_FRAME_DECOR_GRIPS |
1198          OB_FRAME_DECOR_BORDER |
1199          OB_FRAME_DECOR_ICON |
1200          OB_FRAME_DECOR_ALLDESKTOPS |
1201          OB_FRAME_DECOR_ICONIFY |
1202          OB_FRAME_DECOR_MAXIMIZE |
1203          OB_FRAME_DECOR_SHADE |
1204          OB_FRAME_DECOR_CLOSE);
1205     self->functions =
1206         (OB_CLIENT_FUNC_RESIZE |
1207          OB_CLIENT_FUNC_MOVE |
1208          OB_CLIENT_FUNC_ICONIFY |
1209          OB_CLIENT_FUNC_MAXIMIZE |
1210          OB_CLIENT_FUNC_SHADE |
1211          OB_CLIENT_FUNC_CLOSE);
1212
1213     if (!(self->min_size.width < self->max_size.width ||
1214           self->min_size.height < self->max_size.height))
1215         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1216
1217     switch (self->type) {
1218     case OB_CLIENT_TYPE_NORMAL:
1219         /* normal windows retain all of the possible decorations and
1220            functionality, and are the only windows that you can fullscreen */
1221         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1222         break;
1223
1224     case OB_CLIENT_TYPE_DIALOG:
1225     case OB_CLIENT_TYPE_UTILITY:
1226         /* these windows cannot be maximized */
1227         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1228         break;
1229
1230     case OB_CLIENT_TYPE_MENU:
1231     case OB_CLIENT_TYPE_TOOLBAR:
1232         /* these windows get less functionality */
1233         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY | OB_CLIENT_FUNC_RESIZE);
1234         break;
1235
1236     case OB_CLIENT_TYPE_DESKTOP:
1237     case OB_CLIENT_TYPE_DOCK:
1238     case OB_CLIENT_TYPE_SPLASH:
1239         /* none of these windows are manipulated by the window manager */
1240         self->decorations = 0;
1241         self->functions = 0;
1242         break;
1243     }
1244
1245     /* Mwm Hints are applied subtractively to what has already been chosen for
1246        decor and functionality */
1247     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1248         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1249             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1250                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1251                 /* if the mwm hints request no handle or title, then all
1252                    decorations are disabled */
1253                 self->decorations = config_theme_keepborder ? OB_FRAME_DECOR_BORDER : 0;
1254         }
1255     }
1256
1257     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1258         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1259             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1260                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1261             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1262                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1263             /* dont let mwm hints kill any buttons
1264                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1265                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1266                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1267                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1268             */
1269             /* dont let mwm hints kill the close button
1270                if (! (self->mwmhints.functions & MwmFunc_Close))
1271                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1272         }
1273     }
1274
1275     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1276         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1277     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1278         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1279     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1280         self->decorations &= ~OB_FRAME_DECOR_GRIPS;
1281
1282     /* can't maximize without moving/resizing */
1283     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1284           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1285           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1286         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1287         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1288     }
1289
1290     /* kill the handle on fully maxed windows */
1291     if (self->max_vert && self->max_horz)
1292         self->decorations &= ~OB_FRAME_DECOR_HANDLE;
1293
1294     /* finally, the user can have requested no decorations, which overrides
1295        everything (but doesnt give it a border if it doesnt have one) */
1296     if (self->undecorated) {
1297         if (config_theme_keepborder)
1298             self->decorations &= OB_FRAME_DECOR_BORDER;
1299         else
1300             self->decorations = 0;
1301     }
1302
1303     /* if we don't have a titlebar, then we cannot shade! */
1304     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1305         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1306
1307     /* now we need to check against rules for the client's current state */
1308     if (self->fullscreen) {
1309         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1310                             OB_CLIENT_FUNC_FULLSCREEN |
1311                             OB_CLIENT_FUNC_ICONIFY);
1312         self->decorations = 0;
1313     }
1314
1315     client_change_allowed_actions(self);
1316
1317     if (self->frame) {
1318         /* adjust the client's decorations, etc. */
1319         client_reconfigure(self);
1320     }
1321 }
1322
1323 static void client_change_allowed_actions(ObClient *self)
1324 {
1325     gulong actions[9];
1326     gint num = 0;
1327
1328     /* desktop windows are kept on all desktops */
1329     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1330         actions[num++] = prop_atoms.net_wm_action_change_desktop;
1331
1332     if (self->functions & OB_CLIENT_FUNC_SHADE)
1333         actions[num++] = prop_atoms.net_wm_action_shade;
1334     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1335         actions[num++] = prop_atoms.net_wm_action_close;
1336     if (self->functions & OB_CLIENT_FUNC_MOVE)
1337         actions[num++] = prop_atoms.net_wm_action_move;
1338     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1339         actions[num++] = prop_atoms.net_wm_action_minimize;
1340     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1341         actions[num++] = prop_atoms.net_wm_action_resize;
1342     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1343         actions[num++] = prop_atoms.net_wm_action_fullscreen;
1344     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1345         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1346         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1347     }
1348
1349     PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1350
1351     /* make sure the window isn't breaking any rules now */
1352
1353     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1354         if (self->frame) client_shade(self, FALSE);
1355         else self->shaded = FALSE;
1356     }
1357     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1358         if (self->frame) client_iconify(self, FALSE, TRUE);
1359         else self->iconic = FALSE;
1360     }
1361     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1362         if (self->frame) client_fullscreen(self, FALSE, TRUE);
1363         else self->fullscreen = FALSE;
1364     }
1365     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1366                                                          self->max_vert)) {
1367         if (self->frame) client_maximize(self, FALSE, 0, TRUE);
1368         else self->max_vert = self->max_horz = FALSE;
1369     }
1370 }
1371
1372 void client_reconfigure(ObClient *self)
1373 {
1374     /* by making this pass FALSE for user, we avoid the emacs event storm where
1375        every configurenotify causes an update in its normal hints, i think this
1376        is generally what we want anyways... */
1377     client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
1378                      self->area.width, self->area.height, FALSE, TRUE);
1379 }
1380
1381 void client_update_wmhints(ObClient *self)
1382 {
1383     XWMHints *hints;
1384     gboolean ur = FALSE;
1385     GSList *it;
1386
1387     /* assume a window takes input if it doesnt specify */
1388     self->can_focus = TRUE;
1389   
1390     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1391         if (hints->flags & InputHint)
1392             self->can_focus = hints->input;
1393
1394         /* only do this when first managing the window *AND* when we aren't
1395            starting up! */
1396         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1397             if (hints->flags & StateHint)
1398                 self->iconic = hints->initial_state == IconicState;
1399
1400         if (hints->flags & XUrgencyHint)
1401             ur = TRUE;
1402
1403         if (!(hints->flags & WindowGroupHint))
1404             hints->window_group = None;
1405
1406         /* did the group state change? */
1407         if (hints->window_group !=
1408             (self->group ? self->group->leader : None)) {
1409             /* remove from the old group if there was one */
1410             if (self->group != NULL) {
1411                 /* remove transients of the group */
1412                 for (it = self->group->members; it; it = g_slist_next(it))
1413                     self->transients = g_slist_remove(self->transients,
1414                                                       it->data);
1415
1416                 /* remove myself from parents in the group */
1417                 if (self->transient_for == OB_TRAN_GROUP) {
1418                     for (it = self->group->members; it;
1419                          it = g_slist_next(it))
1420                     {
1421                         ObClient *c = it->data;
1422
1423                         if (c != self && !c->transient_for)
1424                             c->transients = g_slist_remove(c->transients,
1425                                                            self);
1426                     }
1427                 }
1428
1429                 group_remove(self->group, self);
1430                 self->group = NULL;
1431             }
1432             if (hints->window_group != None) {
1433                 self->group = group_add(hints->window_group, self);
1434
1435                 /* i can only have transients from the group if i am not
1436                    transient myself */
1437                 if (!self->transient_for) {
1438                     /* add other transients of the group that are already
1439                        set up */
1440                     for (it = self->group->members; it;
1441                          it = g_slist_next(it))
1442                     {
1443                         ObClient *c = it->data;
1444                         if (c != self && c->transient_for == OB_TRAN_GROUP)
1445                             self->transients =
1446                                 g_slist_append(self->transients, c);
1447                     }
1448                 }
1449             }
1450
1451             /* because the self->transient flag wont change from this call,
1452                we don't need to update the window's type and such, only its
1453                transient_for, and the transients lists of other windows in
1454                the group may be affected */
1455             client_update_transient_for(self);
1456         }
1457
1458         /* the WM_HINTS can contain an icon */
1459         client_update_icons(self);
1460
1461         XFree(hints);
1462     }
1463
1464     if (ur != self->urgent) {
1465         self->urgent = ur;
1466         /* fire the urgent callback if we're mapped, otherwise, wait until
1467            after we're mapped */
1468         if (self->frame)
1469             client_urgent_notify(self);
1470     }
1471 }
1472
1473 void client_update_title(ObClient *self)
1474 {
1475     GList *it;
1476     guint32 nums;
1477     guint i;
1478     gchar *data = NULL;
1479     gboolean read_title;
1480     gchar *old_title;
1481
1482     old_title = self->title;
1483      
1484     /* try netwm */
1485     if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1486         /* try old x stuff */
1487         if (!PROP_GETS(self->window, wm_name, locale, &data)) {
1488             // http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1489             if (self->transient) {
1490                 data = g_strdup("");
1491                 goto no_number;
1492             } else
1493                 data = g_strdup("Unnamed Window");
1494         }
1495     }
1496
1497     /* did the title change? then reset the title_count */
1498     if (old_title && 0 != strncmp(old_title, data, strlen(data)))
1499         self->title_count = 1;
1500
1501     /* look for duplicates and append a number */
1502     nums = 0;
1503     for (it = client_list; it; it = g_list_next(it))
1504         if (it->data != self) {
1505             ObClient *c = it->data;
1506             if (0 == strncmp(c->title, data, strlen(data)))
1507                 nums |= 1 << c->title_count;
1508         }
1509     /* find first free number */
1510     for (i = 1; i <= 32; ++i)
1511         if (!(nums & (1 << i))) {
1512             if (self->title_count == 1 || i == 1)
1513                 self->title_count = i;
1514             break;
1515         }
1516     /* dont display the number for the first window */
1517     if (self->title_count > 1) {
1518         gchar *ndata;
1519         ndata = g_strdup_printf("%s - [%u]", data, self->title_count);
1520         g_free(data);
1521         data = ndata;
1522     }
1523
1524     PROP_SETS(self->window, net_wm_visible_name, data);
1525 no_number:
1526     self->title = data;
1527
1528     if (self->frame)
1529         frame_adjust_title(self->frame);
1530
1531     g_free(old_title);
1532
1533     /* update the icon title */
1534     data = NULL;
1535     g_free(self->icon_title);
1536
1537     read_title = TRUE;
1538     /* try netwm */
1539     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1540         /* try old x stuff */
1541         if (!PROP_GETS(self->window, wm_icon_name, locale, &data)) {
1542             data = g_strdup(self->title);
1543             read_title = FALSE;
1544         }
1545
1546     /* append the title count, dont display the number for the first window */
1547     if (read_title && self->title_count > 1) {
1548         gchar *vdata, *ndata;
1549         ndata = g_strdup_printf(" - [%u]", self->title_count);
1550         vdata = g_strconcat(data, ndata, NULL);
1551         g_free(ndata);
1552         g_free(data);
1553         data = vdata;
1554     }
1555
1556     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1557
1558     self->icon_title = data;
1559 }
1560
1561 void client_update_class(ObClient *self)
1562 {
1563     gchar **data;
1564     gchar *s;
1565
1566     if (self->name) g_free(self->name);
1567     if (self->class) g_free(self->class);
1568     if (self->role) g_free(self->role);
1569
1570     self->name = self->class = self->role = NULL;
1571
1572     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1573         if (data[0]) {
1574             self->name = g_strdup(data[0]);
1575             if (data[1])
1576                 self->class = g_strdup(data[1]);
1577         }
1578         g_strfreev(data);     
1579     }
1580
1581     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1582         self->role = s;
1583
1584     if (self->name == NULL) self->name = g_strdup("");
1585     if (self->class == NULL) self->class = g_strdup("");
1586     if (self->role == NULL) self->role = g_strdup("");
1587 }
1588
1589 void client_update_strut(ObClient *self)
1590 {
1591     guint num;
1592     guint32 *data;
1593     gboolean got = FALSE;
1594     StrutPartial strut;
1595
1596     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1597                     &data, &num)) {
1598         if (num == 12) {
1599             got = TRUE;
1600             STRUT_PARTIAL_SET(strut,
1601                               data[0], data[2], data[1], data[3],
1602                               data[4], data[5], data[8], data[9],
1603                               data[6], data[7], data[10], data[11]);
1604         }
1605         g_free(data);
1606     }
1607
1608     if (!got &&
1609         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1610         if (num == 4) {
1611             const Rect *a;
1612
1613             got = TRUE;
1614
1615             /* use the screen's width/height */
1616             a = screen_physical_area();
1617
1618             STRUT_PARTIAL_SET(strut,
1619                               data[0], data[2], data[1], data[3],
1620                               a->y, a->y + a->height - 1,
1621                               a->x, a->x + a->width - 1,
1622                               a->y, a->y + a->height - 1,
1623                               a->x, a->x + a->width - 1);
1624         }
1625         g_free(data);
1626     }
1627
1628     if (!got)
1629         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1630                           0, 0, 0, 0, 0, 0, 0, 0);
1631
1632     if (!STRUT_EQUAL(strut, self->strut)) {
1633         self->strut = strut;
1634
1635         /* updating here is pointless while we're being mapped cuz we're not in
1636            the client list yet */
1637         if (self->frame)
1638             screen_update_areas();
1639     }
1640 }
1641
1642 void client_update_icons(ObClient *self)
1643 {
1644     guint num;
1645     guint32 *data;
1646     guint w, h, i, j;
1647
1648     for (i = 0; i < self->nicons; ++i)
1649         g_free(self->icons[i].data);
1650     if (self->nicons > 0)
1651         g_free(self->icons);
1652     self->nicons = 0;
1653
1654     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1655         /* figure out how many valid icons are in here */
1656         i = 0;
1657         while (num - i > 2) {
1658             w = data[i++];
1659             h = data[i++];
1660             i += w * h;
1661             if (i > num || w*h == 0) break;
1662             ++self->nicons;
1663         }
1664
1665         self->icons = g_new(ObClientIcon, self->nicons);
1666     
1667         /* store the icons */
1668         i = 0;
1669         for (j = 0; j < self->nicons; ++j) {
1670             guint x, y, t;
1671
1672             w = self->icons[j].width = data[i++];
1673             h = self->icons[j].height = data[i++];
1674
1675             if (w*h == 0) continue;
1676
1677             self->icons[j].data = g_new(RrPixel32, w * h);
1678             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1679                 if (x >= w) {
1680                     x = 0;
1681                     ++y;
1682                 }
1683                 self->icons[j].data[t] =
1684                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1685                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1686                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1687                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1688             }
1689             g_assert(i <= num);
1690         }
1691
1692         g_free(data);
1693     } else if (PROP_GETA32(self->window, kwm_win_icon,
1694                            kwm_win_icon, &data, &num)) {
1695         if (num == 2) {
1696             self->nicons++;
1697             self->icons = g_new(ObClientIcon, self->nicons);
1698             xerror_set_ignore(TRUE);
1699             if (!RrPixmapToRGBA(ob_rr_inst,
1700                                 data[0], data[1],
1701                                 &self->icons[self->nicons-1].width,
1702                                 &self->icons[self->nicons-1].height,
1703                                 &self->icons[self->nicons-1].data)) {
1704                 g_free(&self->icons[self->nicons-1]);
1705                 self->nicons--;
1706             }
1707             xerror_set_ignore(FALSE);
1708         }
1709         g_free(data);
1710     } else {
1711         XWMHints *hints;
1712
1713         if ((hints = XGetWMHints(ob_display, self->window))) {
1714             if (hints->flags & IconPixmapHint) {
1715                 self->nicons++;
1716                 self->icons = g_new(ObClientIcon, self->nicons);
1717                 xerror_set_ignore(TRUE);
1718                 if (!RrPixmapToRGBA(ob_rr_inst,
1719                                     hints->icon_pixmap,
1720                                     (hints->flags & IconMaskHint ?
1721                                      hints->icon_mask : None),
1722                                     &self->icons[self->nicons-1].width,
1723                                     &self->icons[self->nicons-1].height,
1724                                     &self->icons[self->nicons-1].data)){
1725                     g_free(&self->icons[self->nicons-1]);
1726                     self->nicons--;
1727                 }
1728                 xerror_set_ignore(FALSE);
1729             }
1730             XFree(hints);
1731         }
1732     }
1733
1734     if (self->frame)
1735         frame_adjust_icon(self->frame);
1736 }
1737
1738 static void client_change_state(ObClient *self)
1739 {
1740     gulong state[2];
1741     gulong netstate[11];
1742     guint num;
1743
1744     state[0] = self->wmstate;
1745     state[1] = None;
1746     PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1747
1748     num = 0;
1749     if (self->modal)
1750         netstate[num++] = prop_atoms.net_wm_state_modal;
1751     if (self->shaded)
1752         netstate[num++] = prop_atoms.net_wm_state_shaded;
1753     if (self->iconic)
1754         netstate[num++] = prop_atoms.net_wm_state_hidden;
1755     if (self->skip_taskbar)
1756         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1757     if (self->skip_pager)
1758         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1759     if (self->fullscreen)
1760         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1761     if (self->max_vert)
1762         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1763     if (self->max_horz)
1764         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1765     if (self->above)
1766         netstate[num++] = prop_atoms.net_wm_state_above;
1767     if (self->below)
1768         netstate[num++] = prop_atoms.net_wm_state_below;
1769     if (self->undecorated)
1770         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1771     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1772
1773     client_calc_layer(self);
1774
1775     if (self->frame)
1776         frame_adjust_state(self->frame);
1777 }
1778
1779 ObClient *client_search_focus_tree(ObClient *self)
1780 {
1781     GSList *it;
1782     ObClient *ret;
1783
1784     for (it = self->transients; it; it = g_slist_next(it)) {
1785         if (client_focused(it->data)) return it->data;
1786         if ((ret = client_search_focus_tree(it->data))) return ret;
1787     }
1788     return NULL;
1789 }
1790
1791 ObClient *client_search_focus_tree_full(ObClient *self)
1792 {
1793     if (self->transient_for) {
1794         if (self->transient_for != OB_TRAN_GROUP) {
1795             return client_search_focus_tree_full(self->transient_for);
1796         } else {
1797             GSList *it;
1798             gboolean recursed = FALSE;
1799         
1800             for (it = self->group->members; it; it = g_slist_next(it))
1801                 if (!((ObClient*)it->data)->transient_for) {
1802                     ObClient *c;
1803                     if ((c = client_search_focus_tree_full(it->data)))
1804                         return c;
1805                     recursed = TRUE;
1806                 }
1807             if (recursed)
1808                 return NULL;
1809         }
1810     }
1811
1812     /* this function checks the whole tree, the client_search_focus_tree~
1813        does not, so we need to check this window */
1814     if (client_focused(self))
1815         return self;
1816     return client_search_focus_tree(self);
1817 }
1818
1819 static ObStackingLayer calc_layer(ObClient *self)
1820 {
1821     ObStackingLayer l;
1822
1823     if (self->fullscreen &&
1824         (client_focused(self) || client_search_focus_tree(self)))
1825         l = OB_STACKING_LAYER_FULLSCREEN;
1826     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1827         l = OB_STACKING_LAYER_DESKTOP;
1828     else if (self->type == OB_CLIENT_TYPE_DOCK) {
1829         if (self->below) l = OB_STACKING_LAYER_NORMAL;
1830         else l = OB_STACKING_LAYER_ABOVE;
1831     }
1832     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1833     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1834     else l = OB_STACKING_LAYER_NORMAL;
1835
1836     return l;
1837 }
1838
1839 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1840                                         ObStackingLayer l, gboolean raised)
1841 {
1842     ObStackingLayer old, own;
1843     GSList *it;
1844
1845     old = self->layer;
1846     own = calc_layer(self);
1847     self->layer = l > own ? l : own;
1848
1849     for (it = self->transients; it; it = g_slist_next(it))
1850         client_calc_layer_recursive(it->data, orig,
1851                                     l, raised ? raised : l != old);
1852
1853     if (!raised && l != old)
1854         if (orig->frame) { /* only restack if the original window is managed */
1855             stacking_remove(CLIENT_AS_WINDOW(self));
1856             stacking_add(CLIENT_AS_WINDOW(self));
1857         }
1858 }
1859
1860 void client_calc_layer(ObClient *self)
1861 {
1862     ObStackingLayer l;
1863     ObClient *orig;
1864
1865     orig = self;
1866
1867     /* transients take on the layer of their parents */
1868     self = client_search_top_transient(self);
1869
1870     l = calc_layer(self);
1871
1872     client_calc_layer_recursive(self, orig, l, FALSE);
1873 }
1874
1875 gboolean client_should_show(ObClient *self)
1876 {
1877     if (self->iconic)
1878         return FALSE;
1879     if (client_normal(self) && screen_showing_desktop)
1880         return FALSE;
1881     /*
1882     if (self->transient_for) {
1883         if (self->transient_for != OB_TRAN_GROUP)
1884             return client_should_show(self->transient_for);
1885         else {
1886             GSList *it;
1887
1888             for (it = self->group->members; it; it = g_slist_next(it)) {
1889                 ObClient *c = it->data;
1890                 if (c != self && !c->transient_for) {
1891                     if (client_should_show(c))
1892                         return TRUE;
1893                 }
1894             }
1895         }
1896     }
1897     */
1898     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
1899         return TRUE;
1900     
1901     return FALSE;
1902 }
1903
1904 static void client_showhide(ObClient *self)
1905 {
1906
1907     if (client_should_show(self))
1908         frame_show(self->frame);
1909     else
1910         frame_hide(self->frame);
1911 }
1912
1913 gboolean client_normal(ObClient *self) {
1914     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
1915               self->type == OB_CLIENT_TYPE_DOCK ||
1916               self->type == OB_CLIENT_TYPE_SPLASH);
1917 }
1918
1919 static void client_apply_startup_state(ObClient *self)
1920 {
1921     /* these are in a carefully crafted order.. */
1922
1923     if (self->iconic) {
1924         self->iconic = FALSE;
1925         client_iconify(self, TRUE, FALSE);
1926     }
1927     if (self->fullscreen) {
1928         self->fullscreen = FALSE;
1929         client_fullscreen(self, TRUE, FALSE);
1930     }
1931     if (self->undecorated) {
1932         self->undecorated = FALSE;
1933         client_set_undecorated(self, TRUE);
1934     }
1935     if (self->shaded) {
1936         self->shaded = FALSE;
1937         client_shade(self, TRUE);
1938     }
1939     if (self->urgent)
1940         client_urgent_notify(self);
1941   
1942     if (self->max_vert && self->max_horz) {
1943         self->max_vert = self->max_horz = FALSE;
1944         client_maximize(self, TRUE, 0, FALSE);
1945     } else if (self->max_vert) {
1946         self->max_vert = FALSE;
1947         client_maximize(self, TRUE, 2, FALSE);
1948     } else if (self->max_horz) {
1949         self->max_horz = FALSE;
1950         client_maximize(self, TRUE, 1, FALSE);
1951     }
1952
1953     /* nothing to do for the other states:
1954        skip_taskbar
1955        skip_pager
1956        modal
1957        above
1958        below
1959     */
1960 }
1961
1962 void client_configure_full(ObClient *self, ObCorner anchor,
1963                            gint x, gint y, gint w, gint h,
1964                            gboolean user, gboolean final,
1965                            gboolean force_reply)
1966 {
1967     gint oldw, oldh;
1968     gboolean send_resize_client;
1969     gboolean moved = FALSE, resized = FALSE;
1970     guint fdecor = self->frame->decorations;
1971     gboolean fhorz = self->frame->max_horz;
1972
1973     /* make the frame recalculate its dimentions n shit without changing
1974        anything visible for real, this way the constraints below can work with
1975        the updated frame dimensions. */
1976     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
1977
1978     /* gets the frame's position */
1979     frame_client_gravity(self->frame, &x, &y);
1980
1981     /* these positions are frame positions, not client positions */
1982
1983     /* set the size and position if fullscreen */
1984     if (self->fullscreen) {
1985 #ifdef VIDMODE
1986         gint dot;
1987         XF86VidModeModeLine mode;
1988 #endif
1989         Rect *a;
1990         guint i;
1991
1992         i = client_monitor(self);
1993         a = screen_physical_area_monitor(i);
1994
1995 #ifdef VIDMODE
1996         if (i == 0 && /* primary head */
1997             extensions_vidmode &&
1998             XF86VidModeGetViewPort(ob_display, ob_screen, &x, &y) &&
1999             /* get the mode last so the mode.privsize isnt freed incorrectly */
2000             XF86VidModeGetModeLine(ob_display, ob_screen, &dot, &mode)) {
2001             x += a->x;
2002             y += a->y;
2003             w = mode.hdisplay;
2004             h = mode.vdisplay;
2005             if (mode.privsize) XFree(mode.private);
2006         } else
2007 #endif
2008         {
2009             x = a->x;
2010             y = a->y;
2011             w = a->width;
2012             h = a->height;
2013         }
2014
2015         user = FALSE; /* ignore that increment etc shit when in fullscreen */
2016     } else {
2017         Rect *a;
2018
2019         a = screen_area_monitor(self->desktop, client_monitor(self));
2020
2021         /* set the size and position if maximized */
2022         if (self->max_horz) {
2023             x = a->x;
2024             w = a->width - self->frame->size.left - self->frame->size.right;
2025         }
2026         if (self->max_vert) {
2027             y = a->y;
2028             h = a->height - self->frame->size.top - self->frame->size.bottom;
2029         }
2030     }
2031
2032     /* gets the client's position */
2033     frame_frame_gravity(self->frame, &x, &y);
2034
2035     /* these override the above states! if you cant move you can't move! */
2036     if (user) {
2037         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2038             x = self->area.x;
2039             y = self->area.y;
2040         }
2041         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2042             w = self->area.width;
2043             h = self->area.height;
2044         }
2045     }
2046
2047     if (!(w == self->area.width && h == self->area.height)) {
2048         gint basew, baseh, minw, minh;
2049
2050         /* base size is substituted with min size if not specified */
2051         if (self->base_size.width || self->base_size.height) {
2052             basew = self->base_size.width;
2053             baseh = self->base_size.height;
2054         } else {
2055             basew = self->min_size.width;
2056             baseh = self->min_size.height;
2057         }
2058         /* min size is substituted with base size if not specified */
2059         if (self->min_size.width || self->min_size.height) {
2060             minw = self->min_size.width;
2061             minh = self->min_size.height;
2062         } else {
2063             minw = self->base_size.width;
2064             minh = self->base_size.height;
2065         }
2066
2067         /* if this is a user-requested resize, then check against min/max
2068            sizes */
2069
2070         /* smaller than min size or bigger than max size? */
2071         if (w > self->max_size.width) w = self->max_size.width;
2072         if (w < minw) w = minw;
2073         if (h > self->max_size.height) h = self->max_size.height;
2074         if (h < minh) h = minh;
2075
2076         w -= basew;
2077         h -= baseh;
2078
2079         /* keep to the increments */
2080         w /= self->size_inc.width;
2081         h /= self->size_inc.height;
2082
2083         /* you cannot resize to nothing */
2084         if (basew + w < 1) w = 1 - basew;
2085         if (baseh + h < 1) h = 1 - baseh;
2086   
2087         /* store the logical size */
2088         SIZE_SET(self->logical_size,
2089                  self->size_inc.width > 1 ? w : w + basew,
2090                  self->size_inc.height > 1 ? h : h + baseh);
2091
2092         w *= self->size_inc.width;
2093         h *= self->size_inc.height;
2094
2095         w += basew;
2096         h += baseh;
2097
2098         /* adjust the height to match the width for the aspect ratios.
2099            for this, min size is not substituted for base size ever. */
2100         w -= self->base_size.width;
2101         h -= self->base_size.height;
2102
2103         if (!self->fullscreen) {
2104             if (self->min_ratio)
2105                 if (h * self->min_ratio > w) {
2106                     h = (gint)(w / self->min_ratio);
2107
2108                     /* you cannot resize to nothing */
2109                     if (h < 1) {
2110                         h = 1;
2111                         w = (gint)(h * self->min_ratio);
2112                     }
2113                 }
2114             if (self->max_ratio)
2115                 if (h * self->max_ratio < w) {
2116                     h = (gint)(w / self->max_ratio);
2117
2118                     /* you cannot resize to nothing */
2119                     if (h < 1) {
2120                         h = 1;
2121                         w = (gint)(h * self->min_ratio);
2122                     }
2123                 }
2124         }
2125
2126         w += self->base_size.width;
2127         h += self->base_size.height;
2128     }
2129
2130     g_assert(w > 0);
2131     g_assert(h > 0);
2132
2133     switch (anchor) {
2134     case OB_CORNER_TOPLEFT:
2135         break;
2136     case OB_CORNER_TOPRIGHT:
2137         x -= w - self->area.width;
2138         break;
2139     case OB_CORNER_BOTTOMLEFT:
2140         y -= h - self->area.height;
2141         break;
2142     case OB_CORNER_BOTTOMRIGHT:
2143         x -= w - self->area.width;
2144         y -= h - self->area.height;
2145         break;
2146     }
2147
2148     moved = x != self->area.x || y != self->area.y;
2149     resized = w != self->area.width || h != self->area.height;
2150
2151     oldw = self->area.width;
2152     oldh = self->area.height;
2153     RECT_SET(self->area, x, y, w, h);
2154
2155     /* for app-requested resizes, always resize if 'resized' is true.
2156        for user-requested ones, only resize if final is true, or when
2157        resizing in redraw mode */
2158     send_resize_client = ((!user && resized) ||
2159                           (user && (final ||
2160                                     (resized && config_resize_redraw))));
2161
2162     /* if the client is enlarging, then resize the client before the frame */
2163     if (send_resize_client && user && (w > oldw || h > oldh))
2164         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2165
2166     /* move/resize the frame to match the request */
2167     if (self->frame) {
2168         if (self->decorations != fdecor || self->max_horz != fhorz)
2169             moved = resized = TRUE;
2170
2171         if (moved || resized)
2172             frame_adjust_area(self->frame, moved, resized, FALSE);
2173
2174         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2175         {
2176             XEvent event;
2177             event.type = ConfigureNotify;
2178             event.xconfigure.display = ob_display;
2179             event.xconfigure.event = self->window;
2180             event.xconfigure.window = self->window;
2181
2182             /* root window real coords */
2183             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2184                 self->border_width;
2185             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2186                 self->border_width;
2187             event.xconfigure.width = w;
2188             event.xconfigure.height = h;
2189             event.xconfigure.border_width = 0;
2190             event.xconfigure.above = self->frame->plate;
2191             event.xconfigure.override_redirect = FALSE;
2192             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2193                        FALSE, StructureNotifyMask, &event);
2194         }
2195     }
2196
2197     /* if the client is shrinking, then resize the frame before the client */
2198     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2199         XResizeWindow(ob_display, self->window, w, h);
2200
2201     XFlush(ob_display);
2202 }
2203
2204 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2205 {
2206     gint x, y, w, h;
2207
2208     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2209         self->fullscreen == fs) return;                   /* already done */
2210
2211     self->fullscreen = fs;
2212     client_change_state(self); /* change the state hints on the client,
2213                                   and adjust out layer/stacking */
2214
2215     if (fs) {
2216         if (savearea)
2217             self->pre_fullscreen_area = self->area;
2218
2219         /* these are not actually used cuz client_configure will set them
2220            as appropriate when the window is fullscreened */
2221         x = y = w = h = 0;
2222     } else {
2223         Rect *a;
2224
2225         if (self->pre_fullscreen_area.width > 0 &&
2226             self->pre_fullscreen_area.height > 0)
2227         {
2228             x = self->pre_fullscreen_area.x;
2229             y = self->pre_fullscreen_area.y;
2230             w = self->pre_fullscreen_area.width;
2231             h = self->pre_fullscreen_area.height;
2232             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2233         } else {
2234             /* pick some fallbacks... */
2235             a = screen_area_monitor(self->desktop, 0);
2236             x = a->x + a->width / 4;
2237             y = a->y + a->height / 4;
2238             w = a->width / 2;
2239             h = a->height / 2;
2240         }
2241     }
2242
2243     client_setup_decor_and_functions(self);
2244
2245     client_move_resize(self, x, y, w, h);
2246
2247     /* try focus us when we go into fullscreen mode */
2248     client_focus(self);
2249 }
2250
2251 static void client_iconify_recursive(ObClient *self,
2252                                      gboolean iconic, gboolean curdesk)
2253 {
2254     GSList *it;
2255     gboolean changed = FALSE;
2256
2257
2258     if (self->iconic != iconic) {
2259         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2260                  self->window);
2261
2262         self->iconic = iconic;
2263
2264         if (iconic) {
2265             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2266                 glong old;
2267
2268                 old = self->wmstate;
2269                 self->wmstate = IconicState;
2270                 if (old != self->wmstate)
2271                     PROP_MSG(self->window, kde_wm_change_state,
2272                              self->wmstate, 1, 0, 0);
2273
2274                 /* update the focus lists.. iconic windows go to the bottom of
2275                    the list, put the new iconic window at the 'top of the
2276                    bottom'. */
2277                 focus_order_to_top(self);
2278
2279                 changed = TRUE;
2280             }
2281         } else {
2282             glong old;
2283
2284             if (curdesk)
2285                 client_set_desktop(self, screen_desktop, FALSE);
2286
2287             old = self->wmstate;
2288             self->wmstate = self->shaded ? IconicState : NormalState;
2289             if (old != self->wmstate)
2290                 PROP_MSG(self->window, kde_wm_change_state,
2291                          self->wmstate, 1, 0, 0);
2292
2293             /* this puts it after the current focused window */
2294             focus_order_remove(self);
2295             focus_order_add_new(self);
2296
2297             /* this is here cuz with the VIDMODE extension, the viewport can
2298                change while a fullscreen window is iconic, and when it
2299                uniconifies, it would be nice if it did so to the new position
2300                of the viewport */
2301             client_reconfigure(self);
2302
2303             changed = TRUE;
2304         }
2305     }
2306
2307     if (changed) {
2308         client_change_state(self);
2309         client_showhide(self);
2310         screen_update_areas();
2311     }
2312
2313     /* iconify all transients */
2314     for (it = self->transients; it; it = g_slist_next(it))
2315         if (it->data != self) client_iconify_recursive(it->data,
2316                                                        iconic, curdesk);
2317 }
2318
2319 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2320 {
2321     /* move up the transient chain as far as possible first */
2322     self = client_search_top_transient(self);
2323
2324     client_iconify_recursive(client_search_top_transient(self),
2325                              iconic, curdesk);
2326 }
2327
2328 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2329 {
2330     gint x, y, w, h;
2331      
2332     g_assert(dir == 0 || dir == 1 || dir == 2);
2333     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2334
2335     /* check if already done */
2336     if (max) {
2337         if (dir == 0 && self->max_horz && self->max_vert) return;
2338         if (dir == 1 && self->max_horz) return;
2339         if (dir == 2 && self->max_vert) return;
2340     } else {
2341         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2342         if (dir == 1 && !self->max_horz) return;
2343         if (dir == 2 && !self->max_vert) return;
2344     }
2345
2346     /* we just tell it to configure in the same place and client_configure
2347        worries about filling the screen with the window */
2348     x = self->area.x;
2349     y = self->area.y;
2350     w = self->area.width;
2351     h = self->area.height;
2352
2353     if (max) {
2354         if (savearea) {
2355             if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2356                 RECT_SET(self->pre_max_area,
2357                          self->area.x, self->pre_max_area.y,
2358                          self->area.width, self->pre_max_area.height);
2359             }
2360             if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2361                 RECT_SET(self->pre_max_area,
2362                          self->pre_max_area.x, self->area.y,
2363                          self->pre_max_area.width, self->area.height);
2364             }
2365         }
2366     } else {
2367         Rect *a;
2368
2369         a = screen_area_monitor(self->desktop, 0);
2370         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2371             if (self->pre_max_area.width > 0) {
2372                 x = self->pre_max_area.x;
2373                 w = self->pre_max_area.width;
2374
2375                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2376                          0, self->pre_max_area.height);
2377             } else {
2378                 /* pick some fallbacks... */
2379                 x = a->x + a->width / 4;
2380                 w = a->width / 2;
2381             }
2382         }
2383         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2384             if (self->pre_max_area.height > 0) {
2385                 y = self->pre_max_area.y;
2386                 h = self->pre_max_area.height;
2387
2388                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2389                          self->pre_max_area.width, 0);
2390             } else {
2391                 /* pick some fallbacks... */
2392                 y = a->y + a->height / 4;
2393                 h = a->height / 2;
2394             }
2395         }
2396     }
2397
2398     if (dir == 0 || dir == 1) /* horz */
2399         self->max_horz = max;
2400     if (dir == 0 || dir == 2) /* vert */
2401         self->max_vert = max;
2402
2403     client_change_state(self); /* change the state hints on the client */
2404
2405     client_setup_decor_and_functions(self);
2406
2407     client_move_resize(self, x, y, w, h);
2408 }
2409
2410 void client_shade(ObClient *self, gboolean shade)
2411 {
2412     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2413          shade) ||                         /* can't shade */
2414         self->shaded == shade) return;     /* already done */
2415
2416     /* when we're iconic, don't change the wmstate */
2417     if (!self->iconic) {
2418         glong old;
2419
2420         old = self->wmstate;
2421         self->wmstate = shade ? IconicState : NormalState;
2422         if (old != self->wmstate)
2423             PROP_MSG(self->window, kde_wm_change_state,
2424                      self->wmstate, 1, 0, 0);
2425     }
2426
2427     self->shaded = shade;
2428     client_change_state(self);
2429     /* resize the frame to just the titlebar */
2430     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2431 }
2432
2433 void client_close(ObClient *self)
2434 {
2435     XEvent ce;
2436
2437     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2438
2439     /* in the case that the client provides no means to requesting that it
2440        close, we just kill it */
2441     if (!self->delete_window)
2442         client_kill(self);
2443     
2444     /*
2445       XXX: itd be cool to do timeouts and shit here for killing the client's
2446       process off
2447       like... if the window is around after 5 seconds, then the close button
2448       turns a nice red, and if this function is called again, the client is
2449       explicitly killed.
2450     */
2451
2452     ce.xclient.type = ClientMessage;
2453     ce.xclient.message_type =  prop_atoms.wm_protocols;
2454     ce.xclient.display = ob_display;
2455     ce.xclient.window = self->window;
2456     ce.xclient.format = 32;
2457     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2458     ce.xclient.data.l[1] = event_lasttime;
2459     ce.xclient.data.l[2] = 0l;
2460     ce.xclient.data.l[3] = 0l;
2461     ce.xclient.data.l[4] = 0l;
2462     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2463 }
2464
2465 void client_kill(ObClient *self)
2466 {
2467     XKillClient(ob_display, self->window);
2468 }
2469
2470 void client_set_desktop_recursive(ObClient *self,
2471                                   guint target, gboolean donthide)
2472 {
2473     guint old;
2474     GSList *it;
2475
2476     if (target != self->desktop) {
2477
2478         ob_debug("Setting desktop %u\n", target+1);
2479
2480         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2481
2482         /* remove from the old desktop(s) */
2483         focus_order_remove(self);
2484
2485         old = self->desktop;
2486         self->desktop = target;
2487         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2488         /* the frame can display the current desktop state */
2489         frame_adjust_state(self->frame);
2490         /* 'move' the window to the new desktop */
2491         if (!donthide)
2492             client_showhide(self);
2493         /* raise if it was not already on the desktop */
2494         if (old != DESKTOP_ALL)
2495             client_raise(self);
2496         screen_update_areas();
2497
2498         /* add to the new desktop(s) */
2499         if (config_focus_new)
2500             focus_order_to_top(self);
2501         else
2502             focus_order_to_bottom(self);
2503     }
2504
2505     /* move all transients */
2506     for (it = self->transients; it; it = g_slist_next(it))
2507         if (it->data != self) client_set_desktop_recursive(it->data,
2508                                                            target, donthide);
2509 }
2510
2511 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2512 {
2513     client_set_desktop_recursive(client_search_top_transient(self),
2514                                  target, donthide);
2515 }
2516
2517 ObClient *client_search_modal_child(ObClient *self)
2518 {
2519     GSList *it;
2520     ObClient *ret;
2521   
2522     for (it = self->transients; it; it = g_slist_next(it)) {
2523         ObClient *c = it->data;
2524         if ((ret = client_search_modal_child(c))) return ret;
2525         if (c->modal) return c;
2526     }
2527     return NULL;
2528 }
2529
2530 gboolean client_validate(ObClient *self)
2531 {
2532     XEvent e; 
2533
2534     XSync(ob_display, FALSE); /* get all events on the server */
2535
2536     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2537         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2538         XPutBackEvent(ob_display, &e);
2539         return FALSE;
2540     }
2541
2542     return TRUE;
2543 }
2544
2545 void client_set_wm_state(ObClient *self, glong state)
2546 {
2547     if (state == self->wmstate) return; /* no change */
2548   
2549     switch (state) {
2550     case IconicState:
2551         client_iconify(self, TRUE, TRUE);
2552         break;
2553     case NormalState:
2554         client_iconify(self, FALSE, TRUE);
2555         break;
2556     }
2557 }
2558
2559 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2560 {
2561     gboolean shaded = self->shaded;
2562     gboolean fullscreen = self->fullscreen;
2563     gboolean undecorated = self->undecorated;
2564     gboolean max_horz = self->max_horz;
2565     gboolean max_vert = self->max_vert;
2566     gboolean modal = self->modal;
2567     gboolean iconic = self->iconic;
2568     gint i;
2569
2570     if (!(action == prop_atoms.net_wm_state_add ||
2571           action == prop_atoms.net_wm_state_remove ||
2572           action == prop_atoms.net_wm_state_toggle))
2573         /* an invalid action was passed to the client message, ignore it */
2574         return; 
2575
2576     for (i = 0; i < 2; ++i) {
2577         Atom state = i == 0 ? data1 : data2;
2578     
2579         if (!state) continue;
2580
2581         /* if toggling, then pick whether we're adding or removing */
2582         if (action == prop_atoms.net_wm_state_toggle) {
2583             if (state == prop_atoms.net_wm_state_modal)
2584                 action = modal ? prop_atoms.net_wm_state_remove :
2585                     prop_atoms.net_wm_state_add;
2586             else if (state == prop_atoms.net_wm_state_maximized_vert)
2587                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2588                     prop_atoms.net_wm_state_add;
2589             else if (state == prop_atoms.net_wm_state_maximized_horz)
2590                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2591                     prop_atoms.net_wm_state_add;
2592             else if (state == prop_atoms.net_wm_state_shaded)
2593                 action = shaded ? prop_atoms.net_wm_state_remove :
2594                     prop_atoms.net_wm_state_add;
2595             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2596                 action = self->skip_taskbar ?
2597                     prop_atoms.net_wm_state_remove :
2598                     prop_atoms.net_wm_state_add;
2599             else if (state == prop_atoms.net_wm_state_skip_pager)
2600                 action = self->skip_pager ?
2601                     prop_atoms.net_wm_state_remove :
2602                     prop_atoms.net_wm_state_add;
2603             else if (state == prop_atoms.net_wm_state_hidden)
2604                 action = self->iconic ?
2605                     prop_atoms.net_wm_state_remove :
2606                     prop_atoms.net_wm_state_add;
2607             else if (state == prop_atoms.net_wm_state_fullscreen)
2608                 action = fullscreen ?
2609                     prop_atoms.net_wm_state_remove :
2610                     prop_atoms.net_wm_state_add;
2611             else if (state == prop_atoms.net_wm_state_above)
2612                 action = self->above ? prop_atoms.net_wm_state_remove :
2613                     prop_atoms.net_wm_state_add;
2614             else if (state == prop_atoms.net_wm_state_below)
2615                 action = self->below ? prop_atoms.net_wm_state_remove :
2616                     prop_atoms.net_wm_state_add;
2617             else if (state == prop_atoms.ob_wm_state_undecorated)
2618                 action = undecorated ? prop_atoms.net_wm_state_remove :
2619                     prop_atoms.net_wm_state_add;
2620         }
2621     
2622         if (action == prop_atoms.net_wm_state_add) {
2623             if (state == prop_atoms.net_wm_state_modal) {
2624                 modal = TRUE;
2625             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2626                 max_vert = TRUE;
2627             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2628                 max_horz = TRUE;
2629             } else if (state == prop_atoms.net_wm_state_shaded) {
2630                 shaded = TRUE;
2631             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2632                 self->skip_taskbar = TRUE;
2633             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2634                 self->skip_pager = TRUE;
2635             } else if (state == prop_atoms.net_wm_state_hidden) {
2636                 iconic = TRUE;
2637             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2638                 fullscreen = TRUE;
2639             } else if (state == prop_atoms.net_wm_state_above) {
2640                 self->above = TRUE;
2641                 self->below = FALSE;
2642             } else if (state == prop_atoms.net_wm_state_below) {
2643                 self->above = FALSE;
2644                 self->below = TRUE;
2645             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2646                 undecorated = TRUE;
2647             }
2648
2649         } else { /* action == prop_atoms.net_wm_state_remove */
2650             if (state == prop_atoms.net_wm_state_modal) {
2651                 modal = FALSE;
2652             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2653                 max_vert = FALSE;
2654             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2655                 max_horz = FALSE;
2656             } else if (state == prop_atoms.net_wm_state_shaded) {
2657                 shaded = FALSE;
2658             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2659                 self->skip_taskbar = FALSE;
2660             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2661                 self->skip_pager = FALSE;
2662             } else if (state == prop_atoms.net_wm_state_hidden) {
2663                 iconic = FALSE;
2664             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2665                 fullscreen = FALSE;
2666             } else if (state == prop_atoms.net_wm_state_above) {
2667                 self->above = FALSE;
2668             } else if (state == prop_atoms.net_wm_state_below) {
2669                 self->below = FALSE;
2670             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2671                 undecorated = FALSE;
2672             }
2673         }
2674     }
2675     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2676         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2677             /* toggling both */
2678             if (max_horz == max_vert) { /* both going the same way */
2679                 client_maximize(self, max_horz, 0, TRUE);
2680             } else {
2681                 client_maximize(self, max_horz, 1, TRUE);
2682                 client_maximize(self, max_vert, 2, TRUE);
2683             }
2684         } else {
2685             /* toggling one */
2686             if (max_horz != self->max_horz)
2687                 client_maximize(self, max_horz, 1, TRUE);
2688             else
2689                 client_maximize(self, max_vert, 2, TRUE);
2690         }
2691     }
2692     /* change fullscreen state before shading, as it will affect if the window
2693        can shade or not */
2694     if (fullscreen != self->fullscreen)
2695         client_fullscreen(self, fullscreen, TRUE);
2696     if (shaded != self->shaded)
2697         client_shade(self, shaded);
2698     if (undecorated != self->undecorated)
2699         client_set_undecorated(self, undecorated);
2700     if (modal != self->modal) {
2701         self->modal = modal;
2702         /* when a window changes modality, then its stacking order with its
2703            transients needs to change */
2704         client_raise(self);
2705     }
2706     if (iconic != self->iconic)
2707         client_iconify(self, iconic, FALSE);
2708
2709     client_calc_layer(self);
2710     client_change_state(self); /* change the hint to reflect these changes */
2711 }
2712
2713 ObClient *client_focus_target(ObClient *self)
2714 {
2715     ObClient *child;
2716      
2717     /* if we have a modal child, then focus it, not us */
2718     child = client_search_modal_child(client_search_top_transient(self));
2719     if (child) return child;
2720     return self;
2721 }
2722
2723 gboolean client_can_focus(ObClient *self)
2724 {
2725     XEvent ev;
2726
2727     /* choose the correct target */
2728     self = client_focus_target(self);
2729
2730     if (!self->frame->visible)
2731         return FALSE;
2732
2733     if (!(self->can_focus || self->focus_notify))
2734         return FALSE;
2735
2736     /* do a check to see if the window has already been unmapped or destroyed
2737        do this intelligently while watching out for unmaps we've generated
2738        (ignore_unmaps > 0) */
2739     if (XCheckTypedWindowEvent(ob_display, self->window,
2740                                DestroyNotify, &ev)) {
2741         XPutBackEvent(ob_display, &ev);
2742         return FALSE;
2743     }
2744     while (XCheckTypedWindowEvent(ob_display, self->window,
2745                                   UnmapNotify, &ev)) {
2746         if (self->ignore_unmaps) {
2747             self->ignore_unmaps--;
2748         } else {
2749             XPutBackEvent(ob_display, &ev);
2750             return FALSE;
2751         }
2752     }
2753
2754     return TRUE;
2755 }
2756
2757 gboolean client_focus(ObClient *self)
2758 {
2759     /* choose the correct target */
2760     self = client_focus_target(self);
2761
2762     if (!client_can_focus(self)) {
2763         if (!self->frame->visible) {
2764             /* update the focus lists */
2765             focus_order_to_top(self);
2766         }
2767         return FALSE;
2768     }
2769
2770     if (self->can_focus) {
2771         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2772            I choose to use it always, hopefully to find errors quicker, if any
2773            are left. (I hate X. I hate focus events.)
2774            
2775            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2776            #799. So now it is RevertToNone again.
2777         */
2778         XSetInputFocus(ob_display, self->window, RevertToNone,
2779                        event_lasttime);
2780     }
2781
2782     if (self->focus_notify) {
2783         XEvent ce;
2784         ce.xclient.type = ClientMessage;
2785         ce.xclient.message_type = prop_atoms.wm_protocols;
2786         ce.xclient.display = ob_display;
2787         ce.xclient.window = self->window;
2788         ce.xclient.format = 32;
2789         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2790         ce.xclient.data.l[1] = event_lasttime;
2791         ce.xclient.data.l[2] = 0l;
2792         ce.xclient.data.l[3] = 0l;
2793         ce.xclient.data.l[4] = 0l;
2794         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2795     }
2796
2797 #ifdef DEBUG_FOCUS
2798     ob_debug("%sively focusing %lx at %d\n",
2799              (self->can_focus ? "act" : "pass"),
2800              self->window, (gint) event_lasttime);
2801 #endif
2802
2803     /* Cause the FocusIn to come back to us. Important for desktop switches,
2804        since otherwise we'll have no FocusIn on the queue and send it off to
2805        the focus_backup. */
2806     XSync(ob_display, FALSE);
2807     return TRUE;
2808 }
2809
2810 void client_unfocus(ObClient *self)
2811 {
2812     if (focus_client == self) {
2813 #ifdef DEBUG_FOCUS
2814         ob_debug("client_unfocus for %lx\n", self->window);
2815 #endif
2816         focus_fallback(OB_FOCUS_FALLBACK_UNFOCUSING);
2817     }
2818 }
2819
2820 void client_activate(ObClient *self, gboolean here)
2821 {
2822     /* This check is for the client_list_menu trying to activate
2823      * a closed client. */
2824     if (!g_list_find(client_list, self)) return;
2825     if (client_normal(self) && screen_showing_desktop)
2826         screen_show_desktop(FALSE);
2827     if (self->iconic)
2828         client_iconify(self, FALSE, here);
2829     if (self->desktop != DESKTOP_ALL &&
2830         self->desktop != screen_desktop) {
2831         if (here)
2832             client_set_desktop(self, screen_desktop, FALSE);
2833         else
2834             screen_set_desktop(self->desktop);
2835     } else if (!self->frame->visible)
2836         /* if its not visible for other reasons, then don't mess
2837            with it */
2838         return;
2839     if (self->shaded)
2840         client_shade(self, FALSE);
2841
2842     client_focus(self);
2843
2844     /* we do this an action here. this is rather important. this is because
2845        we want the results from the focus change to take place BEFORE we go
2846        about raising the window. when a fullscreen window loses focus, we need
2847        this or else the raise wont be able to raise above the to-lose-focus
2848        fullscreen window. */
2849     client_raise(self);
2850 }
2851
2852 void client_raise(ObClient *self)
2853 {
2854     action_run_string("Raise", self);
2855 }
2856
2857 void client_lower(ObClient *self)
2858 {
2859     action_run_string("Lower", self);
2860 }
2861
2862 gboolean client_focused(ObClient *self)
2863 {
2864     return self == focus_client;
2865 }
2866
2867 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
2868 {
2869     guint i;
2870     /* si is the smallest image >= req */
2871     /* li is the largest image < req */
2872     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2873
2874     if (!self->nicons) {
2875         ObClientIcon *parent = NULL;
2876
2877         if (self->transient_for) {
2878             if (self->transient_for != OB_TRAN_GROUP)
2879                 parent = client_icon_recursive(self->transient_for, w, h);
2880             else {
2881                 GSList *it;
2882                 for (it = self->group->members; it; it = g_slist_next(it)) {
2883                     ObClient *c = it->data;
2884                     if (c != self && !c->transient_for) {
2885                         if ((parent = client_icon_recursive(c, w, h)))
2886                             break;
2887                     }
2888                 }
2889             }
2890         }
2891         
2892         return parent;
2893     }
2894
2895     for (i = 0; i < self->nicons; ++i) {
2896         size = self->icons[i].width * self->icons[i].height;
2897         if (size < smallest && size >= (unsigned)(w * h)) {
2898             smallest = size;
2899             si = i;
2900         }
2901         if (size > largest && size <= (unsigned)(w * h)) {
2902             largest = size;
2903             li = i;
2904         }
2905     }
2906     if (largest == 0) /* didnt find one smaller than the requested size */
2907         return &self->icons[si];
2908     return &self->icons[li];
2909 }
2910
2911 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
2912 {
2913     ObClientIcon *ret;
2914     static ObClientIcon deficon;
2915
2916     if (!(ret = client_icon_recursive(self, w, h))) {
2917         deficon.width = deficon.height = 48;
2918         deficon.data = ob_rr_theme->def_win_icon;
2919         ret = &deficon;
2920     }
2921     return ret;
2922 }
2923
2924 /* this be mostly ripped from fvwm */
2925 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2926 {
2927     gint my_cx, my_cy, his_cx, his_cy;
2928     gint offset = 0;
2929     gint distance = 0;
2930     gint score, best_score;
2931     ObClient *best_client, *cur;
2932     GList *it;
2933
2934     if(!client_list)
2935         return NULL;
2936
2937     /* first, find the centre coords of the currently focused window */
2938     my_cx = c->frame->area.x + c->frame->area.width / 2;
2939     my_cy = c->frame->area.y + c->frame->area.height / 2;
2940
2941     best_score = -1;
2942     best_client = NULL;
2943
2944     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
2945         cur = it->data;
2946
2947         /* the currently selected window isn't interesting */
2948         if(cur == c)
2949             continue;
2950         if (!client_normal(cur))
2951             continue;
2952         /* using c->desktop instead of screen_desktop doesn't work if the
2953          * current window was omnipresent, hope this doesn't have any other
2954          * side effects */
2955         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2956             continue;
2957         if(cur->iconic)
2958             continue;
2959         if(client_focus_target(cur) == cur &&
2960            !(cur->can_focus || cur->focus_notify))
2961             continue;
2962
2963         /* find the centre coords of this window, from the
2964          * currently focused window's point of view */
2965         his_cx = (cur->frame->area.x - my_cx)
2966             + cur->frame->area.width / 2;
2967         his_cy = (cur->frame->area.y - my_cy)
2968             + cur->frame->area.height / 2;
2969
2970         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
2971            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
2972             gint tx;
2973             /* Rotate the diagonals 45 degrees counterclockwise.
2974              * To do this, multiply the matrix /+h +h\ with the
2975              * vector (x y).                   \-h +h/
2976              * h = sqrt(0.5). We can set h := 1 since absolute
2977              * distance doesn't matter here. */
2978             tx = his_cx + his_cy;
2979             his_cy = -his_cx + his_cy;
2980             his_cx = tx;
2981         }
2982
2983         switch(dir) {
2984         case OB_DIRECTION_NORTH:
2985         case OB_DIRECTION_SOUTH:
2986         case OB_DIRECTION_NORTHEAST:
2987         case OB_DIRECTION_SOUTHWEST:
2988             offset = (his_cx < 0) ? -his_cx : his_cx;
2989             distance = ((dir == OB_DIRECTION_NORTH ||
2990                          dir == OB_DIRECTION_NORTHEAST) ?
2991                         -his_cy : his_cy);
2992             break;
2993         case OB_DIRECTION_EAST:
2994         case OB_DIRECTION_WEST:
2995         case OB_DIRECTION_SOUTHEAST:
2996         case OB_DIRECTION_NORTHWEST:
2997             offset = (his_cy < 0) ? -his_cy : his_cy;
2998             distance = ((dir == OB_DIRECTION_WEST ||
2999                          dir == OB_DIRECTION_NORTHWEST) ?
3000                         -his_cx : his_cx);
3001             break;
3002         }
3003
3004         /* the target must be in the requested direction */
3005         if(distance <= 0)
3006             continue;
3007
3008         /* Calculate score for this window.  The smaller the better. */
3009         score = distance + offset;
3010
3011         /* windows more than 45 degrees off the direction are
3012          * heavily penalized and will only be chosen if nothing
3013          * else within a million pixels */
3014         if(offset > distance)
3015             score += 1000000;
3016
3017         if(best_score == -1 || score < best_score)
3018             best_client = cur,
3019                 best_score = score;
3020     }
3021
3022     return best_client;
3023 }
3024
3025 void client_set_layer(ObClient *self, gint layer)
3026 {
3027     if (layer < 0) {
3028         self->below = TRUE;
3029         self->above = FALSE;
3030     } else if (layer == 0) {
3031         self->below = self->above = FALSE;
3032     } else {
3033         self->below = FALSE;
3034         self->above = TRUE;
3035     }
3036     client_calc_layer(self);
3037     client_change_state(self); /* reflect this in the state hints */
3038 }
3039
3040 void client_set_undecorated(ObClient *self, gboolean undecorated)
3041 {
3042     if (self->undecorated != undecorated) {
3043         self->undecorated = undecorated;
3044         client_setup_decor_and_functions(self);
3045         /* Make sure the client knows it might have moved. Maybe there is a
3046          * better way of doing this so only one client_configure is sent, but
3047          * since 125 of these are sent per second when moving the window (with
3048          * user = FALSE) i doubt it matters much.
3049          */
3050         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3051                          self->area.width, self->area.height, TRUE, TRUE);
3052         client_change_state(self); /* reflect this in the state hints */
3053     }
3054 }
3055
3056 /* Determines which physical monitor a client is on by calculating the
3057    area of the part of the client on each monitor.  The number of the
3058    monitor containing the greatest area of the client is returned.*/
3059 guint client_monitor(ObClient *self)
3060 {
3061     guint i;
3062     guint most = 0;
3063     guint mostv = 0;
3064
3065     for (i = 0; i < screen_num_monitors; ++i) {
3066         Rect *area = screen_physical_area_monitor(i);
3067         if (RECT_INTERSECTS_RECT(*area, self->frame->area)) {
3068             Rect r;
3069             guint v;
3070
3071             RECT_SET_INTERSECTION(r, *area, self->frame->area);
3072             v = r.width * r.height;
3073
3074             if (v > mostv) {
3075                 mostv = v;
3076                 most = i;
3077             }
3078         }
3079     }
3080     return most;
3081 }
3082
3083 ObClient *client_search_top_transient(ObClient *self)
3084 {
3085     /* move up the transient chain as far as possible */
3086     if (self->transient_for) {
3087         if (self->transient_for != OB_TRAN_GROUP) {
3088             return client_search_top_transient(self->transient_for);
3089         } else {
3090             GSList *it;
3091
3092             g_assert(self->group);
3093
3094             for (it = self->group->members; it; it = g_slist_next(it)) {
3095                 ObClient *c = it->data;
3096
3097                 /* checking transient_for prevents infinate loops! */
3098                 if (c != self && !c->transient_for)
3099                     break;
3100             }
3101             if (it)
3102                 return it->data;
3103         }
3104     }
3105
3106     return self;
3107 }
3108
3109 ObClient *client_search_focus_parent(ObClient *self)
3110 {
3111     if (self->transient_for) {
3112         if (self->transient_for != OB_TRAN_GROUP) {
3113             if (client_focused(self->transient_for))
3114                 return self->transient_for;
3115         } else {
3116             GSList *it;
3117
3118             for (it = self->group->members; it; it = g_slist_next(it)) {
3119                 ObClient *c = it->data;
3120
3121                 /* checking transient_for prevents infinate loops! */
3122                 if (c != self && !c->transient_for)
3123                     if (client_focused(c))
3124                         return c;
3125             }
3126         }
3127     }
3128
3129     return NULL;
3130 }
3131
3132 ObClient *client_search_parent(ObClient *self, ObClient *search)
3133 {
3134     if (self->transient_for) {
3135         if (self->transient_for != OB_TRAN_GROUP) {
3136             if (self->transient_for == search)
3137                 return search;
3138         } else {
3139             GSList *it;
3140
3141             for (it = self->group->members; it; it = g_slist_next(it)) {
3142                 ObClient *c = it->data;
3143
3144                 /* checking transient_for prevents infinate loops! */
3145                 if (c != self && !c->transient_for)
3146                     if (c == search)
3147                         return search;
3148             }
3149         }
3150     }
3151
3152     return NULL;
3153 }
3154
3155 ObClient *client_search_transient(ObClient *self, ObClient *search)
3156 {
3157     GSList *sit;
3158
3159     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3160         if (sit->data == search)
3161             return search;
3162         if (client_search_transient(sit->data, search))
3163             return search;
3164     }
3165     return NULL;
3166 }
3167
3168 void client_update_sm_client_id(ObClient *self)
3169 {
3170     g_free(self->sm_client_id);
3171     self->sm_client_id = NULL;
3172
3173     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3174         self->group)
3175         PROP_GETS(self->group->leader, sm_client_id, locale,
3176                   &self->sm_client_id);
3177 }
3178
3179 /* finds the nearest edge in the given direction from the current client
3180  * note to self: the edge is the -frame- edge (the actual one), not the
3181  * client edge.
3182  */
3183 gint client_directional_edge_search(ObClient *c, ObDirection dir)
3184 {
3185     gint dest, monitor_dest;
3186     gint my_edge_start, my_edge_end, my_offset;
3187     GList *it;
3188     Rect *a, *monitor;
3189     
3190     if(!client_list)
3191         return -1;
3192
3193     a = screen_area(c->desktop);
3194     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3195
3196     switch(dir) {
3197     case OB_DIRECTION_NORTH:
3198         my_edge_start = c->frame->area.x;
3199         my_edge_end = c->frame->area.x + c->frame->area.width;
3200         my_offset = c->frame->area.y;
3201         
3202         /* default: top of screen */
3203         dest = a->y;
3204         monitor_dest = monitor->y;
3205         /* if the monitor edge comes before the screen edge, */
3206         /* use that as the destination instead. (For xinerama) */
3207         if (monitor_dest != dest && my_offset > monitor_dest)
3208             dest = monitor_dest; 
3209
3210         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3211             gint his_edge_start, his_edge_end, his_offset;
3212             ObClient *cur = it->data;
3213
3214             if(cur == c)
3215                 continue;
3216             if(!client_normal(cur))
3217                 continue;
3218             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3219                 continue;
3220             if(cur->iconic)
3221                 continue;
3222             if(cur->layer < c->layer && !config_resist_layers_below)
3223                 continue;
3224
3225             his_edge_start = cur->frame->area.x;
3226             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3227             his_offset = cur->frame->area.y + cur->frame->area.height;
3228
3229             if(his_offset + 1 > my_offset)
3230                 continue;
3231
3232             if(his_offset < dest)
3233                 continue;
3234             
3235             if(his_edge_start >= my_edge_start &&
3236                his_edge_start <= my_edge_end)
3237                 dest = his_offset;
3238
3239             if(my_edge_start >= his_edge_start &&
3240                my_edge_start <= his_edge_end)
3241                 dest = his_offset;
3242
3243         }
3244         break;
3245     case OB_DIRECTION_SOUTH:
3246         my_edge_start = c->frame->area.x;
3247         my_edge_end = c->frame->area.x + c->frame->area.width;
3248         my_offset = c->frame->area.y + c->frame->area.height;
3249
3250         /* default: bottom of screen */
3251         dest = a->y + a->height;
3252         monitor_dest = monitor->y + monitor->height;
3253         /* if the monitor edge comes before the screen edge, */
3254         /* use that as the destination instead. (For xinerama) */
3255         if (monitor_dest != dest && my_offset < monitor_dest)
3256             dest = monitor_dest; 
3257
3258         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3259             gint his_edge_start, his_edge_end, his_offset;
3260             ObClient *cur = it->data;
3261
3262             if(cur == c)
3263                 continue;
3264             if(!client_normal(cur))
3265                 continue;
3266             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3267                 continue;
3268             if(cur->iconic)
3269                 continue;
3270             if(cur->layer < c->layer && !config_resist_layers_below)
3271                 continue;
3272
3273             his_edge_start = cur->frame->area.x;
3274             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3275             his_offset = cur->frame->area.y;
3276
3277
3278             if(his_offset - 1 < my_offset)
3279                 continue;
3280             
3281             if(his_offset > dest)
3282                 continue;
3283             
3284             if(his_edge_start >= my_edge_start &&
3285                his_edge_start <= my_edge_end)
3286                 dest = his_offset;
3287
3288             if(my_edge_start >= his_edge_start &&
3289                my_edge_start <= his_edge_end)
3290                 dest = his_offset;
3291
3292         }
3293         break;
3294     case OB_DIRECTION_WEST:
3295         my_edge_start = c->frame->area.y;
3296         my_edge_end = c->frame->area.y + c->frame->area.height;
3297         my_offset = c->frame->area.x;
3298
3299         /* default: leftmost egde of screen */
3300         dest = a->x;
3301         monitor_dest = monitor->x;
3302         /* if the monitor edge comes before the screen edge, */
3303         /* use that as the destination instead. (For xinerama) */
3304         if (monitor_dest != dest && my_offset > monitor_dest)
3305             dest = monitor_dest;            
3306
3307         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3308             gint his_edge_start, his_edge_end, his_offset;
3309             ObClient *cur = it->data;
3310
3311             if(cur == c)
3312                 continue;
3313             if(!client_normal(cur))
3314                 continue;
3315             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3316                 continue;
3317             if(cur->iconic)
3318                 continue;
3319             if(cur->layer < c->layer && !config_resist_layers_below)
3320                 continue;
3321
3322             his_edge_start = cur->frame->area.y;
3323             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3324             his_offset = cur->frame->area.x + cur->frame->area.width;
3325
3326             if(his_offset + 1 > my_offset)
3327                 continue;
3328             
3329             if(his_offset < dest)
3330                 continue;
3331             
3332             if(his_edge_start >= my_edge_start &&
3333                his_edge_start <= my_edge_end)
3334                 dest = his_offset;
3335
3336             if(my_edge_start >= his_edge_start &&
3337                my_edge_start <= his_edge_end)
3338                 dest = his_offset;
3339                 
3340
3341         }
3342         break;
3343     case OB_DIRECTION_EAST:
3344         my_edge_start = c->frame->area.y;
3345         my_edge_end = c->frame->area.y + c->frame->area.height;
3346         my_offset = c->frame->area.x + c->frame->area.width;
3347         
3348         /* default: rightmost edge of screen */
3349         dest = a->x + a->width;
3350         monitor_dest = monitor->x + monitor->width;
3351         /* if the monitor edge comes before the screen edge, */
3352         /* use that as the destination instead. (For xinerama) */
3353         if (monitor_dest != dest && my_offset < monitor_dest)
3354             dest = monitor_dest;            
3355
3356         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3357             gint his_edge_start, his_edge_end, his_offset;
3358             ObClient *cur = it->data;
3359
3360             if(cur == c)
3361                 continue;
3362             if(!client_normal(cur))
3363                 continue;
3364             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3365                 continue;
3366             if(cur->iconic)
3367                 continue;
3368             if(cur->layer < c->layer && !config_resist_layers_below)
3369                 continue;
3370
3371             his_edge_start = cur->frame->area.y;
3372             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3373             his_offset = cur->frame->area.x;
3374
3375             if(his_offset - 1 < my_offset)
3376                 continue;
3377             
3378             if(his_offset > dest)
3379                 continue;
3380             
3381             if(his_edge_start >= my_edge_start &&
3382                his_edge_start <= my_edge_end)
3383                 dest = his_offset;
3384
3385             if(my_edge_start >= his_edge_start &&
3386                my_edge_start <= his_edge_end)
3387                 dest = his_offset;
3388
3389         }
3390         break;
3391     case OB_DIRECTION_NORTHEAST:
3392     case OB_DIRECTION_SOUTHEAST:
3393     case OB_DIRECTION_NORTHWEST:
3394     case OB_DIRECTION_SOUTHWEST:
3395         /* not implemented */
3396     default:
3397         g_assert_not_reached();
3398         dest = 0; /* suppress warning */
3399     }
3400     return dest;
3401 }
3402
3403 ObClient* client_under_pointer()
3404 {
3405     gint x, y;
3406     GList *it;
3407     ObClient *ret = NULL;
3408
3409     if (screen_pointer_pos(&x, &y)) {
3410         for (it = stacking_list; it; it = g_list_next(it)) {
3411             if (WINDOW_IS_CLIENT(it->data)) {
3412                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3413                 if (c->frame->visible &&
3414                     RECT_CONTAINS(c->frame->area, x, y)) {
3415                     ret = c;
3416                     break;
3417                 }
3418             }
3419         }
3420     }
3421     return ret;
3422 }
3423
3424 gboolean client_has_group_siblings(ObClient *self)
3425 {
3426     return self->group && self->group->members->next;
3427 }