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