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