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