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