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