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