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