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