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