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