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