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