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