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