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