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