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