]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
alert windows can be titleless, maybe all titleless windows should be empty, i dunno
[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     // http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1509     if (self->transient)
1510         data = '\0';
1511     else
1512         data = g_strdup("Unnamed Window");
1513
1514     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1515
1516     self->icon_title = data;
1517 }
1518
1519 void client_update_class(ObClient *self)
1520 {
1521     gchar **data;
1522     gchar *s;
1523
1524     if (self->name) g_free(self->name);
1525     if (self->class) g_free(self->class);
1526     if (self->role) g_free(self->role);
1527
1528     self->name = self->class = self->role = NULL;
1529
1530     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1531         if (data[0]) {
1532             self->name = g_strdup(data[0]);
1533             if (data[1])
1534                 self->class = g_strdup(data[1]);
1535         }
1536         g_strfreev(data);     
1537     }
1538
1539     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1540         self->role = s;
1541
1542     if (self->name == NULL) self->name = g_strdup("");
1543     if (self->class == NULL) self->class = g_strdup("");
1544     if (self->role == NULL) self->role = g_strdup("");
1545 }
1546
1547 void client_update_strut(ObClient *self)
1548 {
1549     guint num;
1550     guint32 *data;
1551     gboolean got = FALSE;
1552     StrutPartial strut;
1553
1554     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1555                     &data, &num)) {
1556         if (num == 12) {
1557             got = TRUE;
1558             STRUT_PARTIAL_SET(strut,
1559                               data[0], data[2], data[1], data[3],
1560                               data[4], data[5], data[8], data[9],
1561                               data[6], data[7], data[10], data[11]);
1562         }
1563         g_free(data);
1564     }
1565
1566     if (!got &&
1567         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1568         if (num == 4) {
1569             const Rect *a;
1570
1571             got = TRUE;
1572
1573             /* use the screen's width/height */
1574             a = screen_physical_area();
1575
1576             STRUT_PARTIAL_SET(strut,
1577                               data[0], data[2], data[1], data[3],
1578                               a->y, a->y + a->height - 1,
1579                               a->x, a->x + a->width - 1,
1580                               a->y, a->y + a->height - 1,
1581                               a->x, a->x + a->width - 1);
1582         }
1583         g_free(data);
1584     }
1585
1586     if (!got)
1587         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1588                           0, 0, 0, 0, 0, 0, 0, 0);
1589
1590     if (!STRUT_EQUAL(strut, self->strut)) {
1591         self->strut = strut;
1592
1593         /* updating here is pointless while we're being mapped cuz we're not in
1594            the client list yet */
1595         if (self->frame)
1596             screen_update_areas();
1597     }
1598 }
1599
1600 void client_update_icons(ObClient *self)
1601 {
1602     guint num;
1603     guint32 *data;
1604     guint w, h, i, j;
1605
1606     for (i = 0; i < self->nicons; ++i)
1607         g_free(self->icons[i].data);
1608     if (self->nicons > 0)
1609         g_free(self->icons);
1610     self->nicons = 0;
1611
1612     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1613         /* figure out how many valid icons are in here */
1614         i = 0;
1615         while (num - i > 2) {
1616             w = data[i++];
1617             h = data[i++];
1618             i += w * h;
1619             if (i > num || w*h == 0) break;
1620             ++self->nicons;
1621         }
1622
1623         self->icons = g_new(ObClientIcon, self->nicons);
1624     
1625         /* store the icons */
1626         i = 0;
1627         for (j = 0; j < self->nicons; ++j) {
1628             guint x, y, t;
1629
1630             w = self->icons[j].width = data[i++];
1631             h = self->icons[j].height = data[i++];
1632
1633             if (w*h == 0) continue;
1634
1635             self->icons[j].data = g_new(RrPixel32, w * h);
1636             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1637                 if (x >= w) {
1638                     x = 0;
1639                     ++y;
1640                 }
1641                 self->icons[j].data[t] =
1642                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1643                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1644                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1645                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1646             }
1647             g_assert(i <= num);
1648         }
1649
1650         g_free(data);
1651     } else if (PROP_GETA32(self->window, kwm_win_icon,
1652                            kwm_win_icon, &data, &num)) {
1653         if (num == 2) {
1654             self->nicons++;
1655             self->icons = g_new(ObClientIcon, self->nicons);
1656             xerror_set_ignore(TRUE);
1657             if (!RrPixmapToRGBA(ob_rr_inst,
1658                                 data[0], data[1],
1659                                 &self->icons[self->nicons-1].width,
1660                                 &self->icons[self->nicons-1].height,
1661                                 &self->icons[self->nicons-1].data)) {
1662                 g_free(&self->icons[self->nicons-1]);
1663                 self->nicons--;
1664             }
1665             xerror_set_ignore(FALSE);
1666         }
1667         g_free(data);
1668     } else {
1669         XWMHints *hints;
1670
1671         if ((hints = XGetWMHints(ob_display, self->window))) {
1672             if (hints->flags & IconPixmapHint) {
1673                 self->nicons++;
1674                 self->icons = g_new(ObClientIcon, self->nicons);
1675                 xerror_set_ignore(TRUE);
1676                 if (!RrPixmapToRGBA(ob_rr_inst,
1677                                     hints->icon_pixmap,
1678                                     (hints->flags & IconMaskHint ?
1679                                      hints->icon_mask : None),
1680                                     &self->icons[self->nicons-1].width,
1681                                     &self->icons[self->nicons-1].height,
1682                                     &self->icons[self->nicons-1].data)){
1683                     g_free(&self->icons[self->nicons-1]);
1684                     self->nicons--;
1685                 }
1686                 xerror_set_ignore(FALSE);
1687             }
1688             XFree(hints);
1689         }
1690     }
1691
1692     if (self->frame)
1693         frame_adjust_icon(self->frame);
1694 }
1695
1696 static void client_change_state(ObClient *self)
1697 {
1698     guint32 state[2];
1699     guint32 netstate[11];
1700     guint num;
1701
1702     state[0] = self->wmstate;
1703     state[1] = None;
1704     PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1705
1706     num = 0;
1707     if (self->modal)
1708         netstate[num++] = prop_atoms.net_wm_state_modal;
1709     if (self->shaded)
1710         netstate[num++] = prop_atoms.net_wm_state_shaded;
1711     if (self->iconic)
1712         netstate[num++] = prop_atoms.net_wm_state_hidden;
1713     if (self->skip_taskbar)
1714         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1715     if (self->skip_pager)
1716         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1717     if (self->fullscreen)
1718         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1719     if (self->max_vert)
1720         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1721     if (self->max_horz)
1722         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1723     if (self->above)
1724         netstate[num++] = prop_atoms.net_wm_state_above;
1725     if (self->below)
1726         netstate[num++] = prop_atoms.net_wm_state_below;
1727     if (self->undecorated)
1728         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1729     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1730
1731     client_calc_layer(self);
1732
1733     if (self->frame)
1734         frame_adjust_state(self->frame);
1735 }
1736
1737 ObClient *client_search_focus_tree(ObClient *self)
1738 {
1739     GSList *it;
1740     ObClient *ret;
1741
1742     for (it = self->transients; it; it = g_slist_next(it)) {
1743         if (client_focused(it->data)) return it->data;
1744         if ((ret = client_search_focus_tree(it->data))) return ret;
1745     }
1746     return NULL;
1747 }
1748
1749 ObClient *client_search_focus_tree_full(ObClient *self)
1750 {
1751     if (self->transient_for) {
1752         if (self->transient_for != OB_TRAN_GROUP) {
1753             return client_search_focus_tree_full(self->transient_for);
1754         } else {
1755             GSList *it;
1756             gboolean recursed = FALSE;
1757         
1758             for (it = self->group->members; it; it = g_slist_next(it))
1759                 if (!((ObClient*)it->data)->transient_for) {
1760                     ObClient *c;
1761                     if ((c = client_search_focus_tree_full(it->data)))
1762                         return c;
1763                     recursed = TRUE;
1764                 }
1765             if (recursed)
1766                 return NULL;
1767         }
1768     }
1769
1770     /* this function checks the whole tree, the client_search_focus_tree~
1771        does not, so we need to check this window */
1772     if (client_focused(self))
1773         return self;
1774     return client_search_focus_tree(self);
1775 }
1776
1777 static ObStackingLayer calc_layer(ObClient *self)
1778 {
1779     ObStackingLayer l;
1780
1781     if (self->fullscreen &&
1782         (client_focused(self) || client_search_focus_tree(self)))
1783         l = OB_STACKING_LAYER_FULLSCREEN;
1784     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1785         l = OB_STACKING_LAYER_DESKTOP;
1786     else if (self->type == OB_CLIENT_TYPE_DOCK) {
1787         if (self->above) l = OB_STACKING_LAYER_DOCK_ABOVE;
1788         else if (self->below) l = OB_STACKING_LAYER_DOCK_BELOW;
1789         else l = OB_STACKING_LAYER_DOCK_NORMAL;
1790     }
1791     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1792     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1793     else l = OB_STACKING_LAYER_NORMAL;
1794
1795     return l;
1796 }
1797
1798 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1799                                         ObStackingLayer l, gboolean raised)
1800 {
1801     ObStackingLayer old, own;
1802     GSList *it;
1803
1804     old = self->layer;
1805     own = calc_layer(self);
1806     self->layer = l > own ? l : own;
1807
1808     for (it = self->transients; it; it = g_slist_next(it))
1809         client_calc_layer_recursive(it->data, orig,
1810                                     l, raised ? raised : l != old);
1811
1812     if (!raised && l != old)
1813         if (orig->frame) { /* only restack if the original window is managed */
1814             stacking_remove(CLIENT_AS_WINDOW(self));
1815             stacking_add(CLIENT_AS_WINDOW(self));
1816         }
1817 }
1818
1819 void client_calc_layer(ObClient *self)
1820 {
1821     ObStackingLayer l;
1822     ObClient *orig;
1823
1824     orig = self;
1825
1826     /* transients take on the layer of their parents */
1827     self = client_search_top_transient(self);
1828
1829     l = calc_layer(self);
1830
1831     client_calc_layer_recursive(self, orig, l, FALSE);
1832 }
1833
1834 gboolean client_should_show(ObClient *self)
1835 {
1836     if (self->iconic)
1837         return FALSE;
1838     if (client_normal(self) && screen_showing_desktop)
1839         return FALSE;
1840     /*
1841     if (self->transient_for) {
1842         if (self->transient_for != OB_TRAN_GROUP)
1843             return client_should_show(self->transient_for);
1844         else {
1845             GSList *it;
1846
1847             for (it = self->group->members; it; it = g_slist_next(it)) {
1848                 ObClient *c = it->data;
1849                 if (c != self && !c->transient_for) {
1850                     if (client_should_show(c))
1851                         return TRUE;
1852                 }
1853             }
1854         }
1855     }
1856     */
1857     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
1858         return TRUE;
1859     
1860     return FALSE;
1861 }
1862
1863 static void client_showhide(ObClient *self)
1864 {
1865
1866     if (client_should_show(self))
1867         frame_show(self->frame);
1868     else
1869         frame_hide(self->frame);
1870 }
1871
1872 gboolean client_normal(ObClient *self) {
1873     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
1874               self->type == OB_CLIENT_TYPE_DOCK ||
1875               self->type == OB_CLIENT_TYPE_SPLASH);
1876 }
1877
1878 static void client_apply_startup_state(ObClient *self)
1879 {
1880     /* these are in a carefully crafted order.. */
1881
1882     if (self->iconic) {
1883         self->iconic = FALSE;
1884         client_iconify(self, TRUE, FALSE);
1885     }
1886     if (self->fullscreen) {
1887         self->fullscreen = FALSE;
1888         client_fullscreen(self, TRUE, FALSE);
1889     }
1890     if (self->undecorated) {
1891         self->undecorated = FALSE;
1892         client_set_undecorated(self, TRUE);
1893     }
1894     if (self->shaded) {
1895         self->shaded = FALSE;
1896         client_shade(self, TRUE);
1897     }
1898     if (self->urgent)
1899         client_urgent_notify(self);
1900   
1901     if (self->max_vert && self->max_horz) {
1902         self->max_vert = self->max_horz = FALSE;
1903         client_maximize(self, TRUE, 0, FALSE);
1904     } else if (self->max_vert) {
1905         self->max_vert = FALSE;
1906         client_maximize(self, TRUE, 2, FALSE);
1907     } else if (self->max_horz) {
1908         self->max_horz = FALSE;
1909         client_maximize(self, TRUE, 1, FALSE);
1910     }
1911
1912     /* nothing to do for the other states:
1913        skip_taskbar
1914        skip_pager
1915        modal
1916        above
1917        below
1918     */
1919 }
1920
1921 void client_configure_full(ObClient *self, ObCorner anchor,
1922                            gint x, gint y, gint w, gint h,
1923                            gboolean user, gboolean final,
1924                            gboolean force_reply)
1925 {
1926     gint oldw, oldh;
1927     gboolean send_resize_client;
1928     gboolean moved = FALSE, resized = FALSE;
1929     guint fdecor = self->frame->decorations;
1930     gboolean fhorz = self->frame->max_horz;
1931
1932     /* make the frame recalculate its dimentions n shit without changing
1933        anything visible for real, this way the constraints below can work with
1934        the updated frame dimensions. */
1935     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
1936
1937     /* gets the frame's position */
1938     frame_client_gravity(self->frame, &x, &y);
1939
1940     /* these positions are frame positions, not client positions */
1941
1942     /* set the size and position if fullscreen */
1943     if (self->fullscreen) {
1944 #ifdef VIDMODE
1945         gint dot;
1946         XF86VidModeModeLine mode;
1947 #endif
1948         Rect *a;
1949         guint i;
1950
1951         i = client_monitor(self);
1952         a = screen_physical_area_monitor(i);
1953
1954 #ifdef VIDMODE
1955         if (i == 0 && /* primary head */
1956             extensions_vidmode &&
1957             XF86VidModeGetViewPort(ob_display, ob_screen, &x, &y) &&
1958             /* get the mode last so the mode.privsize isnt freed incorrectly */
1959             XF86VidModeGetModeLine(ob_display, ob_screen, &dot, &mode)) {
1960             x += a->x;
1961             y += a->y;
1962             w = mode.hdisplay;
1963             h = mode.vdisplay;
1964             if (mode.privsize) XFree(mode.private);
1965         } else
1966 #endif
1967         {
1968             x = a->x;
1969             y = a->y;
1970             w = a->width;
1971             h = a->height;
1972         }
1973
1974         user = FALSE; /* ignore that increment etc shit when in fullscreen */
1975     } else {
1976         Rect *a;
1977
1978         a = screen_area_monitor(self->desktop, client_monitor(self));
1979
1980         /* set the size and position if maximized */
1981         if (self->max_horz) {
1982             x = a->x;
1983             w = a->width - self->frame->size.left - self->frame->size.right;
1984         }
1985         if (self->max_vert) {
1986             y = a->y;
1987             h = a->height - self->frame->size.top - self->frame->size.bottom;
1988         }
1989     }
1990
1991     /* gets the client's position */
1992     frame_frame_gravity(self->frame, &x, &y);
1993
1994     /* these override the above states! if you cant move you can't move! */
1995     if (user) {
1996         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
1997             x = self->area.x;
1998             y = self->area.y;
1999         }
2000         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2001             w = self->area.width;
2002             h = self->area.height;
2003         }
2004     }
2005
2006     if (!(w == self->area.width && h == self->area.height)) {
2007         gint basew, baseh, minw, minh;
2008
2009         /* base size is substituted with min size if not specified */
2010         if (self->base_size.width || self->base_size.height) {
2011             basew = self->base_size.width;
2012             baseh = self->base_size.height;
2013         } else {
2014             basew = self->min_size.width;
2015             baseh = self->min_size.height;
2016         }
2017         /* min size is substituted with base size if not specified */
2018         if (self->min_size.width || self->min_size.height) {
2019             minw = self->min_size.width;
2020             minh = self->min_size.height;
2021         } else {
2022             minw = self->base_size.width;
2023             minh = self->base_size.height;
2024         }
2025
2026         /* if this is a user-requested resize, then check against min/max
2027            sizes */
2028
2029         /* smaller than min size or bigger than max size? */
2030         if (w > self->max_size.width) w = self->max_size.width;
2031         if (w < minw) w = minw;
2032         if (h > self->max_size.height) h = self->max_size.height;
2033         if (h < minh) h = minh;
2034
2035         w -= basew;
2036         h -= baseh;
2037
2038         /* keep to the increments */
2039         w /= self->size_inc.width;
2040         h /= self->size_inc.height;
2041
2042         /* you cannot resize to nothing */
2043         if (basew + w < 1) w = 1 - basew;
2044         if (baseh + h < 1) h = 1 - baseh;
2045   
2046         /* store the logical size */
2047         SIZE_SET(self->logical_size,
2048                  self->size_inc.width > 1 ? w : w + basew,
2049                  self->size_inc.height > 1 ? h : h + baseh);
2050
2051         w *= self->size_inc.width;
2052         h *= self->size_inc.height;
2053
2054         w += basew;
2055         h += baseh;
2056
2057         /* adjust the height to match the width for the aspect ratios.
2058            for this, min size is not substituted for base size ever. */
2059         w -= self->base_size.width;
2060         h -= self->base_size.height;
2061
2062         if (self->min_ratio)
2063             if (h * self->min_ratio > w) {
2064                 h = (gint)(w / self->min_ratio);
2065
2066                 /* you cannot resize to nothing */
2067                 if (h < 1) {
2068                     h = 1;
2069                     w = (gint)(h * self->min_ratio);
2070                 }
2071             }
2072         if (self->max_ratio)
2073             if (h * self->max_ratio < w) {
2074                 h = (gint)(w / self->max_ratio);
2075
2076                 /* you cannot resize to nothing */
2077                 if (h < 1) {
2078                     h = 1;
2079                     w = (gint)(h * self->min_ratio);
2080                 }
2081             }
2082
2083         w += self->base_size.width;
2084         h += self->base_size.height;
2085     }
2086
2087     g_assert(w > 0);
2088     g_assert(h > 0);
2089
2090     switch (anchor) {
2091     case OB_CORNER_TOPLEFT:
2092         break;
2093     case OB_CORNER_TOPRIGHT:
2094         x -= w - self->area.width;
2095         break;
2096     case OB_CORNER_BOTTOMLEFT:
2097         y -= h - self->area.height;
2098         break;
2099     case OB_CORNER_BOTTOMRIGHT:
2100         x -= w - self->area.width;
2101         y -= h - self->area.height;
2102         break;
2103     }
2104
2105     moved = x != self->area.x || y != self->area.y;
2106     resized = w != self->area.width || h != self->area.height;
2107
2108     oldw = self->area.width;
2109     oldh = self->area.height;
2110     RECT_SET(self->area, x, y, w, h);
2111
2112     /* for app-requested resizes, always resize if 'resized' is true.
2113        for user-requested ones, only resize if final is true, or when
2114        resizing in redraw mode */
2115     send_resize_client = ((!user && resized) ||
2116                           (user && (final ||
2117                                     (resized && config_redraw_resize))));
2118
2119     /* if the client is enlarging, the resize the client before the frame */
2120     if (send_resize_client && user && (w > oldw || h > oldh))
2121         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2122
2123     /* move/resize the frame to match the request */
2124     if (self->frame) {
2125         if (self->decorations != fdecor || self->max_horz != fhorz)
2126             moved = resized = TRUE;
2127
2128         if (moved || resized)
2129             frame_adjust_area(self->frame, moved, resized, FALSE);
2130
2131         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2132         {
2133             XEvent event;
2134             event.type = ConfigureNotify;
2135             event.xconfigure.display = ob_display;
2136             event.xconfigure.event = self->window;
2137             event.xconfigure.window = self->window;
2138
2139             /* root window real coords */
2140             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2141                 self->border_width;
2142             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2143                 self->border_width;
2144             event.xconfigure.width = w;
2145             event.xconfigure.height = h;
2146             event.xconfigure.border_width = 0;
2147             event.xconfigure.above = self->frame->plate;
2148             event.xconfigure.override_redirect = FALSE;
2149             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2150                        FALSE, StructureNotifyMask, &event);
2151         }
2152     }
2153
2154     /* if the client is shrinking, then resize the frame before the client */
2155     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2156         XResizeWindow(ob_display, self->window, w, h);
2157
2158     XFlush(ob_display);
2159 }
2160
2161 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2162 {
2163     gint x, y, w, h;
2164
2165     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2166         self->fullscreen == fs) return;                   /* already done */
2167
2168     self->fullscreen = fs;
2169     client_change_state(self); /* change the state hints on the client,
2170                                   and adjust out layer/stacking */
2171
2172     if (fs) {
2173         if (savearea)
2174             self->pre_fullscreen_area = self->area;
2175
2176         /* these are not actually used cuz client_configure will set them
2177            as appropriate when the window is fullscreened */
2178         x = y = w = h = 0;
2179     } else {
2180         Rect *a;
2181
2182         if (self->pre_fullscreen_area.width > 0 &&
2183             self->pre_fullscreen_area.height > 0)
2184         {
2185             x = self->pre_fullscreen_area.x;
2186             y = self->pre_fullscreen_area.y;
2187             w = self->pre_fullscreen_area.width;
2188             h = self->pre_fullscreen_area.height;
2189             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2190         } else {
2191             /* pick some fallbacks... */
2192             a = screen_area_monitor(self->desktop, 0);
2193             x = a->x + a->width / 4;
2194             y = a->y + a->height / 4;
2195             w = a->width / 2;
2196             h = a->height / 2;
2197         }
2198     }
2199
2200     client_setup_decor_and_functions(self);
2201
2202     client_move_resize(self, x, y, w, h);
2203
2204     /* try focus us when we go into fullscreen mode */
2205     client_focus(self);
2206 }
2207
2208 static void client_iconify_recursive(ObClient *self,
2209                                      gboolean iconic, gboolean curdesk)
2210 {
2211     GSList *it;
2212     gboolean changed = FALSE;
2213
2214
2215     if (self->iconic != iconic) {
2216         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2217                  self->window);
2218
2219         self->iconic = iconic;
2220
2221         if (iconic) {
2222             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2223                 glong old;
2224
2225                 old = self->wmstate;
2226                 self->wmstate = IconicState;
2227                 if (old != self->wmstate)
2228                     PROP_MSG(self->window, kde_wm_change_state,
2229                              self->wmstate, 1, 0, 0);
2230
2231                 /* update the focus lists.. iconic windows go to the bottom of
2232                    the list, put the new iconic window at the 'top of the
2233                    bottom'. */
2234                 focus_order_to_top(self);
2235
2236                 changed = TRUE;
2237             }
2238         } else {
2239             glong old;
2240
2241             if (curdesk)
2242                 client_set_desktop(self, screen_desktop, FALSE);
2243
2244             old = self->wmstate;
2245             self->wmstate = self->shaded ? IconicState : NormalState;
2246             if (old != self->wmstate)
2247                 PROP_MSG(self->window, kde_wm_change_state,
2248                          self->wmstate, 1, 0, 0);
2249
2250             /* this puts it after the current focused window */
2251             focus_order_remove(self);
2252             focus_order_add_new(self);
2253
2254             /* this is here cuz with the VIDMODE extension, the viewport can
2255                change while a fullscreen window is iconic, and when it
2256                uniconifies, it would be nice if it did so to the new position
2257                of the viewport */
2258             client_reconfigure(self);
2259
2260             changed = TRUE;
2261         }
2262     }
2263
2264     if (changed) {
2265         client_change_state(self);
2266         client_showhide(self);
2267         screen_update_areas();
2268     }
2269
2270     /* iconify all transients */
2271     for (it = self->transients; it; it = g_slist_next(it))
2272         if (it->data != self) client_iconify_recursive(it->data,
2273                                                        iconic, curdesk);
2274 }
2275
2276 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2277 {
2278     /* move up the transient chain as far as possible first */
2279     self = client_search_top_transient(self);
2280
2281     client_iconify_recursive(client_search_top_transient(self),
2282                              iconic, curdesk);
2283 }
2284
2285 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2286 {
2287     gint x, y, w, h;
2288      
2289     g_assert(dir == 0 || dir == 1 || dir == 2);
2290     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2291
2292     /* check if already done */
2293     if (max) {
2294         if (dir == 0 && self->max_horz && self->max_vert) return;
2295         if (dir == 1 && self->max_horz) return;
2296         if (dir == 2 && self->max_vert) return;
2297     } else {
2298         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2299         if (dir == 1 && !self->max_horz) return;
2300         if (dir == 2 && !self->max_vert) return;
2301     }
2302
2303     /* we just tell it to configure in the same place and client_configure
2304        worries about filling the screen with the window */
2305     x = self->area.x;
2306     y = self->area.y;
2307     w = self->area.width;
2308     h = self->area.height;
2309
2310     if (max) {
2311         if (savearea) {
2312             if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2313                 RECT_SET(self->pre_max_area,
2314                          self->area.x, self->pre_max_area.y,
2315                          self->area.width, self->pre_max_area.height);
2316             }
2317             if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2318                 RECT_SET(self->pre_max_area,
2319                          self->pre_max_area.x, self->area.y,
2320                          self->pre_max_area.width, self->area.height);
2321             }
2322         }
2323     } else {
2324         Rect *a;
2325
2326         a = screen_area_monitor(self->desktop, 0);
2327         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2328             if (self->pre_max_area.width > 0) {
2329                 x = self->pre_max_area.x;
2330                 w = self->pre_max_area.width;
2331
2332                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2333                          0, self->pre_max_area.height);
2334             } else {
2335                 /* pick some fallbacks... */
2336                 x = a->x + a->width / 4;
2337                 w = a->width / 2;
2338             }
2339         }
2340         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2341             if (self->pre_max_area.height > 0) {
2342                 y = self->pre_max_area.y;
2343                 h = self->pre_max_area.height;
2344
2345                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2346                          self->pre_max_area.width, 0);
2347             } else {
2348                 /* pick some fallbacks... */
2349                 y = a->y + a->height / 4;
2350                 h = a->height / 2;
2351             }
2352         }
2353     }
2354
2355     if (dir == 0 || dir == 1) /* horz */
2356         self->max_horz = max;
2357     if (dir == 0 || dir == 2) /* vert */
2358         self->max_vert = max;
2359
2360     client_change_state(self); /* change the state hints on the client */
2361
2362     client_setup_decor_and_functions(self);
2363
2364     client_move_resize(self, x, y, w, h);
2365 }
2366
2367 void client_shade(ObClient *self, gboolean shade)
2368 {
2369     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2370          shade) ||                         /* can't shade */
2371         self->shaded == shade) return;     /* already done */
2372
2373     /* when we're iconic, don't change the wmstate */
2374     if (!self->iconic) {
2375         glong old;
2376
2377         old = self->wmstate;
2378         self->wmstate = shade ? IconicState : NormalState;
2379         if (old != self->wmstate)
2380             PROP_MSG(self->window, kde_wm_change_state,
2381                      self->wmstate, 1, 0, 0);
2382     }
2383
2384     self->shaded = shade;
2385     client_change_state(self);
2386     /* resize the frame to just the titlebar */
2387     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2388 }
2389
2390 void client_close(ObClient *self)
2391 {
2392     XEvent ce;
2393
2394     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2395
2396     /* in the case that the client provides no means to requesting that it
2397        close, we just kill it */
2398     if (!self->delete_window)
2399         client_kill(self);
2400     
2401     /*
2402       XXX: itd be cool to do timeouts and shit here for killing the client's
2403       process off
2404       like... if the window is around after 5 seconds, then the close button
2405       turns a nice red, and if this function is called again, the client is
2406       explicitly killed.
2407     */
2408
2409     ce.xclient.type = ClientMessage;
2410     ce.xclient.message_type =  prop_atoms.wm_protocols;
2411     ce.xclient.display = ob_display;
2412     ce.xclient.window = self->window;
2413     ce.xclient.format = 32;
2414     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2415     ce.xclient.data.l[1] = event_lasttime;
2416     ce.xclient.data.l[2] = 0l;
2417     ce.xclient.data.l[3] = 0l;
2418     ce.xclient.data.l[4] = 0l;
2419     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2420 }
2421
2422 void client_kill(ObClient *self)
2423 {
2424     XKillClient(ob_display, self->window);
2425 }
2426
2427 void client_set_desktop_recursive(ObClient *self,
2428                                   guint target, gboolean donthide)
2429 {
2430     guint old;
2431     GSList *it;
2432
2433     if (target != self->desktop) {
2434
2435         ob_debug("Setting desktop %u\n", target+1);
2436
2437         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2438
2439         /* remove from the old desktop(s) */
2440         focus_order_remove(self);
2441
2442         old = self->desktop;
2443         self->desktop = target;
2444         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2445         /* the frame can display the current desktop state */
2446         frame_adjust_state(self->frame);
2447         /* 'move' the window to the new desktop */
2448         if (!donthide)
2449             client_showhide(self);
2450         /* raise if it was not already on the desktop */
2451         if (old != DESKTOP_ALL)
2452             client_raise(self);
2453         screen_update_areas();
2454
2455         /* add to the new desktop(s) */
2456         if (config_focus_new)
2457             focus_order_to_top(self);
2458         else
2459             focus_order_to_bottom(self);
2460     }
2461
2462     /* move all transients */
2463     for (it = self->transients; it; it = g_slist_next(it))
2464         if (it->data != self) client_set_desktop_recursive(it->data,
2465                                                            target, donthide);
2466 }
2467
2468 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2469 {
2470     client_set_desktop_recursive(client_search_top_transient(self),
2471                                  target, donthide);
2472 }
2473
2474 ObClient *client_search_modal_child(ObClient *self)
2475 {
2476     GSList *it;
2477     ObClient *ret;
2478   
2479     for (it = self->transients; it; it = g_slist_next(it)) {
2480         ObClient *c = it->data;
2481         if ((ret = client_search_modal_child(c))) return ret;
2482         if (c->modal) return c;
2483     }
2484     return NULL;
2485 }
2486
2487 gboolean client_validate(ObClient *self)
2488 {
2489     XEvent e; 
2490
2491     XSync(ob_display, FALSE); /* get all events on the server */
2492
2493     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2494         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2495         XPutBackEvent(ob_display, &e);
2496         return FALSE;
2497     }
2498
2499     return TRUE;
2500 }
2501
2502 void client_set_wm_state(ObClient *self, glong state)
2503 {
2504     if (state == self->wmstate) return; /* no change */
2505   
2506     switch (state) {
2507     case IconicState:
2508         client_iconify(self, TRUE, TRUE);
2509         break;
2510     case NormalState:
2511         client_iconify(self, FALSE, TRUE);
2512         break;
2513     }
2514 }
2515
2516 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2517 {
2518     gboolean shaded = self->shaded;
2519     gboolean fullscreen = self->fullscreen;
2520     gboolean undecorated = self->undecorated;
2521     gboolean max_horz = self->max_horz;
2522     gboolean max_vert = self->max_vert;
2523     gboolean modal = self->modal;
2524     gint i;
2525
2526     if (!(action == prop_atoms.net_wm_state_add ||
2527           action == prop_atoms.net_wm_state_remove ||
2528           action == prop_atoms.net_wm_state_toggle))
2529         /* an invalid action was passed to the client message, ignore it */
2530         return; 
2531
2532     for (i = 0; i < 2; ++i) {
2533         Atom state = i == 0 ? data1 : data2;
2534     
2535         if (!state) continue;
2536
2537         /* if toggling, then pick whether we're adding or removing */
2538         if (action == prop_atoms.net_wm_state_toggle) {
2539             if (state == prop_atoms.net_wm_state_modal)
2540                 action = modal ? prop_atoms.net_wm_state_remove :
2541                     prop_atoms.net_wm_state_add;
2542             else if (state == prop_atoms.net_wm_state_maximized_vert)
2543                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2544                     prop_atoms.net_wm_state_add;
2545             else if (state == prop_atoms.net_wm_state_maximized_horz)
2546                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2547                     prop_atoms.net_wm_state_add;
2548             else if (state == prop_atoms.net_wm_state_shaded)
2549                 action = shaded ? prop_atoms.net_wm_state_remove :
2550                     prop_atoms.net_wm_state_add;
2551             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2552                 action = self->skip_taskbar ?
2553                     prop_atoms.net_wm_state_remove :
2554                     prop_atoms.net_wm_state_add;
2555             else if (state == prop_atoms.net_wm_state_skip_pager)
2556                 action = self->skip_pager ?
2557                     prop_atoms.net_wm_state_remove :
2558                     prop_atoms.net_wm_state_add;
2559             else if (state == prop_atoms.net_wm_state_fullscreen)
2560                 action = fullscreen ?
2561                     prop_atoms.net_wm_state_remove :
2562                     prop_atoms.net_wm_state_add;
2563             else if (state == prop_atoms.net_wm_state_above)
2564                 action = self->above ? prop_atoms.net_wm_state_remove :
2565                     prop_atoms.net_wm_state_add;
2566             else if (state == prop_atoms.net_wm_state_below)
2567                 action = self->below ? prop_atoms.net_wm_state_remove :
2568                     prop_atoms.net_wm_state_add;
2569             else if (state == prop_atoms.ob_wm_state_undecorated)
2570                 action = undecorated ? prop_atoms.net_wm_state_remove :
2571                     prop_atoms.net_wm_state_add;
2572         }
2573     
2574         if (action == prop_atoms.net_wm_state_add) {
2575             if (state == prop_atoms.net_wm_state_modal) {
2576                 modal = TRUE;
2577             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2578                 max_vert = TRUE;
2579             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2580                 max_horz = TRUE;
2581             } else if (state == prop_atoms.net_wm_state_shaded) {
2582                 shaded = TRUE;
2583             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2584                 self->skip_taskbar = TRUE;
2585             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2586                 self->skip_pager = TRUE;
2587             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2588                 fullscreen = TRUE;
2589             } else if (state == prop_atoms.net_wm_state_above) {
2590                 self->above = TRUE;
2591                 self->below = FALSE;
2592             } else if (state == prop_atoms.net_wm_state_below) {
2593                 self->above = FALSE;
2594                 self->below = TRUE;
2595             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2596                 undecorated = TRUE;
2597             }
2598
2599         } else { /* action == prop_atoms.net_wm_state_remove */
2600             if (state == prop_atoms.net_wm_state_modal) {
2601                 modal = FALSE;
2602             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2603                 max_vert = FALSE;
2604             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2605                 max_horz = FALSE;
2606             } else if (state == prop_atoms.net_wm_state_shaded) {
2607                 shaded = FALSE;
2608             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2609                 self->skip_taskbar = FALSE;
2610             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2611                 self->skip_pager = FALSE;
2612             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2613                 fullscreen = FALSE;
2614             } else if (state == prop_atoms.net_wm_state_above) {
2615                 self->above = FALSE;
2616             } else if (state == prop_atoms.net_wm_state_below) {
2617                 self->below = FALSE;
2618             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2619                 undecorated = FALSE;
2620             }
2621         }
2622     }
2623     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2624         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2625             /* toggling both */
2626             if (max_horz == max_vert) { /* both going the same way */
2627                 client_maximize(self, max_horz, 0, TRUE);
2628             } else {
2629                 client_maximize(self, max_horz, 1, TRUE);
2630                 client_maximize(self, max_vert, 2, TRUE);
2631             }
2632         } else {
2633             /* toggling one */
2634             if (max_horz != self->max_horz)
2635                 client_maximize(self, max_horz, 1, TRUE);
2636             else
2637                 client_maximize(self, max_vert, 2, TRUE);
2638         }
2639     }
2640     /* change fullscreen state before shading, as it will affect if the window
2641        can shade or not */
2642     if (fullscreen != self->fullscreen)
2643         client_fullscreen(self, fullscreen, TRUE);
2644     if (shaded != self->shaded)
2645         client_shade(self, shaded);
2646     if (undecorated != self->undecorated)
2647         client_set_undecorated(self, undecorated);
2648     if (modal != self->modal) {
2649         self->modal = modal;
2650         /* when a window changes modality, then its stacking order with its
2651            transients needs to change */
2652         client_raise(self);
2653     }
2654     client_calc_layer(self);
2655     client_change_state(self); /* change the hint to reflect these changes */
2656 }
2657
2658 ObClient *client_focus_target(ObClient *self)
2659 {
2660     ObClient *child;
2661      
2662     /* if we have a modal child, then focus it, not us */
2663     child = client_search_modal_child(client_search_top_transient(self));
2664     if (child) return child;
2665     return self;
2666 }
2667
2668 gboolean client_can_focus(ObClient *self)
2669 {
2670     XEvent ev;
2671
2672     /* choose the correct target */
2673     self = client_focus_target(self);
2674
2675     if (!self->frame->visible)
2676         return FALSE;
2677
2678     if (!(self->can_focus || self->focus_notify))
2679         return FALSE;
2680
2681     /* do a check to see if the window has already been unmapped or destroyed
2682        do this intelligently while watching out for unmaps we've generated
2683        (ignore_unmaps > 0) */
2684     if (XCheckTypedWindowEvent(ob_display, self->window,
2685                                DestroyNotify, &ev)) {
2686         XPutBackEvent(ob_display, &ev);
2687         return FALSE;
2688     }
2689     while (XCheckTypedWindowEvent(ob_display, self->window,
2690                                   UnmapNotify, &ev)) {
2691         if (self->ignore_unmaps) {
2692             self->ignore_unmaps--;
2693         } else {
2694             XPutBackEvent(ob_display, &ev);
2695             return FALSE;
2696         }
2697     }
2698
2699     return TRUE;
2700 }
2701
2702 gboolean client_focus(ObClient *self)
2703 {
2704     /* choose the correct target */
2705     self = client_focus_target(self);
2706
2707     if (!client_can_focus(self)) {
2708         if (!self->frame->visible) {
2709             /* update the focus lists */
2710             focus_order_to_top(self);
2711         }
2712         return FALSE;
2713     }
2714
2715     if (self->can_focus) {
2716         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2717            I choose to use it always, hopefully to find errors quicker, if any
2718            are left. (I hate X. I hate focus events.)
2719            
2720            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2721            #799. So now it is RevertToNone again.
2722         */
2723         XSetInputFocus(ob_display, self->window, RevertToNone,
2724                        event_lasttime);
2725     }
2726
2727     if (self->focus_notify) {
2728         XEvent ce;
2729         ce.xclient.type = ClientMessage;
2730         ce.xclient.message_type = prop_atoms.wm_protocols;
2731         ce.xclient.display = ob_display;
2732         ce.xclient.window = self->window;
2733         ce.xclient.format = 32;
2734         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2735         ce.xclient.data.l[1] = event_lasttime;
2736         ce.xclient.data.l[2] = 0l;
2737         ce.xclient.data.l[3] = 0l;
2738         ce.xclient.data.l[4] = 0l;
2739         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2740     }
2741
2742 #ifdef DEBUG_FOCUS
2743     ob_debug("%sively focusing %lx at %d\n",
2744              (self->can_focus ? "act" : "pass"),
2745              self->window, (gint) event_lasttime);
2746 #endif
2747
2748     /* Cause the FocusIn to come back to us. Important for desktop switches,
2749        since otherwise we'll have no FocusIn on the queue and send it off to
2750        the focus_backup. */
2751     XSync(ob_display, FALSE);
2752     return TRUE;
2753 }
2754
2755 void client_unfocus(ObClient *self)
2756 {
2757     if (focus_client == self) {
2758 #ifdef DEBUG_FOCUS
2759         ob_debug("client_unfocus for %lx\n", self->window);
2760 #endif
2761         focus_fallback(OB_FOCUS_FALLBACK_UNFOCUSING);
2762     }
2763 }
2764
2765 void client_activate(ObClient *self, gboolean here)
2766 {
2767     if (client_normal(self) && screen_showing_desktop)
2768         screen_show_desktop(FALSE);
2769     if (self->iconic)
2770         client_iconify(self, FALSE, here);
2771     if (self->desktop != DESKTOP_ALL &&
2772         self->desktop != screen_desktop) {
2773         if (here)
2774             client_set_desktop(self, screen_desktop, FALSE);
2775         else
2776             screen_set_desktop(self->desktop);
2777     } else if (!self->frame->visible)
2778         /* if its not visible for other reasons, then don't mess
2779            with it */
2780         return;
2781     if (self->shaded)
2782         client_shade(self, FALSE);
2783
2784     client_focus(self);
2785
2786     /* we do this an action here. this is rather important. this is because
2787        we want the results from the focus change to take place BEFORE we go
2788        about raising the window. when a fullscreen window loses focus, we need
2789        this or else the raise wont be able to raise above the to-lose-focus
2790        fullscreen window. */
2791     client_raise(self);
2792 }
2793
2794 void client_raise(ObClient *self)
2795 {
2796     action_run_string("Raise", self);
2797 }
2798
2799 void client_lower(ObClient *self)
2800 {
2801     action_run_string("Lower", self);
2802 }
2803
2804 gboolean client_focused(ObClient *self)
2805 {
2806     return self == focus_client;
2807 }
2808
2809 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
2810 {
2811     guint i;
2812     /* si is the smallest image >= req */
2813     /* li is the largest image < req */
2814     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2815
2816     if (!self->nicons) {
2817         ObClientIcon *parent = NULL;
2818
2819         if (self->transient_for) {
2820             if (self->transient_for != OB_TRAN_GROUP)
2821                 parent = client_icon_recursive(self->transient_for, w, h);
2822             else {
2823                 GSList *it;
2824                 for (it = self->group->members; it; it = g_slist_next(it)) {
2825                     ObClient *c = it->data;
2826                     if (c != self && !c->transient_for) {
2827                         if ((parent = client_icon_recursive(c, w, h)))
2828                             break;
2829                     }
2830                 }
2831             }
2832         }
2833         
2834         return parent;
2835     }
2836
2837     for (i = 0; i < self->nicons; ++i) {
2838         size = self->icons[i].width * self->icons[i].height;
2839         if (size < smallest && size >= (unsigned)(w * h)) {
2840             smallest = size;
2841             si = i;
2842         }
2843         if (size > largest && size <= (unsigned)(w * h)) {
2844             largest = size;
2845             li = i;
2846         }
2847     }
2848     if (largest == 0) /* didnt find one smaller than the requested size */
2849         return &self->icons[si];
2850     return &self->icons[li];
2851 }
2852
2853 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
2854 {
2855     ObClientIcon *ret;
2856     static ObClientIcon deficon;
2857
2858     if (!(ret = client_icon_recursive(self, w, h))) {
2859         deficon.width = deficon.height = 48;
2860         deficon.data = ob_rr_theme->def_win_icon;
2861         ret = &deficon;
2862     }
2863     return ret;
2864 }
2865
2866 /* this be mostly ripped from fvwm */
2867 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2868 {
2869     gint my_cx, my_cy, his_cx, his_cy;
2870     gint offset = 0;
2871     gint distance = 0;
2872     gint score, best_score;
2873     ObClient *best_client, *cur;
2874     GList *it;
2875
2876     if(!client_list)
2877         return NULL;
2878
2879     /* first, find the centre coords of the currently focused window */
2880     my_cx = c->frame->area.x + c->frame->area.width / 2;
2881     my_cy = c->frame->area.y + c->frame->area.height / 2;
2882
2883     best_score = -1;
2884     best_client = NULL;
2885
2886     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
2887         cur = it->data;
2888
2889         /* the currently selected window isn't interesting */
2890         if(cur == c)
2891             continue;
2892         if (!client_normal(cur))
2893             continue;
2894         if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2895             continue;
2896         if(cur->iconic)
2897             continue;
2898         if(client_focus_target(cur) == cur &&
2899            !(cur->can_focus || cur->focus_notify))
2900             continue;
2901
2902         /* find the centre coords of this window, from the
2903          * currently focused window's point of view */
2904         his_cx = (cur->frame->area.x - my_cx)
2905             + cur->frame->area.width / 2;
2906         his_cy = (cur->frame->area.y - my_cy)
2907             + cur->frame->area.height / 2;
2908
2909         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
2910            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
2911             gint tx;
2912             /* Rotate the diagonals 45 degrees counterclockwise.
2913              * To do this, multiply the matrix /+h +h\ with the
2914              * vector (x y).                   \-h +h/
2915              * h = sqrt(0.5). We can set h := 1 since absolute
2916              * distance doesn't matter here. */
2917             tx = his_cx + his_cy;
2918             his_cy = -his_cx + his_cy;
2919             his_cx = tx;
2920         }
2921
2922         switch(dir) {
2923         case OB_DIRECTION_NORTH:
2924         case OB_DIRECTION_SOUTH:
2925         case OB_DIRECTION_NORTHEAST:
2926         case OB_DIRECTION_SOUTHWEST:
2927             offset = (his_cx < 0) ? -his_cx : his_cx;
2928             distance = ((dir == OB_DIRECTION_NORTH ||
2929                          dir == OB_DIRECTION_NORTHEAST) ?
2930                         -his_cy : his_cy);
2931             break;
2932         case OB_DIRECTION_EAST:
2933         case OB_DIRECTION_WEST:
2934         case OB_DIRECTION_SOUTHEAST:
2935         case OB_DIRECTION_NORTHWEST:
2936             offset = (his_cy < 0) ? -his_cy : his_cy;
2937             distance = ((dir == OB_DIRECTION_WEST ||
2938                          dir == OB_DIRECTION_NORTHWEST) ?
2939                         -his_cx : his_cx);
2940             break;
2941         }
2942
2943         /* the target must be in the requested direction */
2944         if(distance <= 0)
2945             continue;
2946
2947         /* Calculate score for this window.  The smaller the better. */
2948         score = distance + offset;
2949
2950         /* windows more than 45 degrees off the direction are
2951          * heavily penalized and will only be chosen if nothing
2952          * else within a million pixels */
2953         if(offset > distance)
2954             score += 1000000;
2955
2956         if(best_score == -1 || score < best_score)
2957             best_client = cur,
2958                 best_score = score;
2959     }
2960
2961     return best_client;
2962 }
2963
2964 void client_set_layer(ObClient *self, gint layer)
2965 {
2966     if (layer < 0) {
2967         self->below = TRUE;
2968         self->above = FALSE;
2969     } else if (layer == 0) {
2970         self->below = self->above = FALSE;
2971     } else {
2972         self->below = FALSE;
2973         self->above = TRUE;
2974     }
2975     client_calc_layer(self);
2976     client_change_state(self); /* reflect this in the state hints */
2977 }
2978
2979 void client_set_undecorated(ObClient *self, gboolean undecorated)
2980 {
2981     if (self->undecorated != undecorated) {
2982         self->undecorated = undecorated;
2983         client_setup_decor_and_functions(self);
2984         client_change_state(self); /* reflect this in the state hints */
2985     }
2986 }
2987
2988 guint client_monitor(ObClient *self)
2989 {
2990     guint i;
2991     guint most = 0;
2992     guint mostv = 0;
2993
2994     for (i = 0; i < screen_num_monitors; ++i) {
2995         Rect *area = screen_physical_area_monitor(i);
2996         if (RECT_INTERSECTS_RECT(*area, self->frame->area)) {
2997             Rect r;
2998             guint v;
2999
3000             RECT_SET_INTERSECTION(r, *area, self->frame->area);
3001             v = r.width * r.height;
3002
3003             if (v > mostv) {
3004                 mostv = v;
3005                 most = i;
3006             }
3007         }
3008     }
3009     return most;
3010 }
3011
3012 ObClient *client_search_top_transient(ObClient *self)
3013 {
3014     /* move up the transient chain as far as possible */
3015     if (self->transient_for) {
3016         if (self->transient_for != OB_TRAN_GROUP) {
3017             return client_search_top_transient(self->transient_for);
3018         } else {
3019             GSList *it;
3020
3021             g_assert(self->group);
3022
3023             for (it = self->group->members; it; it = g_slist_next(it)) {
3024                 ObClient *c = it->data;
3025
3026                 /* checking transient_for prevents infinate loops! */
3027                 if (c != self && !c->transient_for)
3028                     break;
3029             }
3030             if (it)
3031                 return it->data;
3032         }
3033     }
3034
3035     return self;
3036 }
3037
3038 ObClient *client_search_focus_parent(ObClient *self)
3039 {
3040     if (self->transient_for) {
3041         if (self->transient_for != OB_TRAN_GROUP) {
3042             if (client_focused(self->transient_for))
3043                 return self->transient_for;
3044         } else {
3045             GSList *it;
3046
3047             for (it = self->group->members; it; it = g_slist_next(it)) {
3048                 ObClient *c = it->data;
3049
3050                 /* checking transient_for prevents infinate loops! */
3051                 if (c != self && !c->transient_for)
3052                     if (client_focused(c))
3053                         return c;
3054             }
3055         }
3056     }
3057
3058     return NULL;
3059 }
3060
3061 ObClient *client_search_parent(ObClient *self, ObClient *search)
3062 {
3063     if (self->transient_for) {
3064         if (self->transient_for != OB_TRAN_GROUP) {
3065             if (self->transient_for == search)
3066                 return search;
3067         } else {
3068             GSList *it;
3069
3070             for (it = self->group->members; it; it = g_slist_next(it)) {
3071                 ObClient *c = it->data;
3072
3073                 /* checking transient_for prevents infinate loops! */
3074                 if (c != self && !c->transient_for)
3075                     if (c == search)
3076                         return search;
3077             }
3078         }
3079     }
3080
3081     return NULL;
3082 }
3083
3084 ObClient *client_search_transient(ObClient *self, ObClient *search)
3085 {
3086     GSList *sit;
3087
3088     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3089         if (sit->data == search)
3090             return search;
3091         if (client_search_transient(sit->data, search))
3092             return search;
3093     }
3094     return NULL;
3095 }
3096
3097 void client_update_sm_client_id(ObClient *self)
3098 {
3099     g_free(self->sm_client_id);
3100     self->sm_client_id = NULL;
3101
3102     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3103         self->group)
3104         PROP_GETS(self->group->leader, sm_client_id, locale,
3105                   &self->sm_client_id);
3106 }
3107
3108 /* finds the nearest edge in the given direction from the current client
3109  * note to self: the edge is the -frame- edge (the actual one), not the
3110  * client edge.
3111  */
3112 gint client_directional_edge_search(ObClient *c, ObDirection dir)
3113 {
3114     gint dest;
3115     gint my_edge_start, my_edge_end, my_offset;
3116     GList *it;
3117     Rect *a;
3118     
3119     if(!client_list)
3120         return -1;
3121
3122     a = screen_area(c->desktop);
3123
3124     switch(dir) {
3125     case OB_DIRECTION_NORTH:
3126         my_edge_start = c->frame->area.x;
3127         my_edge_end = c->frame->area.x + c->frame->area.width;
3128         my_offset = c->frame->area.y;
3129         
3130         /* default: top of screen */
3131         dest = a->y;
3132
3133         for(it = client_list; it; it = g_list_next(it)) {
3134             gint his_edge_start, his_edge_end, his_offset;
3135             ObClient *cur = it->data;
3136
3137             if(cur == c)
3138                 continue;
3139             if(!client_normal(cur))
3140                 continue;
3141             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3142                 continue;
3143             if(cur->iconic)
3144                 continue;
3145
3146             his_edge_start = cur->frame->area.x;
3147             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3148             his_offset = cur->frame->area.y + cur->frame->area.height;
3149
3150             if(his_offset + 1 > my_offset)
3151                 continue;
3152
3153             if(his_offset < dest)
3154                 continue;
3155             
3156             if(his_edge_start >= my_edge_start &&
3157                his_edge_start <= my_edge_end)
3158                 dest = his_offset;
3159
3160             if(my_edge_start >= his_edge_start &&
3161                my_edge_start <= his_edge_end)
3162                 dest = his_offset;
3163
3164         }
3165         break;
3166     case OB_DIRECTION_SOUTH:
3167         my_edge_start = c->frame->area.x;
3168         my_edge_end = c->frame->area.x + c->frame->area.width;
3169         my_offset = c->frame->area.y + c->frame->area.height;
3170
3171         /* default: bottom of screen */
3172         dest = a->y + a->height;
3173
3174         for(it = client_list; it; it = g_list_next(it)) {
3175             gint his_edge_start, his_edge_end, his_offset;
3176             ObClient *cur = it->data;
3177
3178             if(cur == c)
3179                 continue;
3180             if(!client_normal(cur))
3181                 continue;
3182             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3183                 continue;
3184             if(cur->iconic)
3185                 continue;
3186
3187             his_edge_start = cur->frame->area.x;
3188             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3189             his_offset = cur->frame->area.y;
3190
3191
3192             if(his_offset - 1 < my_offset)
3193                 continue;
3194             
3195             if(his_offset > dest)
3196                 continue;
3197             
3198             if(his_edge_start >= my_edge_start &&
3199                his_edge_start <= my_edge_end)
3200                 dest = his_offset;
3201
3202             if(my_edge_start >= his_edge_start &&
3203                my_edge_start <= his_edge_end)
3204                 dest = his_offset;
3205
3206         }
3207         break;
3208     case OB_DIRECTION_WEST:
3209         my_edge_start = c->frame->area.y;
3210         my_edge_end = c->frame->area.y + c->frame->area.height;
3211         my_offset = c->frame->area.x;
3212
3213         /* default: leftmost egde of screen */
3214         dest = a->x;
3215
3216         for(it = client_list; it; it = g_list_next(it)) {
3217             gint his_edge_start, his_edge_end, his_offset;
3218             ObClient *cur = it->data;
3219
3220             if(cur == c)
3221                 continue;
3222             if(!client_normal(cur))
3223                 continue;
3224             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3225                 continue;
3226             if(cur->iconic)
3227                 continue;
3228
3229             his_edge_start = cur->frame->area.y;
3230             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3231             his_offset = cur->frame->area.x + cur->frame->area.width;
3232
3233             if(his_offset + 1 > my_offset)
3234                 continue;
3235             
3236             if(his_offset < dest)
3237                 continue;
3238             
3239             if(his_edge_start >= my_edge_start &&
3240                his_edge_start <= my_edge_end)
3241                 dest = his_offset;
3242
3243             if(my_edge_start >= his_edge_start &&
3244                my_edge_start <= his_edge_end)
3245                 dest = his_offset;
3246                 
3247
3248         }
3249         break;
3250     case OB_DIRECTION_EAST:
3251         my_edge_start = c->frame->area.y;
3252         my_edge_end = c->frame->area.y + c->frame->area.height;
3253         my_offset = c->frame->area.x + c->frame->area.width;
3254         
3255         /* default: rightmost edge of screen */
3256         dest = a->x + a->width;
3257
3258         for(it = client_list; it; it = g_list_next(it)) {
3259             gint his_edge_start, his_edge_end, his_offset;
3260             ObClient *cur = it->data;
3261
3262             if(cur == c)
3263                 continue;
3264             if(!client_normal(cur))
3265                 continue;
3266             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3267                 continue;
3268             if(cur->iconic)
3269                 continue;
3270
3271             his_edge_start = cur->frame->area.y;
3272             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3273             his_offset = cur->frame->area.x;
3274
3275             if(his_offset - 1 < my_offset)
3276                 continue;
3277             
3278             if(his_offset > dest)
3279                 continue;
3280             
3281             if(his_edge_start >= my_edge_start &&
3282                his_edge_start <= my_edge_end)
3283                 dest = his_offset;
3284
3285             if(my_edge_start >= his_edge_start &&
3286                my_edge_start <= his_edge_end)
3287                 dest = his_offset;
3288
3289         }
3290         break;
3291     case OB_DIRECTION_NORTHEAST:
3292     case OB_DIRECTION_SOUTHEAST:
3293     case OB_DIRECTION_NORTHWEST:
3294     case OB_DIRECTION_SOUTHWEST:
3295         /* not implemented */
3296     default:
3297         g_assert_not_reached();
3298     }
3299     return dest;
3300 }
3301
3302 ObClient* client_under_pointer()
3303 {
3304     gint x, y;
3305     GList *it;
3306     ObClient *ret = NULL;
3307
3308     if (screen_pointer_pos(&x, &y)) {
3309         for (it = stacking_list; it; it = g_list_next(it)) {
3310             if (WINDOW_IS_CLIENT(it->data)) {
3311                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3312                 if (c->frame->visible &&
3313                     RECT_CONTAINS(c->frame->area, x, y)) {
3314                     ret = c;
3315                     break;
3316                 }
3317             }
3318         }
3319     }
3320     return ret;
3321 }
3322
3323 gboolean client_has_group_siblings(ObClient *self)
3324 {
3325     return self->group && self->group->members->next;
3326 }