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