]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/client.c
dont resize the window when restoring session state if the sizes are invalid
[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 */
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->below) l = OB_STACKING_LAYER_TOP;
1701         else l = OB_STACKING_LAYER_NORMAL;
1702     }
1703     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1704     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1705     else l = OB_STACKING_LAYER_NORMAL;
1706
1707     return l;
1708 }
1709
1710 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1711                                         ObStackingLayer l, gboolean raised)
1712 {
1713     ObStackingLayer old, own;
1714     GSList *it;
1715
1716     old = self->layer;
1717     own = calc_layer(self);
1718     self->layer = l > own ? l : own;
1719
1720     for (it = self->transients; it; it = it->next)
1721         client_calc_layer_recursive(it->data, orig,
1722                                     l, raised ? raised : l != old);
1723
1724     if (!raised && l != old)
1725         if (orig->frame) { /* only restack if the original window is managed */
1726             /* XXX add_non_intrusive ever? */
1727             stacking_remove(CLIENT_AS_WINDOW(self));
1728             stacking_add(CLIENT_AS_WINDOW(self));
1729         }
1730 }
1731
1732 void client_calc_layer(ObClient *self)
1733 {
1734     ObStackingLayer l;
1735     ObClient *orig;
1736
1737     orig = self;
1738
1739     /* transients take on the layer of their parents */
1740     self = client_search_top_transient(self);
1741
1742     l = calc_layer(self);
1743
1744     client_calc_layer_recursive(self, orig, l, FALSE);
1745 }
1746
1747 gboolean client_should_show(ObClient *self)
1748 {
1749     if (self->iconic) return FALSE;
1750     else if (!(self->desktop == screen_desktop ||
1751                self->desktop == DESKTOP_ALL)) return FALSE;
1752     else if (client_normal(self) && screen_showing_desktop) return FALSE;
1753     
1754     return TRUE;
1755 }
1756
1757 static void client_showhide(ObClient *self)
1758 {
1759
1760     if (client_should_show(self))
1761         frame_show(self->frame);
1762     else
1763         frame_hide(self->frame);
1764 }
1765
1766 gboolean client_normal(ObClient *self) {
1767     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
1768               self->type == OB_CLIENT_TYPE_DOCK ||
1769               self->type == OB_CLIENT_TYPE_SPLASH);
1770 }
1771
1772 static void client_apply_startup_state(ObClient *self)
1773 {
1774     /* these are in a carefully crafted order.. */
1775
1776     if (self->iconic) {
1777         self->iconic = FALSE;
1778         client_iconify(self, TRUE, FALSE);
1779     }
1780     if (self->fullscreen) {
1781         self->fullscreen = FALSE;
1782         client_fullscreen(self, TRUE, FALSE);
1783     }
1784     if (self->undecorated) {
1785         self->undecorated = FALSE;
1786         client_set_undecorated(self, TRUE);
1787     }
1788     if (self->shaded) {
1789         self->shaded = FALSE;
1790         client_shade(self, TRUE);
1791     }
1792     if (self->urgent)
1793         client_urgent_notify(self);
1794   
1795     if (self->max_vert && self->max_horz) {
1796         self->max_vert = self->max_horz = FALSE;
1797         client_maximize(self, TRUE, 0, FALSE);
1798     } else if (self->max_vert) {
1799         self->max_vert = FALSE;
1800         client_maximize(self, TRUE, 2, FALSE);
1801     } else if (self->max_horz) {
1802         self->max_horz = FALSE;
1803         client_maximize(self, TRUE, 1, FALSE);
1804     }
1805
1806     /* nothing to do for the other states:
1807        skip_taskbar
1808        skip_pager
1809        modal
1810        above
1811        below
1812     */
1813 }
1814
1815 void client_configure_full(ObClient *self, ObCorner anchor,
1816                            int x, int y, int w, int h,
1817                            gboolean user, gboolean final,
1818                            gboolean force_reply)
1819 {
1820     gint oldw, oldh;
1821     gboolean send_resize_client;
1822     gboolean moved = FALSE, resized = FALSE;
1823     guint fdecor = self->frame->decorations;
1824     gboolean fhorz = self->frame->max_horz;
1825
1826     /* make the frame recalculate its dimentions n shit without changing
1827        anything visible for real, this way the constraints below can work with
1828        the updated frame dimensions. */
1829     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
1830
1831     /* gets the frame's position */
1832     frame_client_gravity(self->frame, &x, &y);
1833
1834     /* these positions are frame positions, not client positions */
1835
1836     /* set the size and position if fullscreen */
1837     if (self->fullscreen) {
1838 #ifdef VIDMODE
1839         int dot;
1840         XF86VidModeModeLine mode;
1841 #endif
1842         Rect *a;
1843         guint i;
1844
1845         i = client_monitor(self);
1846         a = screen_physical_area_monitor(i);
1847
1848 #ifdef VIDMODE
1849         if (i == 0 && /* primary head */
1850             extensions_vidmode &&
1851             XF86VidModeGetViewPort(ob_display, ob_screen, &x, &y) &&
1852             /* get the mode last so the mode.privsize isnt freed incorrectly */
1853             XF86VidModeGetModeLine(ob_display, ob_screen, &dot, &mode)) {
1854             x += a->x;
1855             y += a->y;
1856             w = mode.hdisplay;
1857             h = mode.vdisplay;
1858             if (mode.privsize) XFree(mode.private);
1859         } else
1860 #endif
1861         {
1862             x = a->x;
1863             y = a->y;
1864             w = a->width;
1865             h = a->height;
1866         }
1867
1868         user = FALSE; /* ignore that increment etc shit when in fullscreen */
1869     } else {
1870         Rect *a;
1871
1872         a = screen_area_monitor(self->desktop, client_monitor(self));
1873
1874         /* set the size and position if maximized */
1875         if (self->max_horz) {
1876             x = a->x;
1877             w = a->width - self->frame->size.left - self->frame->size.right;
1878         }
1879         if (self->max_vert) {
1880             y = a->y;
1881             h = a->height - self->frame->size.top - self->frame->size.bottom;
1882         }
1883     }
1884
1885     /* gets the client's position */
1886     frame_frame_gravity(self->frame, &x, &y);
1887
1888     /* these override the above states! if you cant move you can't move! */
1889     if (user) {
1890         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
1891             x = self->area.x;
1892             y = self->area.y;
1893         }
1894         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
1895             w = self->area.width;
1896             h = self->area.height;
1897         }
1898     }
1899
1900     if (!(w == self->area.width && h == self->area.height)) {
1901         int basew, baseh, minw, minh;
1902
1903         /* base size is substituted with min size if not specified */
1904         if (self->base_size.width || self->base_size.height) {
1905             basew = self->base_size.width;
1906             baseh = self->base_size.height;
1907         } else {
1908             basew = self->min_size.width;
1909             baseh = self->min_size.height;
1910         }
1911         /* min size is substituted with base size if not specified */
1912         if (self->min_size.width || self->min_size.height) {
1913             minw = self->min_size.width;
1914             minh = self->min_size.height;
1915         } else {
1916             minw = self->base_size.width;
1917             minh = self->base_size.height;
1918         }
1919
1920         /* if this is a user-requested resize, then check against min/max
1921            sizes */
1922
1923         /* smaller than min size or bigger than max size? */
1924         if (w > self->max_size.width) w = self->max_size.width;
1925         if (w < minw) w = minw;
1926         if (h > self->max_size.height) h = self->max_size.height;
1927         if (h < minh) h = minh;
1928
1929         w -= basew;
1930         h -= baseh;
1931
1932         /* keep to the increments */
1933         w /= self->size_inc.width;
1934         h /= self->size_inc.height;
1935
1936         /* you cannot resize to nothing */
1937         if (basew + w < 1) w = 1 - basew;
1938         if (baseh + h < 1) h = 1 - baseh;
1939   
1940         /* store the logical size */
1941         SIZE_SET(self->logical_size,
1942                  self->size_inc.width > 1 ? w : w + basew,
1943                  self->size_inc.height > 1 ? h : h + baseh);
1944
1945         w *= self->size_inc.width;
1946         h *= self->size_inc.height;
1947
1948         w += basew;
1949         h += baseh;
1950
1951         /* adjust the height to match the width for the aspect ratios.
1952            for this, min size is not substituted for base size ever. */
1953         w -= self->base_size.width;
1954         h -= self->base_size.height;
1955
1956         if (self->min_ratio)
1957             if (h * self->min_ratio > w) {
1958                 h = (int)(w / self->min_ratio);
1959
1960                 /* you cannot resize to nothing */
1961                 if (h < 1) {
1962                     h = 1;
1963                     w = (int)(h * self->min_ratio);
1964                 }
1965             }
1966         if (self->max_ratio)
1967             if (h * self->max_ratio < w) {
1968                 h = (int)(w / self->max_ratio);
1969
1970                 /* you cannot resize to nothing */
1971                 if (h < 1) {
1972                     h = 1;
1973                     w = (int)(h * self->min_ratio);
1974                 }
1975             }
1976
1977         w += self->base_size.width;
1978         h += self->base_size.height;
1979     }
1980
1981     g_assert(w > 0);
1982     g_assert(h > 0);
1983
1984     switch (anchor) {
1985     case OB_CORNER_TOPLEFT:
1986         break;
1987     case OB_CORNER_TOPRIGHT:
1988         x -= w - self->area.width;
1989         break;
1990     case OB_CORNER_BOTTOMLEFT:
1991         y -= h - self->area.height;
1992         break;
1993     case OB_CORNER_BOTTOMRIGHT:
1994         x -= w - self->area.width;
1995         y -= h - self->area.height;
1996         break;
1997     }
1998
1999     moved = x != self->area.x || y != self->area.y;
2000     resized = w != self->area.width || h != self->area.height;
2001
2002     oldw = self->area.width;
2003     oldh = self->area.height;
2004     RECT_SET(self->area, x, y, w, h);
2005
2006     /* for app-requested resizes, always resize if 'resized' is true.
2007        for user-requested ones, only resize if final is true, or when
2008        resizing in redraw mode */
2009     send_resize_client = ((!user && resized) ||
2010                           (user && (final ||
2011                                     (resized && config_redraw_resize))));
2012
2013     /* if the client is enlarging, the resize the client before the frame */
2014     if (send_resize_client && user && (w > oldw || h > oldh))
2015         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2016
2017     /* move/resize the frame to match the request */
2018     if (self->frame) {
2019         if (self->decorations != fdecor || self->max_horz != fhorz)
2020             moved = resized = TRUE;
2021
2022         if (moved || resized)
2023             frame_adjust_area(self->frame, moved, resized, FALSE);
2024
2025         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2026         {
2027             XEvent event;
2028             event.type = ConfigureNotify;
2029             event.xconfigure.display = ob_display;
2030             event.xconfigure.event = self->window;
2031             event.xconfigure.window = self->window;
2032
2033             /* root window real coords */
2034             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2035                 self->border_width;
2036             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2037                 self->border_width;
2038             event.xconfigure.width = w;
2039             event.xconfigure.height = h;
2040             event.xconfigure.border_width = 0;
2041             event.xconfigure.above = self->frame->plate;
2042             event.xconfigure.override_redirect = FALSE;
2043             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2044                        FALSE, StructureNotifyMask, &event);
2045         }
2046     }
2047
2048     /* if the client is shrinking, then resize the frame before the client */
2049     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2050         XResizeWindow(ob_display, self->window, w, h);
2051
2052     XFlush(ob_display);
2053 }
2054
2055 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2056 {
2057     int x, y, w, h;
2058
2059     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2060         self->fullscreen == fs) return;                   /* already done */
2061
2062     self->fullscreen = fs;
2063     client_change_state(self); /* change the state hints on the client,
2064                                   and adjust out layer/stacking */
2065
2066     if (fs) {
2067         if (savearea)
2068             self->pre_fullscreen_area = self->area;
2069
2070         /* these are not actually used cuz client_configure will set them
2071            as appropriate when the window is fullscreened */
2072         x = y = w = h = 0;
2073     } else {
2074         Rect *a;
2075
2076         if (self->pre_fullscreen_area.width > 0 &&
2077             self->pre_fullscreen_area.height > 0)
2078         {
2079             x = self->pre_fullscreen_area.x;
2080             y = self->pre_fullscreen_area.y;
2081             w = self->pre_fullscreen_area.width;
2082             h = self->pre_fullscreen_area.height;
2083             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2084         } else {
2085             /* pick some fallbacks... */
2086             a = screen_area_monitor(self->desktop, 0);
2087             x = a->x + a->width / 4;
2088             y = a->y + a->height / 4;
2089             w = a->width / 2;
2090             h = a->height / 2;
2091         }
2092     }
2093
2094     client_setup_decor_and_functions(self);
2095
2096     client_move_resize(self, x, y, w, h);
2097
2098     /* try focus us when we go into fullscreen mode */
2099     client_focus(self);
2100 }
2101
2102 static void client_iconify_recursive(ObClient *self,
2103                                      gboolean iconic, gboolean curdesk)
2104 {
2105     GSList *it;
2106     gboolean changed = FALSE;
2107
2108
2109     if (self->iconic != iconic) {
2110         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2111                  self->window);
2112
2113         self->iconic = iconic;
2114
2115         if (iconic) {
2116             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2117                 long old;
2118
2119                 old = self->wmstate;
2120                 self->wmstate = IconicState;
2121                 if (old != self->wmstate)
2122                     PROP_MSG(self->window, kde_wm_change_state,
2123                              self->wmstate, 1, 0, 0);
2124
2125                 self->ignore_unmaps++;
2126                 /* we unmap the client itself so that we can get MapRequest
2127                    events, and because the ICCCM tells us to! */
2128                 XUnmapWindow(ob_display, self->window);
2129
2130                 /* update the focus lists.. iconic windows go to the bottom of
2131                    the list, put the new iconic window at the 'top of the
2132                    bottom'. */
2133                 focus_order_to_top(self);
2134
2135                 changed = TRUE;
2136             }
2137         } else {
2138             long old;
2139
2140             if (curdesk)
2141                 client_set_desktop(self, screen_desktop, FALSE);
2142
2143             old = self->wmstate;
2144             self->wmstate = self->shaded ? IconicState : NormalState;
2145             if (old != self->wmstate)
2146                 PROP_MSG(self->window, kde_wm_change_state,
2147                          self->wmstate, 1, 0, 0);
2148
2149             XMapWindow(ob_display, self->window);
2150
2151             /* this puts it after the current focused window */
2152             focus_order_remove(self);
2153             focus_order_add_new(self);
2154
2155             /* this is here cuz with the VIDMODE extension, the viewport can
2156                change while a fullscreen window is iconic, and when it
2157                uniconifies, it would be nice if it did so to the new position
2158                of the viewport */
2159             client_reconfigure(self);
2160
2161             changed = TRUE;
2162         }
2163     }
2164
2165     if (changed) {
2166         client_change_state(self);
2167         client_showhide(self);
2168         screen_update_areas();
2169     }
2170
2171     /* iconify all transients */
2172     for (it = self->transients; it != NULL; it = it->next)
2173         if (it->data != self) client_iconify_recursive(it->data,
2174                                                        iconic, curdesk);
2175 }
2176
2177 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2178 {
2179     /* move up the transient chain as far as possible first */
2180     self = client_search_top_transient(self);
2181
2182     client_iconify_recursive(client_search_top_transient(self),
2183                              iconic, curdesk);
2184 }
2185
2186 void client_maximize(ObClient *self, gboolean max, int dir, gboolean savearea)
2187 {
2188     int x, y, w, h;
2189      
2190     g_assert(dir == 0 || dir == 1 || dir == 2);
2191     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2192
2193     /* check if already done */
2194     if (max) {
2195         if (dir == 0 && self->max_horz && self->max_vert) return;
2196         if (dir == 1 && self->max_horz) return;
2197         if (dir == 2 && self->max_vert) return;
2198     } else {
2199         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2200         if (dir == 1 && !self->max_horz) return;
2201         if (dir == 2 && !self->max_vert) return;
2202     }
2203
2204     /* we just tell it to configure in the same place and client_configure
2205        worries about filling the screen with the window */
2206     x = self->area.x;
2207     y = self->area.y;
2208     w = self->area.width;
2209     h = self->area.height;
2210
2211     if (max) {
2212         if (savearea)
2213             self->pre_max_area = self->area;
2214     } else {
2215         Rect *a;
2216
2217         a = screen_area_monitor(self->desktop, 0);
2218         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2219             if (self->pre_max_area.width > 0) {
2220                 x = self->pre_max_area.x;
2221                 w = self->pre_max_area.width;
2222
2223                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2224                          0, self->pre_max_area.height);
2225             } else {
2226                 /* pick some fallbacks... */
2227                 x = a->x + a->width / 4;
2228                 w = a->width / 2;
2229             }
2230         }
2231         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2232             if (self->pre_max_area.height > 0) {
2233                 y = self->pre_max_area.y;
2234                 h = self->pre_max_area.height;
2235
2236                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2237                          self->pre_max_area.width, 0);
2238             } else {
2239                 /* pick some fallbacks... */
2240                 y = a->y + a->height / 4;
2241                 h = a->height / 2;
2242             }
2243         }
2244     }
2245
2246     if (dir == 0 || dir == 1) /* horz */
2247         self->max_horz = max;
2248     if (dir == 0 || dir == 2) /* vert */
2249         self->max_vert = max;
2250
2251     client_change_state(self); /* change the state hints on the client */
2252
2253     client_setup_decor_and_functions(self);
2254
2255     client_move_resize(self, x, y, w, h);
2256 }
2257
2258 void client_shade(ObClient *self, gboolean shade)
2259 {
2260     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2261          shade) ||                         /* can't shade */
2262         self->shaded == shade) return;     /* already done */
2263
2264     /* when we're iconic, don't change the wmstate */
2265     if (!self->iconic) {
2266         long old;
2267
2268         old = self->wmstate;
2269         self->wmstate = shade ? IconicState : NormalState;
2270         if (old != self->wmstate)
2271             PROP_MSG(self->window, kde_wm_change_state,
2272                      self->wmstate, 1, 0, 0);
2273     }
2274
2275     self->shaded = shade;
2276     client_change_state(self);
2277     /* resize the frame to just the titlebar */
2278     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2279 }
2280
2281 void client_close(ObClient *self)
2282 {
2283     XEvent ce;
2284
2285     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2286
2287     /*
2288       XXX: itd be cool to do timeouts and shit here for killing the client's
2289       process off
2290       like... if the window is around after 5 seconds, then the close button
2291       turns a nice red, and if this function is called again, the client is
2292       explicitly killed.
2293     */
2294
2295     ce.xclient.type = ClientMessage;
2296     ce.xclient.message_type =  prop_atoms.wm_protocols;
2297     ce.xclient.display = ob_display;
2298     ce.xclient.window = self->window;
2299     ce.xclient.format = 32;
2300     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2301     ce.xclient.data.l[1] = event_lasttime;
2302     ce.xclient.data.l[2] = 0l;
2303     ce.xclient.data.l[3] = 0l;
2304     ce.xclient.data.l[4] = 0l;
2305     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2306 }
2307
2308 void client_kill(ObClient *self)
2309 {
2310     XKillClient(ob_display, self->window);
2311 }
2312
2313 void client_set_desktop_recursive(ObClient *self,
2314                                   guint target, gboolean donthide)
2315 {
2316     guint old;
2317     GSList *it;
2318
2319     if (target != self->desktop) {
2320
2321         ob_debug("Setting desktop %u\n", target+1);
2322
2323         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2324
2325         /* remove from the old desktop(s) */
2326         focus_order_remove(self);
2327
2328         old = self->desktop;
2329         self->desktop = target;
2330         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2331         /* the frame can display the current desktop state */
2332         frame_adjust_state(self->frame);
2333         /* 'move' the window to the new desktop */
2334         if (!donthide)
2335             client_showhide(self);
2336         /* raise if it was not already on the desktop */
2337         if (old != DESKTOP_ALL)
2338             client_raise(self);
2339         screen_update_areas();
2340
2341         /* add to the new desktop(s) */
2342         if (config_focus_new)
2343             focus_order_to_top(self);
2344         else
2345             focus_order_to_bottom(self);
2346     }
2347
2348     /* move all transients */
2349     for (it = self->transients; it != NULL; it = it->next)
2350         if (it->data != self) client_set_desktop_recursive(it->data,
2351                                                            target, donthide);
2352 }
2353
2354 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2355 {
2356     client_set_desktop_recursive(client_search_top_transient(self),
2357                                  target, donthide);
2358 }
2359
2360 ObClient *client_search_modal_child(ObClient *self)
2361 {
2362     GSList *it;
2363     ObClient *ret;
2364   
2365     for (it = self->transients; it != NULL; it = it->next) {
2366         ObClient *c = it->data;
2367         if ((ret = client_search_modal_child(c))) return ret;
2368         if (c->modal) return c;
2369     }
2370     return NULL;
2371 }
2372
2373 gboolean client_validate(ObClient *self)
2374 {
2375     XEvent e; 
2376
2377     XSync(ob_display, FALSE); /* get all events on the server */
2378
2379     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2380         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2381         XPutBackEvent(ob_display, &e);
2382         return FALSE;
2383     }
2384
2385     return TRUE;
2386 }
2387
2388 void client_set_wm_state(ObClient *self, long state)
2389 {
2390     if (state == self->wmstate) return; /* no change */
2391   
2392     switch (state) {
2393     case IconicState:
2394         client_iconify(self, TRUE, TRUE);
2395         break;
2396     case NormalState:
2397         client_iconify(self, FALSE, TRUE);
2398         break;
2399     }
2400 }
2401
2402 void client_set_state(ObClient *self, Atom action, long data1, long data2)
2403 {
2404     gboolean shaded = self->shaded;
2405     gboolean fullscreen = self->fullscreen;
2406     gboolean undecorated = self->undecorated;
2407     gboolean max_horz = self->max_horz;
2408     gboolean max_vert = self->max_vert;
2409     int i;
2410
2411     if (!(action == prop_atoms.net_wm_state_add ||
2412           action == prop_atoms.net_wm_state_remove ||
2413           action == prop_atoms.net_wm_state_toggle))
2414         /* an invalid action was passed to the client message, ignore it */
2415         return; 
2416
2417     for (i = 0; i < 2; ++i) {
2418         Atom state = i == 0 ? data1 : data2;
2419     
2420         if (!state) continue;
2421
2422         /* if toggling, then pick whether we're adding or removing */
2423         if (action == prop_atoms.net_wm_state_toggle) {
2424             if (state == prop_atoms.net_wm_state_modal)
2425                 action = self->modal ? prop_atoms.net_wm_state_remove :
2426                     prop_atoms.net_wm_state_add;
2427             else if (state == prop_atoms.net_wm_state_maximized_vert)
2428                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2429                     prop_atoms.net_wm_state_add;
2430             else if (state == prop_atoms.net_wm_state_maximized_horz)
2431                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2432                     prop_atoms.net_wm_state_add;
2433             else if (state == prop_atoms.net_wm_state_shaded)
2434                 action = shaded ? prop_atoms.net_wm_state_remove :
2435                     prop_atoms.net_wm_state_add;
2436             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2437                 action = self->skip_taskbar ?
2438                     prop_atoms.net_wm_state_remove :
2439                     prop_atoms.net_wm_state_add;
2440             else if (state == prop_atoms.net_wm_state_skip_pager)
2441                 action = self->skip_pager ?
2442                     prop_atoms.net_wm_state_remove :
2443                     prop_atoms.net_wm_state_add;
2444             else if (state == prop_atoms.net_wm_state_fullscreen)
2445                 action = fullscreen ?
2446                     prop_atoms.net_wm_state_remove :
2447                     prop_atoms.net_wm_state_add;
2448             else if (state == prop_atoms.net_wm_state_above)
2449                 action = self->above ? prop_atoms.net_wm_state_remove :
2450                     prop_atoms.net_wm_state_add;
2451             else if (state == prop_atoms.net_wm_state_below)
2452                 action = self->below ? prop_atoms.net_wm_state_remove :
2453                     prop_atoms.net_wm_state_add;
2454             else if (state == prop_atoms.ob_wm_state_undecorated)
2455                 action = undecorated ? prop_atoms.net_wm_state_remove :
2456                     prop_atoms.net_wm_state_add;
2457         }
2458     
2459         if (action == prop_atoms.net_wm_state_add) {
2460             if (state == prop_atoms.net_wm_state_modal) {
2461                 /* XXX raise here or something? */
2462                 self->modal = TRUE;
2463             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2464                 max_vert = TRUE;
2465             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2466                 max_horz = TRUE;
2467             } else if (state == prop_atoms.net_wm_state_shaded) {
2468                 shaded = TRUE;
2469             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2470                 self->skip_taskbar = TRUE;
2471             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2472                 self->skip_pager = TRUE;
2473             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2474                 fullscreen = TRUE;
2475             } else if (state == prop_atoms.net_wm_state_above) {
2476                 self->above = TRUE;
2477             } else if (state == prop_atoms.net_wm_state_below) {
2478                 self->below = TRUE;
2479             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2480                 undecorated = TRUE;
2481             }
2482
2483         } else { /* action == prop_atoms.net_wm_state_remove */
2484             if (state == prop_atoms.net_wm_state_modal) {
2485                 self->modal = FALSE;
2486             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2487                 max_vert = FALSE;
2488             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2489                 max_horz = FALSE;
2490             } else if (state == prop_atoms.net_wm_state_shaded) {
2491                 shaded = FALSE;
2492             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2493                 self->skip_taskbar = FALSE;
2494             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2495                 self->skip_pager = FALSE;
2496             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2497                 fullscreen = FALSE;
2498             } else if (state == prop_atoms.net_wm_state_above) {
2499                 self->above = FALSE;
2500             } else if (state == prop_atoms.net_wm_state_below) {
2501                 self->below = FALSE;
2502             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2503                 undecorated = FALSE;
2504             }
2505         }
2506     }
2507     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2508         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2509             /* toggling both */
2510             if (max_horz == max_vert) { /* both going the same way */
2511                 client_maximize(self, max_horz, 0, TRUE);
2512             } else {
2513                 client_maximize(self, max_horz, 1, TRUE);
2514                 client_maximize(self, max_vert, 2, TRUE);
2515             }
2516         } else {
2517             /* toggling one */
2518             if (max_horz != self->max_horz)
2519                 client_maximize(self, max_horz, 1, TRUE);
2520             else
2521                 client_maximize(self, max_vert, 2, TRUE);
2522         }
2523     }
2524     /* change fullscreen state before shading, as it will affect if the window
2525        can shade or not */
2526     if (fullscreen != self->fullscreen)
2527         client_fullscreen(self, fullscreen, TRUE);
2528     if (shaded != self->shaded)
2529         client_shade(self, shaded);
2530     if (undecorated != self->undecorated)
2531         client_set_undecorated(self, undecorated);
2532     client_calc_layer(self);
2533     client_change_state(self); /* change the hint to reflect these changes */
2534 }
2535
2536 ObClient *client_focus_target(ObClient *self)
2537 {
2538     ObClient *child;
2539      
2540     /* if we have a modal child, then focus it, not us */
2541     child = client_search_modal_child(client_search_top_transient(self));
2542     if (child) return child;
2543     return self;
2544 }
2545
2546 gboolean client_can_focus(ObClient *self)
2547 {
2548     XEvent ev;
2549
2550     /* choose the correct target */
2551     self = client_focus_target(self);
2552
2553     if (!self->frame->visible)
2554         return FALSE;
2555
2556     if (!(self->can_focus || self->focus_notify))
2557         return FALSE;
2558
2559     /* do a check to see if the window has already been unmapped or destroyed
2560        do this intelligently while watching out for unmaps we've generated
2561        (ignore_unmaps > 0) */
2562     if (XCheckTypedWindowEvent(ob_display, self->window,
2563                                DestroyNotify, &ev)) {
2564         XPutBackEvent(ob_display, &ev);
2565         return FALSE;
2566     }
2567     while (XCheckTypedWindowEvent(ob_display, self->window,
2568                                   UnmapNotify, &ev)) {
2569         if (self->ignore_unmaps) {
2570             self->ignore_unmaps--;
2571         } else {
2572             XPutBackEvent(ob_display, &ev);
2573             return FALSE;
2574         }
2575     }
2576
2577     return TRUE;
2578 }
2579
2580 gboolean client_focus(ObClient *self)
2581 {
2582     /* choose the correct target */
2583     self = client_focus_target(self);
2584
2585     if (!client_can_focus(self)) {
2586         if (!self->frame->visible) {
2587             /* update the focus lists */
2588             focus_order_to_top(self);
2589         }
2590         return FALSE;
2591     }
2592
2593     if (self->can_focus) {
2594         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2595            I choose to use it always, hopefully to find errors quicker, if any
2596            are left. (I hate X. I hate focus events.)
2597            
2598            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2599            #799. So now it is RevertToNone again.
2600         */
2601         XSetInputFocus(ob_display, self->window, RevertToNone,
2602                        event_lasttime);
2603     }
2604
2605     if (self->focus_notify) {
2606         XEvent ce;
2607         ce.xclient.type = ClientMessage;
2608         ce.xclient.message_type = prop_atoms.wm_protocols;
2609         ce.xclient.display = ob_display;
2610         ce.xclient.window = self->window;
2611         ce.xclient.format = 32;
2612         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2613         ce.xclient.data.l[1] = event_lasttime;
2614         ce.xclient.data.l[2] = 0l;
2615         ce.xclient.data.l[3] = 0l;
2616         ce.xclient.data.l[4] = 0l;
2617         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2618     }
2619
2620 #ifdef DEBUG_FOCUS
2621     ob_debug("%sively focusing %lx at %d\n",
2622              (self->can_focus ? "act" : "pass"),
2623              self->window, (int) event_lasttime);
2624 #endif
2625
2626     /* Cause the FocusIn to come back to us. Important for desktop switches,
2627        since otherwise we'll have no FocusIn on the queue and send it off to
2628        the focus_backup. */
2629     XSync(ob_display, FALSE);
2630     return TRUE;
2631 }
2632
2633 void client_unfocus(ObClient *self)
2634 {
2635     if (focus_client == self) {
2636 #ifdef DEBUG_FOCUS
2637         ob_debug("client_unfocus for %lx\n", self->window);
2638 #endif
2639         focus_fallback(OB_FOCUS_FALLBACK_UNFOCUSING);
2640     }
2641 }
2642
2643 void client_activate(ObClient *self, gboolean here)
2644 {
2645     if (client_normal(self) && screen_showing_desktop)
2646         screen_show_desktop(FALSE);
2647     if (self->iconic)
2648         client_iconify(self, FALSE, here);
2649     if (self->desktop != DESKTOP_ALL &&
2650         self->desktop != screen_desktop) {
2651         if (here)
2652             client_set_desktop(self, screen_desktop, FALSE);
2653         else
2654             screen_set_desktop(self->desktop);
2655     } else if (!self->frame->visible)
2656         /* if its not visible for other reasons, then don't mess
2657            with it */
2658         return;
2659     if (self->shaded)
2660         client_shade(self, FALSE);
2661
2662     client_focus(self);
2663
2664     /* we do this an action here. this is rather important. this is because
2665        we want the results from the focus change to take place BEFORE we go
2666        about raising the window. when a fullscreen window loses focus, we need
2667        this or else the raise wont be able to raise above the to-lose-focus
2668        fullscreen window. */
2669     client_raise(self);
2670 }
2671
2672 void client_raise(ObClient *self)
2673 {
2674     action_run_string("Raise", self);
2675 }
2676
2677 void client_lower(ObClient *self)
2678 {
2679     action_run_string("Raise", self);
2680 }
2681
2682 gboolean client_focused(ObClient *self)
2683 {
2684     return self == focus_client;
2685 }
2686
2687 static ObClientIcon* client_icon_recursive(ObClient *self, int w, int h)
2688 {
2689     guint i;
2690     /* si is the smallest image >= req */
2691     /* li is the largest image < req */
2692     unsigned long size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2693
2694     if (!self->nicons) {
2695         ObClientIcon *parent = NULL;
2696
2697         if (self->transient_for) {
2698             if (self->transient_for != OB_TRAN_GROUP)
2699                 parent = client_icon_recursive(self->transient_for, w, h);
2700             else {
2701                 GSList *it;
2702                 for (it = self->group->members; it; it = g_slist_next(it)) {
2703                     ObClient *c = it->data;
2704                     if (c != self && !c->transient_for) {
2705                         if ((parent = client_icon_recursive(c, w, h)))
2706                             break;
2707                     }
2708                 }
2709             }
2710         }
2711         
2712         return parent;
2713     }
2714
2715     for (i = 0; i < self->nicons; ++i) {
2716         size = self->icons[i].width * self->icons[i].height;
2717         if (size < smallest && size >= (unsigned)(w * h)) {
2718             smallest = size;
2719             si = i;
2720         }
2721         if (size > largest && size <= (unsigned)(w * h)) {
2722             largest = size;
2723             li = i;
2724         }
2725     }
2726     if (largest == 0) /* didnt find one smaller than the requested size */
2727         return &self->icons[si];
2728     return &self->icons[li];
2729 }
2730
2731 const ObClientIcon* client_icon(ObClient *self, int w, int h)
2732 {
2733     ObClientIcon *ret;
2734     static ObClientIcon deficon;
2735
2736     if (!(ret = client_icon_recursive(self, w, h))) {
2737         deficon.width = deficon.height = 48;
2738         deficon.data = ob_rr_theme->def_win_icon;
2739         ret = &deficon;
2740     }
2741     return ret;
2742 }
2743
2744 /* this be mostly ripped from fvwm */
2745 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2746 {
2747     int my_cx, my_cy, his_cx, his_cy;
2748     int offset = 0;
2749     int distance = 0;
2750     int score, best_score;
2751     ObClient *best_client, *cur;
2752     GList *it;
2753
2754     if(!client_list)
2755         return NULL;
2756
2757     /* first, find the centre coords of the currently focused window */
2758     my_cx = c->frame->area.x + c->frame->area.width / 2;
2759     my_cy = c->frame->area.y + c->frame->area.height / 2;
2760
2761     best_score = -1;
2762     best_client = NULL;
2763
2764     for(it = g_list_first(client_list); it; it = it->next) {
2765         cur = it->data;
2766
2767         /* the currently selected window isn't interesting */
2768         if(cur == c)
2769             continue;
2770         if (!client_normal(cur))
2771             continue;
2772         if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2773             continue;
2774         if(cur->iconic)
2775             continue;
2776         if(client_focus_target(cur) == cur &&
2777            !(cur->can_focus || cur->focus_notify))
2778             continue;
2779
2780         /* find the centre coords of this window, from the
2781          * currently focused window's point of view */
2782         his_cx = (cur->frame->area.x - my_cx)
2783             + cur->frame->area.width / 2;
2784         his_cy = (cur->frame->area.y - my_cy)
2785             + cur->frame->area.height / 2;
2786
2787         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
2788            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
2789             int tx;
2790             /* Rotate the diagonals 45 degrees counterclockwise.
2791              * To do this, multiply the matrix /+h +h\ with the
2792              * vector (x y).                   \-h +h/
2793              * h = sqrt(0.5). We can set h := 1 since absolute
2794              * distance doesn't matter here. */
2795             tx = his_cx + his_cy;
2796             his_cy = -his_cx + his_cy;
2797             his_cx = tx;
2798         }
2799
2800         switch(dir) {
2801         case OB_DIRECTION_NORTH:
2802         case OB_DIRECTION_SOUTH:
2803         case OB_DIRECTION_NORTHEAST:
2804         case OB_DIRECTION_SOUTHWEST:
2805             offset = (his_cx < 0) ? -his_cx : his_cx;
2806             distance = ((dir == OB_DIRECTION_NORTH ||
2807                         dir == OB_DIRECTION_NORTHEAST) ?
2808                         -his_cy : his_cy);
2809             break;
2810         case OB_DIRECTION_EAST:
2811         case OB_DIRECTION_WEST:
2812         case OB_DIRECTION_SOUTHEAST:
2813         case OB_DIRECTION_NORTHWEST:
2814             offset = (his_cy < 0) ? -his_cy : his_cy;
2815             distance = ((dir == OB_DIRECTION_WEST ||
2816                         dir == OB_DIRECTION_NORTHWEST) ?
2817                         -his_cx : his_cx);
2818             break;
2819         }
2820
2821         /* the target must be in the requested direction */
2822         if(distance <= 0)
2823             continue;
2824
2825         /* Calculate score for this window.  The smaller the better. */
2826         score = distance + offset;
2827
2828         /* windows more than 45 degrees off the direction are
2829          * heavily penalized and will only be chosen if nothing
2830          * else within a million pixels */
2831         if(offset > distance)
2832             score += 1000000;
2833
2834         if(best_score == -1 || score < best_score)
2835             best_client = cur,
2836                 best_score = score;
2837     }
2838
2839     return best_client;
2840 }
2841
2842 void client_set_layer(ObClient *self, int layer)
2843 {
2844     if (layer < 0) {
2845         self->below = TRUE;
2846         self->above = FALSE;
2847     } else if (layer == 0) {
2848         self->below = self->above = FALSE;
2849     } else {
2850         self->below = FALSE;
2851         self->above = TRUE;
2852     }
2853     client_calc_layer(self);
2854     client_change_state(self); /* reflect this in the state hints */
2855 }
2856
2857 void client_set_undecorated(ObClient *self, gboolean undecorated)
2858 {
2859     if (self->undecorated != undecorated) {
2860         self->undecorated = undecorated;
2861         client_setup_decor_and_functions(self);
2862         client_change_state(self); /* reflect this in the state hints */
2863     }
2864 }
2865
2866 guint client_monitor(ObClient *self)
2867 {
2868     guint i;
2869
2870     for (i = 0; i < screen_num_monitors; ++i) {
2871         Rect *area = screen_physical_area_monitor(i);
2872         if (RECT_INTERSECTS_RECT(*area, self->frame->area))
2873             break;
2874     }
2875     if (i == screen_num_monitors) i = 0;
2876     g_assert(i < screen_num_monitors);
2877     return i;
2878 }
2879
2880 ObClient *client_search_top_transient(ObClient *self)
2881 {
2882     /* move up the transient chain as far as possible */
2883     if (self->transient_for) {
2884         if (self->transient_for != OB_TRAN_GROUP) {
2885             return client_search_top_transient(self->transient_for);
2886         } else {
2887             GSList *it;
2888
2889             g_assert(self->group);
2890
2891             for (it = self->group->members; it; it = it->next) {
2892                 ObClient *c = it->data;
2893
2894                 /* checking transient_for prevents infinate loops! */
2895                 if (c != self && !c->transient_for)
2896                     break;
2897             }
2898             if (it)
2899                 return it->data;
2900         }
2901     }
2902
2903     return self;
2904 }
2905
2906 ObClient *client_search_focus_parent(ObClient *self)
2907 {
2908     if (self->transient_for) {
2909         if (self->transient_for != OB_TRAN_GROUP) {
2910             if (client_focused(self->transient_for))
2911                 return self->transient_for;
2912         } else {
2913             GSList *it;
2914
2915             for (it = self->group->members; it; it = it->next) {
2916                 ObClient *c = it->data;
2917
2918                 /* checking transient_for prevents infinate loops! */
2919                 if (c != self && !c->transient_for)
2920                     if (client_focused(c))
2921                         return c;
2922             }
2923         }
2924     }
2925
2926     return NULL;
2927 }
2928
2929 ObClient *client_search_parent(ObClient *self, ObClient *search)
2930 {
2931     if (self->transient_for) {
2932         if (self->transient_for != OB_TRAN_GROUP) {
2933             if (self->transient_for == search)
2934                 return search;
2935         } else {
2936             GSList *it;
2937
2938             for (it = self->group->members; it; it = it->next) {
2939                 ObClient *c = it->data;
2940
2941                 /* checking transient_for prevents infinate loops! */
2942                 if (c != self && !c->transient_for)
2943                     if (c == search)
2944                         return search;
2945             }
2946         }
2947     }
2948
2949     return NULL;
2950 }
2951
2952 ObClient *client_search_transient(ObClient *self, ObClient *search)
2953 {
2954     GSList *sit;
2955
2956     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
2957         if (sit->data == search)
2958             return search;
2959         if (client_search_transient(sit->data, search))
2960             return search;
2961     }
2962     return NULL;
2963 }
2964
2965 void client_update_sm_client_id(ObClient *self)
2966 {
2967     g_free(self->sm_client_id);
2968     self->sm_client_id = NULL;
2969
2970     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
2971         self->group)
2972         PROP_GETS(self->group->leader, sm_client_id, locale,
2973                   &self->sm_client_id);
2974 }
2975
2976 /* finds the nearest edge in the given direction from the current client
2977  * note to self: the edge is the -frame- edge (the actual one), not the
2978  * client edge.
2979  */
2980 int client_directional_edge_search(ObClient *c, ObDirection dir)
2981 {
2982     int dest;
2983     int my_edge_start, my_edge_end, my_offset;
2984     GList *it;
2985     Rect *a;
2986     
2987     if(!client_list)
2988         return -1;
2989
2990     a = screen_area(c->desktop);
2991
2992     switch(dir) {
2993     case OB_DIRECTION_NORTH:
2994         my_edge_start = c->frame->area.x;
2995         my_edge_end = c->frame->area.x + c->frame->area.width;
2996         my_offset = c->frame->area.y;
2997         
2998         dest = a->y; /* default: top of screen */
2999
3000         for(it = g_list_first(client_list); it; it = it->next) {
3001             int his_edge_start, his_edge_end, his_offset;
3002             ObClient *cur = it->data;
3003
3004             if(cur == c)
3005                 continue;
3006             if(!client_normal(cur))
3007                 continue;
3008             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3009                 continue;
3010             if(cur->iconic)
3011                 continue;
3012
3013             his_edge_start = cur->frame->area.x;
3014             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3015             his_offset = cur->frame->area.y + cur->frame->area.height;
3016
3017             if(his_offset + 1 > my_offset)
3018                 continue;
3019
3020             if(his_offset < dest)
3021                 continue;
3022             
3023             if(his_edge_start >= my_edge_start &&
3024                his_edge_start <= my_edge_end)
3025                 dest = his_offset;
3026
3027             if(my_edge_start >= his_edge_start &&
3028                my_edge_start <= his_edge_end)
3029                 dest = his_offset;
3030
3031         }
3032         break;
3033     case OB_DIRECTION_SOUTH:
3034         my_edge_start = c->frame->area.x;
3035         my_edge_end = c->frame->area.x + c->frame->area.width;
3036         my_offset = c->frame->area.y + c->frame->area.height;
3037         
3038         dest = a->y + a->height; /* default: bottom of screen */
3039
3040         for(it = g_list_first(client_list); it; it = it->next) {
3041             int his_edge_start, his_edge_end, his_offset;
3042             ObClient *cur = it->data;
3043
3044             if(cur == c)
3045                 continue;
3046             if(!client_normal(cur))
3047                 continue;
3048             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3049                 continue;
3050             if(cur->iconic)
3051                 continue;
3052
3053             his_edge_start = cur->frame->area.x;
3054             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3055             his_offset = cur->frame->area.y;
3056
3057
3058             if(his_offset - 1 < my_offset)
3059                 continue;
3060             
3061             if(his_offset > dest)
3062                 continue;
3063             
3064             if(his_edge_start >= my_edge_start &&
3065                his_edge_start <= my_edge_end)
3066                 dest = his_offset;
3067
3068             if(my_edge_start >= his_edge_start &&
3069                my_edge_start <= his_edge_end)
3070                 dest = his_offset;
3071
3072         }
3073         break;
3074     case OB_DIRECTION_WEST:
3075         my_edge_start = c->frame->area.y;
3076         my_edge_end = c->frame->area.y + c->frame->area.height;
3077         my_offset = c->frame->area.x;
3078
3079         dest = a->x; /* default: leftmost egde of screen */
3080
3081         for(it = g_list_first(client_list); it; it = it->next) {
3082             int his_edge_start, his_edge_end, his_offset;
3083             ObClient *cur = it->data;
3084
3085             if(cur == c)
3086                 continue;
3087             if(!client_normal(cur))
3088                 continue;
3089             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3090                 continue;
3091             if(cur->iconic)
3092                 continue;
3093
3094             his_edge_start = cur->frame->area.y;
3095             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3096             his_offset = cur->frame->area.x + cur->frame->area.width;
3097
3098             if(his_offset + 1 > my_offset)
3099                 continue;
3100             
3101             if(his_offset < dest)
3102                 continue;
3103             
3104             if(his_edge_start >= my_edge_start &&
3105                his_edge_start <= my_edge_end)
3106                 dest = his_offset;
3107
3108             if(my_edge_start >= his_edge_start &&
3109                my_edge_start <= his_edge_end)
3110                 dest = his_offset;
3111                 
3112
3113         }
3114         break;
3115     case OB_DIRECTION_EAST:
3116         my_edge_start = c->frame->area.y;
3117         my_edge_end = c->frame->area.y + c->frame->area.height;
3118         my_offset = c->frame->area.x + c->frame->area.width;
3119         
3120         dest = a->x + a->width; /* default: rightmost edge of screen */
3121
3122         for(it = g_list_first(client_list); it; it = it->next) {
3123             int his_edge_start, his_edge_end, his_offset;
3124             ObClient *cur = it->data;
3125
3126             if(cur == c)
3127                 continue;
3128             if(!client_normal(cur))
3129                 continue;
3130             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3131                 continue;
3132             if(cur->iconic)
3133                 continue;
3134
3135             his_edge_start = cur->frame->area.y;
3136             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3137             his_offset = cur->frame->area.x;
3138
3139             if(his_offset - 1 < my_offset)
3140                 continue;
3141             
3142             if(his_offset > dest)
3143                 continue;
3144             
3145             if(his_edge_start >= my_edge_start &&
3146                his_edge_start <= my_edge_end)
3147                 dest = his_offset;
3148
3149             if(my_edge_start >= his_edge_start &&
3150                my_edge_start <= his_edge_end)
3151                 dest = his_offset;
3152
3153         }
3154         break;
3155     case OB_DIRECTION_NORTHEAST:
3156     case OB_DIRECTION_SOUTHEAST:
3157     case OB_DIRECTION_NORTHWEST:
3158     case OB_DIRECTION_SOUTHWEST:
3159         /* not implemented */
3160     default:
3161         g_assert_not_reached();
3162     }
3163     return dest;
3164 }
3165
3166 ObClient* client_under_pointer()
3167 {
3168     int x, y;
3169     GList *it;
3170     ObClient *ret = NULL;
3171
3172     if (screen_pointer_pos(&x, &y)) {
3173         for (it = stacking_list; it != NULL; it = it->next) {
3174             if (WINDOW_IS_CLIENT(it->data)) {
3175                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3176                 if (c->frame->visible &&
3177                     RECT_CONTAINS(c->frame->area, x, y)) {
3178                     ret = c;
3179                     break;
3180                 }
3181             }
3182         }
3183     }
3184     return ret;
3185 }