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