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