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