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