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