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