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