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