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